<?php
/**
 * Version REST API Controller
 *
 * Provides REST API endpoints for workflow version management.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Rest;

use Sequensy\Container;
use Sequensy\Storage\WorkflowStorage;
use Sequensy\Storage\VersionStorage;
use Sequensy\Triggers\TriggerRegistry;
use Sequensy\Actions\ActionRegistry;
use Sequensy\Validation\WorkflowValidator;
use Sequensy\ValueObjects\WorkflowConfig;
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 VersionController
 *
 * REST API controller for workflow version management.
 */
class VersionController extends WP_REST_Controller {

	use RestControllerTrait;

	/**
	 * Workflow storage instance.
	 *
	 * @var WorkflowStorage
	 */
	private WorkflowStorage $workflow_storage;

	/**
	 * Version storage instance.
	 *
	 * @var VersionStorage
	 */
	private VersionStorage $version_storage;

	/**
	 * Trigger registry instance.
	 *
	 * @var TriggerRegistry
	 */
	private TriggerRegistry $trigger_registry;

	/**
	 * Action registry instance.
	 *
	 * @var ActionRegistry
	 */
	private ActionRegistry $action_registry;

	/**
	 * Factory method for creating controller instance with container.
	 *
	 * @param Container $container Service container instance.
	 * @return self Controller instance.
	 */
	public static function create( Container $container ): self {
		return new self(
			$container->get( 'workflow_storage' ),
			$container->get( 'version_storage' ),
			$container->get( 'trigger_registry' ),
			$container->get( 'action_registry' )
		);
	}

	/**
	 * Constructor.
	 *
	 * @param WorkflowStorage|null $workflow_storage Optional workflow storage instance.
	 * @param VersionStorage|null  $version_storage  Optional version storage instance.
	 * @param TriggerRegistry|null $trigger_registry Optional trigger registry instance.
	 * @param ActionRegistry|null  $action_registry  Optional action registry instance.
	 */
	public function __construct(
		?WorkflowStorage $workflow_storage = null,
		?VersionStorage $version_storage = null,
		?TriggerRegistry $trigger_registry = null,
		?ActionRegistry $action_registry = null
	) {
		$this->namespace = 'sequensy/v1';

		// Use provided dependencies or get from container.
		$container              = Container::getInstance();
		$this->workflow_storage = $workflow_storage ?? $container->get( 'workflow_storage' );
		$this->version_storage  = $version_storage ?? $container->get( 'version_storage' );
		$this->trigger_registry = $trigger_registry ?? $container->get( 'trigger_registry' );
		$this->action_registry  = $action_registry ?? $container->get( 'action_registry' );
	}

