<?php
/**
 * Abstract Integration Routes
 *
 * Base class for integration-specific REST API routes with multi-connection support.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Rest;

use Sequensy\Integrations\IntegrationStorage;
use Sequensy\Storage\WorkflowStorage;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class AbstractIntegrationRoutes
 *
 * Base class providing shared REST route implementation for integrations.
 * Supports multiple connections (accounts) per integration.
 */
abstract class AbstractIntegrationRoutes extends WP_REST_Controller {

	use RestControllerTrait;

	/**
	 * Cached descriptor instance.
	 *
	 * @var IntegrationRouteDescriptor|null
	 */
	private ?IntegrationRouteDescriptor $descriptor = null;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->namespace = 'sequensy/v1';
		$this->rest_base = $this->get_descriptor()->get_base_route();
	}

	/**
	 * Get the route descriptor for this integration.
	 *
	 * @return IntegrationRouteDescriptor Configuration descriptor.
	 */
	abstract protected function configure(): IntegrationRouteDescriptor;

	/**
	 * Create an API instance with given credentials.
	 *
	 * @param array $credentials Credential values.
	 * @return object API client instance.
	 */
	abstract protected function create_api( array $credentials ): object;

	/**
	 * Register integration-specific sub-routes.
	 *
	 * @return void
	 */
	abstract protected function register_sub_routes(): void;

	/**
	 * Get the cached descriptor.
	 *
	 * @return IntegrationRouteDescriptor
	 */
	protected function get_descriptor(): IntegrationRouteDescriptor {
		if ( null === $this->descriptor ) {
			$this->descriptor = $this->configure();
		}
		return $this->descriptor;
	}

	/**
	 * Register REST API routes.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		$descriptor = $this->get_descriptor();

		// Main integration status route.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_status' ),
					'permission_callback' => array( $this, 'permissions_check' ),
				),
			)
		);

		// Connection management routes.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connections',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'list_connections' ),
					'permission_callback' => array( $this, 'permissions_check' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_connection' ),
					'permission_callback' => array( $this, 'permissions_check' ),
					'args'                => $this->get_create_connection_args(),
				),
			)
		);

		// Single connection routes.
		$connection_id_arg = array(
			'connection_id' => array(
				'description'       => __( 'Connection ID.', 'sequensy' ),
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => 'rest_validate_request_arg',
			),
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connections/(?P<connection_id>[a-zA-Z0-9_-]+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_connection' ),
					'permission_callback' => array( $this, 'permissions_check' ),
					'args'                => $connection_id_arg,
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_connection' ),
					'permission_callback' => array( $this, 'permissions_check' ),
					'args'                => array_merge( $connection_id_arg, $this->get_update_connection_args() ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_connection' ),
					'permission_callback' => array( $this, 'permissions_check' ),
					'args'                => $connection_id_arg,
				),
			)
		);

		// Register integration-specific sub-routes.
		$this->register_sub_routes();
	}

	/**
	 * Get arguments for create connection endpoint.
	 *
	 * @return array Argument definitions.
	 */
	protected function get_create_connection_args(): array {
		$descriptor               = $this->get_descriptor();
		$credential_args          = $descriptor->get_connect_args();
		$credential_args['label'] = array(
			'description'       => __( 'User-friendly label for this connection.', 'sequensy' ),
			'type'              => 'string',
			'required'          => true,
			'sanitize_callback' => 'sanitize_text_field',
			'validate_callback' => 'rest_validate_request_arg',
		);
		return $credential_args;
	}

	/**
	 * Get arguments for update connection endpoint.
	 *
	 * @return array Argument definitions.
	 */
	protected function get_update_connection_args(): array {
		$descriptor      = $this->get_descriptor();
		$credential_args = $descriptor->get_connect_args();

		// Make all credential args optional for updates.
		foreach ( $credential_args as $key => $config ) {
			$credential_args[ $key ]['required'] = false;
		}

		$credential_args['label'] = array(
			'description'       => __( 'User-friendly label for this connection.', 'sequensy' ),
			'type'              => 'string',
			'required'          => false,
			'sanitize_callback' => 'sanitize_text_field',
			'validate_callback' => 'rest_validate_request_arg',
		);

		return $credential_args;
	}

	/**
	 * Clear the action metadata cache.
	 *
	 * Called after connection changes so the frontend receives updated
	 * is_connected status for integration actions.
	 *
	 * @return void
	 */
	protected function clear_metadata_cache(): void {
		delete_transient( 'sequensy_action_metadata' );
		delete_transient( 'sequensy_trigger_metadata' );
	}

	/**
	 * Get integration status with all connections.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function get_status( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$descriptor  = $this->get_descriptor();
		$integration = IntegrationStorage::get_integration( $descriptor->slug );

		if ( ! $integration ) {
			return $this->rest_error(
				'integration_not_found',
				/* translators: %s: integration display name */
				sprintf( __( '%s integration not found.', 'sequensy' ), $descriptor->display_name ),
				404
			);
		}

		return rest_ensure_response(
			array(
				'integration' => $integration,
			)
		);
	}

	/**
	 * List all connections for this integration.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response Response object.
	 */
	public function list_connections( WP_REST_Request $request ): WP_REST_Response {
		$descriptor  = $this->get_descriptor();
		$connections = IntegrationStorage::get_connections( $descriptor->slug );

		// Add masked credentials to each connection.
		$connections_with_credentials = array_map(
			function ( $conn ) use ( $descriptor ) {
				$masked = IntegrationStorage::get_masked_connection( $descriptor->slug, $conn['id'] );
				return $masked ?? $conn;
			},
			$connections
		);

		return rest_ensure_response( $connections_with_credentials );
	}

	/**
	 * Create a new connection.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response|WP_Error Response object or error.
	 */
	public function create_connection( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$descriptor  = $this->get_descriptor();
		$label       = $request->get_param( 'label' );
		$credentials = $descriptor->extract_credentials( $request );

		// Create API instance and validate credentials.
		$api        = $this->create_api( $credentials );
		$validation = $api->validate_credentials();

		if ( is_wp_error( $validation ) ) {
			return $this->rest_error(
				'invalid_credentials',
				$validation->get_error_message()
			);
		}

		// Create the connection.
		$connection_id = IntegrationStorage::create_connection( $descriptor->slug, $label, $credentials );

		if ( is_wp_error( $connection_id ) ) {
			return $connection_id;
		}

		$this->clear_metadata_cache();

		return rest_ensure_response(
			array(
				'success'       => true,
				'connection_id' => $connection_id,
				'message'       => sprintf(
					/* translators: 1: integration display name, 2: connection label */
					__( '%1$s connected successfully as "%2$s".', 'sequensy' ),
					$descriptor->display_name,
					$label
				),
			)
		);
	}

	/**
	 * Get a specific connection.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response|WP_Error Response object or error.
	 */
	public function get_connection( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$descriptor    = $this->get_descriptor();
		$connection_id = $request->get_param( 'connection_id' );

		$connection = IntegrationStorage::get_masked_connection( $descriptor->slug, $connection_id );

		if ( ! $connection ) {
			return $this->rest_error(
				'connection_not_found',
				__( 'Connection not found.', 'sequensy' ),
				404
			);
		}

		return rest_ensure_response( $connection );
	}

	/**
	 * Update a connection.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response|WP_Error Response object or error.
	 */
	public function update_connection( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$descriptor    = $this->get_descriptor();
		$connection_id = $request->get_param( 'connection_id' );

		// Check connection exists.
		$existing = IntegrationStorage::get_connection( $descriptor->slug, $connection_id );
		if ( ! $existing ) {
			return $this->rest_error(
				'connection_not_found',
				__( 'Connection not found.', 'sequensy' ),
				404
			);
		}

		$update_data = array();

		// Check if label is being updated.
		$label = $request->get_param( 'label' );
		if ( null !== $label ) {
			$update_data['label'] = $label;
		}

		// Check if credentials are being updated.
		$credentials     = $descriptor->extract_credentials( $request );
		$has_credentials = ! empty( array_filter( $credentials, fn( $v ) => null !== $v && '' !== $v ) );

		if ( $has_credentials ) {
			// Validate new credentials.
			$api        = $this->create_api( $credentials );
			$validation = $api->validate_credentials();

			if ( is_wp_error( $validation ) ) {
				return $this->rest_error(
					'invalid_credentials',
					$validation->get_error_message()
				);
			}

			$update_data['credentials'] = $credentials;
		}

		if ( empty( $update_data ) ) {
			return $this->rest_error(
				'no_changes',
				__( 'No changes provided.', 'sequensy' )
			);
		}

		$updated = IntegrationStorage::update_connection( $descriptor->slug, $connection_id, $update_data );

		if ( is_wp_error( $updated ) ) {
			return $updated;
		}

		if ( ! $updated ) {
			return $this->rest_error(
				'update_failed',
				__( 'Failed to update connection.', 'sequensy' ),
				500
			);
		}

		$this->clear_metadata_cache();

		return rest_ensure_response(
			array(
				'success' => true,
				'message' => __( 'Connection updated successfully.', 'sequensy' ),
			)
		);
	}

	/**
	 * Delete a connection.
	 *
	 * If this is the last connection for an integration, checks for active workflows
	 * that use this integration and requires confirmation before deletion.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response|WP_Error Response object or error.
	 */
	public function delete_connection( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$descriptor         = $this->get_descriptor();
		$connection_id      = $request->get_param( 'connection_id' );
		$confirm_deactivate = $request->get_param( 'confirm_deactivate_workflows' );

		// Check if connection exists.
		$connection = IntegrationStorage::get_connection( $descriptor->slug, $connection_id );
		if ( ! $connection ) {
			return $this->rest_error(
				'connection_not_found',
				__( 'Connection not found.', 'sequensy' ),
				404
			);
		}

		// Check if this is the last connection.
		$all_connections    = IntegrationStorage::get_connections( $descriptor->slug );
		$is_last_connection = count( $all_connections ) === 1;

		// If this is the last connection, check for affected workflows.
		if ( $is_last_connection ) {
			$workflow_storage   = new WorkflowStorage();
			$affected_workflows = $workflow_storage->get_workflows_using_integration( $descriptor->slug );

			// If there are affected workflows and user hasn't confirmed, return error with workflow list.
			if ( ! empty( $affected_workflows ) && ! $confirm_deactivate ) {
				$workflow_data = array_map(
					function ( $workflow ) {
						return array(
							'id'   => $workflow->id,
							'name' => $workflow->name,
						);
					},
					$affected_workflows
				);

				return $this->rest_error(
					'workflows_affected',
					// phpcs:disable Generic.Files.LineLength.TooLong -- Translation strings must be single literals.
					sprintf(
						/* translators: %d: number of affected workflows */
						_n(
							'This is the last connection for this integration. %d active workflow uses this integration and will be deactivated.',
							'This is the last connection for this integration. %d active workflows use this integration and will be deactivated.',
							count( $affected_workflows ),
							'sequensy'
						),
						count( $affected_workflows )
					),
					// phpcs:enable Generic.Files.LineLength.TooLong
					409,
					array( 'affected_workflows' => $workflow_data )
				);
			}

			// User confirmed, deactivate affected workflows.
			if ( ! empty( $affected_workflows ) && $confirm_deactivate ) {
				$reason = sprintf(
					/* translators: %s: integration display name */
					__( 'Integration disconnected: %s', 'sequensy' ),
					$descriptor->display_name
				);

				foreach ( $affected_workflows as $workflow ) {
					$workflow_storage->deactivate_workflow( (int) $workflow->id, $reason );
				}
			}
		}

		// Delete the connection.
		$deleted = IntegrationStorage::delete_connection( $descriptor->slug, $connection_id );

		if ( ! $deleted ) {
			return $this->rest_error(
				'delete_failed',
				__( 'Failed to delete connection.', 'sequensy' ),
				500
			);
		}

		$this->clear_metadata_cache();

		$response = array(
			'success' => true,
			/* translators: %s: integration display name */
			'message' => sprintf( __( '%s connection deleted successfully.', 'sequensy' ), $descriptor->display_name ),
		);

		// Include info about paused workflows if any.
		if ( $is_last_connection && ! empty( $affected_workflows ) ) {
			$response['paused_workflows'] = count( $affected_workflows );
		}

		return rest_ensure_response( $response );
	}

	/**
	 * Get API instance for a specific connection.
	 *
	 * @param string $connection_id Connection ID.
	 * @return object|WP_Error API instance or error.
	 */
	protected function get_api_for_connection( string $connection_id ): mixed {
		$descriptor = $this->get_descriptor();

		$credentials = IntegrationStorage::get_connection_credentials( $descriptor->slug, $connection_id );

		if ( null === $credentials ) {
			return $this->rest_error(
				'connection_not_found',
				/* translators: %s: integration display name */
				sprintf( __( '%s connection not found.', 'sequensy' ), $descriptor->display_name ),
				404
			);
		}

		return $this->create_api( $credentials );
	}

	/**
	 * Helper to make an API call with a specific connection.
	 *
	 * @param string   $connection_id Connection ID.
	 * @param callable $api_call      Function that calls the API.
	 * @return WP_REST_Response|WP_Error Response or error.
	 */
	protected function api_call_with_connection(
		string $connection_id,
		callable $api_call
	): WP_REST_Response|WP_Error {
		$api = $this->get_api_for_connection( $connection_id );

		if ( is_wp_error( $api ) ) {
			return $api;
		}

		$result = $api_call( $api );

		if ( is_wp_error( $result ) ) {
			return $this->rest_error(
				'api_error',
				$result->get_error_message()
			);
		}

		return rest_ensure_response( $result );
	}

	/**
	 * Register a GET sub-route that requires a connection.
	 *
	 * @param string   $route    Route path after /connections/{connection_id}/.
	 * @param callable $callback Handler callback.
	 * @param array    $args     Additional route arguments.
	 * @return void
	 */
	protected function register_connection_get_route( string $route, callable $callback, array $args = array() ): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connections/(?P<connection_id>[a-zA-Z0-9_-]+)/' . $route,
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => $callback,
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array_merge(
					array(
						'connection_id' => array(
							'description'       => __( 'Connection ID.', 'sequensy' ),
							'type'              => 'string',
							'required'          => true,
							'sanitize_callback' => 'sanitize_text_field',
							'validate_callback' => 'rest_validate_request_arg',
						),
					),
					$args
				),
			)
		);
	}

	/**
	 * Register a POST sub-route that requires a connection.
	 *
	 * @param string   $route    Route path after /connections/{connection_id}/.
	 * @param callable $callback Handler callback.
	 * @param array    $args     Additional route arguments.
	 * @return void
	 */
	protected function register_connection_post_route(
		string $route,
		callable $callback,
		array $args = array()
	): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connections/(?P<connection_id>[a-zA-Z0-9_-]+)/' . $route,
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => $callback,
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array_merge(
					array(
						'connection_id' => array(
							'description'       => __( 'Connection ID.', 'sequensy' ),
							'type'              => 'string',
							'required'          => true,
							'sanitize_callback' => 'sanitize_text_field',
							'validate_callback' => 'rest_validate_request_arg',
						),
					),
					$args
				),
			)
		);
	}
}