	/**
	 * Register REST API routes.
	 */
	public function register_routes(): void {
		// List versions for a workflow.
		register_rest_route(
			$this->namespace,
			'/workflows/(?P<workflow_id>[\d]+)/versions',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_versions' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'workflow_id' => array(
						'description'       => 'Workflow ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'limit'       => array(
						'description'       => 'Number of versions to return',
						'type'              => 'integer',
						'default'           => 10,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'offset'      => array(
						'description'       => 'Offset for pagination',
						'type'              => 'integer',
						'default'           => 0,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);

		// Publish workflow (create new version).
		register_rest_route(
			$this->namespace,
			'/workflows/(?P<workflow_id>[\d]+)/publish',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'publish_workflow' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'workflow_id' => array(
						'description'       => 'Workflow ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'notes'       => array(
						'description'       => 'Optional notes describing the version',
						'type'              => 'string',
						'default'           => '',
						'sanitize_callback' => 'sanitize_textarea_field',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);

		// Get a specific version.
		register_rest_route(
			$this->namespace,
			'/workflows/(?P<workflow_id>[\d]+)/versions/(?P<version_id>[\d]+)',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_version' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'workflow_id' => array(
						'description'       => 'Workflow ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'version_id'  => array(
						'description'       => 'Version ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);

		// Restore a version.
		register_rest_route(
			$this->namespace,
			'/workflows/(?P<workflow_id>[\d]+)/versions/(?P<version_id>[\d]+)/restore',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'restore_version' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'workflow_id' => array(
						'description'       => 'Workflow ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'version_id'  => array(
						'description'       => 'Version ID to restore',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);

		// Get workflow status (published state, unpublished changes).
		register_rest_route(
			$this->namespace,
			'/workflows/(?P<workflow_id>[\d]+)/status',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_workflow_status' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'workflow_id' => array(
						'description'       => 'Workflow ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);

		// Make a specific version live.
		register_rest_route(
			$this->namespace,
			'/workflows/(?P<workflow_id>[\d]+)/versions/(?P<version_id>[\d]+)/make-live',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'make_version_live' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'workflow_id' => array(
						'description'       => 'Workflow ID',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'version_id'  => array(
						'description'       => 'Version ID to make live',
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);
	}

	/**
	 * Get versions for a workflow.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function get_versions( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$workflow_id = (int) $request['workflow_id'];

		// Verify workflow exists.
		$workflow = $this->workflow_storage->get_workflow( $workflow_id );
		if ( ! $workflow ) {
			return $this->rest_error(
				'not_found',
				__( 'Workflow not found', 'sequensy' ),
				404
			);
		}

		$limit  = (int) $request['limit'];
		$offset = (int) $request['offset'];

		$versions = $this->version_storage->get_versions( $workflow_id, $limit, $offset );
		$total    = $this->version_storage->get_version_count( $workflow_id );

		// Add metadata to each version.
		foreach ( $versions as $version ) {
			$version->is_published = ( $workflow->published_version_id === $version->id );
		}

		$response = rest_ensure_response( $versions );
		$response->header( 'X-WP-Total', (string) $total );

		return $response;
	}

	/**
	 * Publish a workflow (create new version from draft).
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function publish_workflow( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$workflow_id = (int) $request['workflow_id'];
		$notes       = sanitize_textarea_field( $request['notes'] ?? '' );

		// Verify workflow exists.
		$workflow = $this->workflow_storage->get_workflow( $workflow_id );
		if ( ! $workflow ) {
			return $this->rest_error(
				'not_found',
				__( 'Workflow not found', 'sequensy' ),
				404
			);
		}

		// Check if there are changes to publish.
		if ( ! $this->workflow_storage->has_unpublished_changes( $workflow_id ) ) {
			return $this->rest_error(
				'no_changes',
				__( 'No changes to publish', 'sequensy' )
			);
		}

		// Validate workflow configuration before publishing.
		$validator  = new WorkflowValidator( $this->trigger_registry, $this->action_registry );
		$validation = $validator->validate_for_activation( $workflow );

		if ( ! $validation['valid'] ) {
			return $this->rest_error(
				'validation_failed',
				__( 'Workflow cannot be published due to configuration errors.', 'sequensy' ),
				400,
				array( 'errors' => $validation['errors'] )
			);
		}

		// Get current user ID.
		$user_id = get_current_user_id();

		// Create new version.
		$version_id = $this->version_storage->create_version( $workflow_id, $notes, $user_id ? $user_id : null );

		if ( ! $version_id ) {
			return $this->rest_error(
				'publish_failed',
				__( 'Failed to create version', 'sequensy' ),
				500
			);
		}

		// Update workflow's published version reference.
		$this->workflow_storage->set_published_version( $workflow_id, $version_id );

		// Get the created version.
		$version = $this->version_storage->get_version( $version_id );

		return rest_ensure_response(
			array(
				'success' => true,
				'version' => $version,
				'message' => __( 'Workflow published successfully', 'sequensy' ),
			)
		);
	}

	/**
	 * Get a specific version.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function get_version( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$workflow_id = (int) $request['workflow_id'];
		$version_id  = (int) $request['version_id'];

		// Verify workflow exists.
		$workflow = $this->workflow_storage->get_workflow( $workflow_id );
		if ( ! $workflow ) {
			return $this->rest_error(
				'not_found',
				__( 'Workflow not found', 'sequensy' ),
				404
			);
		}

		// Get the version.
		$version = $this->version_storage->get_version( $version_id );

		if ( ! $version || $version->workflow_id !== $workflow_id ) {
			return $this->rest_error(
				'not_found',
				__( 'Version not found', 'sequensy' ),
				404
			);
		}

		$version->is_published = ( $workflow->published_version_id === $version->id );

		return rest_ensure_response( $version );
	}

	/**
	 * Restore a version (load its config as the current draft).
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function restore_version( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$workflow_id = (int) $request['workflow_id'];
		$version_id  = (int) $request['version_id'];

		// Verify workflow exists.
		$workflow = $this->workflow_storage->get_workflow( $workflow_id );
		if ( ! $workflow ) {
			return $this->rest_error(
				'not_found',
				__( 'Workflow not found', 'sequensy' ),
				404
			);
		}

		// Verify version exists and belongs to workflow.
		$version = $this->version_storage->get_version( $version_id );
		if ( ! $version || $version->workflow_id !== $workflow_id ) {
			return $this->rest_error(
				'not_found',
				__( 'Version not found', 'sequensy' ),
				404
			);
		}

		// Restore the version.
		$result = $this->version_storage->restore_version( $workflow_id, $version_id );

		if ( ! $result ) {
			return $this->rest_error(
				'restore_failed',
				__( 'Failed to restore version', 'sequensy' ),
				500
			);
		}

		// Return the updated workflow with the restored config.
		$updated_workflow = $this->workflow_storage->get_workflow( $workflow_id );

		return rest_ensure_response(
			array(
				'success'          => true,
				'message'          => sprintf(
					/* translators: %d: version number */
					__( 'Version %d restored. Publish to make it live.', 'sequensy' ),
					$version->version_number
				),
				'workflow'         => $updated_workflow,
				'restored_version' => $version->version_number,
				'is_active'        => 'active' === $workflow->status,
			)
		);
	}

	/**
	 * Get workflow publishing status.
	 *
	 * Returns version state including unpublished changes flag.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function get_workflow_status( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$workflow_id = (int) $request['workflow_id'];

		// Verify workflow exists.
		$workflow = $this->workflow_storage->get_workflow( $workflow_id );
		if ( ! $workflow ) {
			return $this->rest_error(
				'not_found',
				__( 'Workflow not found', 'sequensy' ),
				404
			);
		}

		$has_unpublished_changes = $this->workflow_storage->has_unpublished_changes( $workflow_id );

		// Get published version info if exists.
		$published_version = null;
		if ( $workflow->published_version_id ) {
			$published_version = $this->version_storage->get_version( $workflow->published_version_id );
		}

		return rest_ensure_response(
			array(
				'has_unpublished_changes' => $has_unpublished_changes,
				'published_version_id'    => $workflow->published_version_id,
				'last_published_at'       => $workflow->last_published_at,
				'published_version'       => $published_version
					? array(
						'id'             => $published_version->id,
						'version_number' => $published_version->version_number,
						'created_at'     => $published_version->created_at,
						'notes'          => $published_version->notes,
					)
					: null,
				'version_count'           => $this->version_storage->get_version_count( $workflow_id ),
			)
		);
	}

	/**
	 * Make a specific version live.
	 *
	 * Sets the specified version as the currently published version and syncs draft.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function make_version_live( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$workflow_id = (int) $request['workflow_id'];
		$version_id  = (int) $request['version_id'];

		// Verify workflow exists.
		$workflow = $this->workflow_storage->get_workflow( $workflow_id );
		if ( ! $workflow ) {
			return $this->rest_error(
				'not_found',
				__( 'Workflow not found', 'sequensy' ),
				404
			);
		}

		// Verify version exists and belongs to workflow.
		$version = $this->version_storage->get_version( $version_id );
		if ( ! $version || $version->workflow_id !== $workflow_id ) {
			return $this->rest_error(
				'not_found',
				__( 'Version not found', 'sequensy' ),
				404
			);
		}

		// Parse version config for validation.
		$version_config = WorkflowConfig::from_json( $version->config );
		if ( is_wp_error( $version_config ) ) {
			return $this->rest_error(
				'invalid_config',
				__( 'Version configuration is invalid', 'sequensy' )
			);
		}

		// Create a temporary object for validation (validator expects object with draft_config property).
		$validation_target               = new \stdClass();
		$validation_target->draft_config = $version_config;

		// Validate version configuration before making it live.
		$validator  = new WorkflowValidator( $this->trigger_registry, $this->action_registry );
		$validation = $validator->validate_for_activation( $validation_target );

		if ( ! $validation['valid'] ) {
			return $this->rest_error(
				'validation_failed',
				__( 'Version cannot be made live due to configuration errors.', 'sequensy' ),
				400,
				array( 'errors' => $validation['errors'] )
			);
		}

		// Update published version pointer.
		$this->workflow_storage->set_published_version( $workflow_id, $version_id );

		// Sync draft to match live version (copies version config to draft_config).
		$this->version_storage->restore_version( $workflow_id, $version_id );

		// Add is_published flag to response.
		$version->is_published = true;

		return rest_ensure_response(
			array(
				'success' => true,
				'version' => $version,
				'message' => sprintf(
					/* translators: %d: version number */
					__( 'Version %d is now live', 'sequensy' ),
					$version->version_number
				),
			)
		);
	}
}
