<?php

declare( strict_types=1 );

namespace StrykeWP\Modules\Connector;

/**
 * REST API Endpoint Registration & Read Callbacks for Hub Connector.
 *
 * Ported from StrykeHub_API (stryke-connector v1.0.0).
 * Registers all /stryke/v1/* routes and handles read-only endpoint callbacks.
 * Write/action callbacks are delegated to ApiManager (injected via constructor).
 *
 * @package StrykeWP\Modules\Connector
 */
class Api {

	/** The REST API namespace. Produces /wp-json/stryke/v1/<endpoint> */
	const NAMESPACE = 'stryke/v1';

	// -----------------------------------------------------------------------
	// Constructor
	// -----------------------------------------------------------------------

	public function __construct(
		private readonly Auth           $auth,
		private readonly ApiLogger      $logger,
		private readonly ApiManager     $manager,
		private readonly ContentManager $content,
		private readonly AutoLogin      $auto_login,
	) {}

	// -----------------------------------------------------------------------
	// Route Registration
	// -----------------------------------------------------------------------

	/**
	 * Registers all REST API routes.
	 * Hooked to `rest_api_init` by ConnectorModule.
	 *
	 * @return void
	 */
	public function register_routes(): void {

		$permission = array( $this->auth, 'validate_request' );

		// GET /stryke/v1/status
		register_rest_route(
			self::NAMESPACE,
			'/status',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_status' ),
				'permission_callback' => $permission,
				'args'                => array(
					'check_integrity' => array(
						'description'       => __( 'When true, verifies WP core file checksums against WordPress.org API. Opt-in (slower on first call).', 'stryke-wp' ),
						'type'              => 'boolean',
						'default'           => false,
						'required'          => false,
						'sanitize_callback' => 'rest_sanitize_boolean',
					),
				),
			)
		);

		// GET /stryke/v1/plugins
		register_rest_route(
			self::NAMESPACE,
			'/plugins',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_plugins' ),
				'permission_callback' => $permission,
				'args'                => array(
					'status' => array(
						'description'       => __( 'Filter plugins by status: active, inactive, network-active.', 'stryke-wp' ),
						'type'              => 'string',
						'enum'              => array( 'active', 'inactive', 'network-active' ),
						'required'          => false,
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		// GET /stryke/v1/themes
		register_rest_route(
			self::NAMESPACE,
			'/themes',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_themes' ),
				'permission_callback' => $permission,
				'args'                => array(
					'status' => array(
						'description'       => __( 'Filter themes by status: active, parent, inactive.', 'stryke-wp' ),
						'type'              => 'string',
						'enum'              => array( 'active', 'parent', 'inactive' ),
						'required'          => false,
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		// GET /stryke/v1/logs
		register_rest_route(
			self::NAMESPACE,
			'/logs',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_logs' ),
				'permission_callback' => $permission,
				'args'                => array(
					'limit' => array(
						'description'       => __( 'Number of log entries to return. Min: 1, Max: 200. Default: 50.', 'stryke-wp' ),
						'type'              => 'integer',
						'default'           => ApiLogger::DEFAULT_LIMIT,
						'minimum'           => 1,
						'maximum'           => ApiLogger::MAX_LIMIT,
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		// POST /stryke/v1/plugins/manage
		register_rest_route(
			self::NAMESPACE,
			'/plugins/manage',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->manager, 'handle_plugin_manage' ),
				'permission_callback' => $permission,
				'args'                => array(
					'plugin_file' => array(
						'description'       => __( 'Plugin file identifier as returned by the /plugins endpoint (e.g. "akismet/akismet.php").', 'stryke-wp' ),
						'type'              => 'string',
						'required'          => true,
						'validate_callback' => array( $this->manager, 'validate_plugin_file' ),
						'sanitize_callback' => array( $this->manager, 'sanitize_plugin_file' ),
					),
					'action'      => array(
						'description'       => __( 'Action to perform: activate, deactivate, or update.', 'stryke-wp' ),
						'type'              => 'string',
						'required'          => true,
						'enum'              => array( 'activate', 'deactivate', 'update' ),
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		// GET /stryke/v1/system/health
		register_rest_route(
			self::NAMESPACE,
			'/system/health',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_system_health' ),
				'permission_callback' => $permission,
			)
		);

		// POST /stryke/v1/system/maintenance
		register_rest_route(
			self::NAMESPACE,
			'/system/maintenance',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->manager, 'handle_system_maintenance' ),
				'permission_callback' => $permission,
				'args'                => array(
					'action' => array(
						'description'       => __( 'Maintenance action: flush_cache or optimize_logs.', 'stryke-wp' ),
						'type'              => 'string',
						'required'          => true,
						'enum'              => array( 'flush_cache', 'optimize_logs' ),
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		// POST /stryke/v1/backup/trigger
		register_rest_route(
			self::NAMESPACE,
			'/backup/trigger',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'handle_backup_trigger' ),
				'permission_callback' => $permission,
			)
		);

		// GET /stryke/v1/backup/status
		register_rest_route(
			self::NAMESPACE,
			'/backup/status',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_backup_status' ),
				'permission_callback' => $permission,
			)
		);

		// ===================================================================
		// Content Management Endpoints (18)
		// ===================================================================

		// GET /stryke/v1/posts
		register_rest_route(
			self::NAMESPACE,
			'/posts',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_posts_list' ),
				'permission_callback' => $permission,
				'args'                => array(
					'post_type' => array(
						'description'       => __( 'Filter by post type: post, page, or any.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'post',
						'sanitize_callback' => 'sanitize_key',
					),
					'status'    => array(
						'description'       => __( 'Filter by status: publish, draft, pending, private, trash, future, any.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'any',
						'sanitize_callback' => 'sanitize_key',
					),
					'per_page'  => array(
						'description'       => __( 'Results per page (1-100). Default: 20.', 'stryke-wp' ),
						'type'              => 'integer',
						'default'           => 20,
						'minimum'           => 1,
						'maximum'           => 100,
						'sanitize_callback' => 'absint',
					),
					'page'      => array(
						'description'       => __( 'Page number. Default: 1.', 'stryke-wp' ),
						'type'              => 'integer',
						'default'           => 1,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
					),
					'search'    => array(
						'description'       => __( 'Search keyword.', 'stryke-wp' ),
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'orderby'   => array(
						'description'       => __( 'Sort by: date, title, modified, ID.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'date',
						'sanitize_callback' => 'sanitize_key',
					),
					'order'     => array(
						'description'       => __( 'Sort order: ASC or DESC.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'DESC',
						'enum'              => array( 'ASC', 'DESC' ),
					),
					'category'  => array(
						'description'       => __( 'Filter by category ID.', 'stryke-wp' ),
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'tag'       => array(
						'description'       => __( 'Filter by tag slug.', 'stryke-wp' ),
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'author'    => array(
						'description'       => __( 'Filter by author ID.', 'stryke-wp' ),
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		// GET /stryke/v1/posts/{id}
		register_rest_route(
			self::NAMESPACE,
			'/posts/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_post_get' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id' => array(
						'validate_callback' => array( $this->content, 'validate_post_id' ),
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		// POST /stryke/v1/posts
		register_rest_route(
			self::NAMESPACE,
			'/posts',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->content, 'handle_post_create' ),
				'permission_callback' => $permission,
				'args'                => array(
					'title'          => array(
						'description' => __( 'Post title.', 'stryke-wp' ),
						'type'        => 'string',
						'required'    => true,
					),
					'content'        => array(
						'description' => __( 'Post content (HTML).', 'stryke-wp' ),
						'type'        => 'string',
					),
					'excerpt'        => array(
						'description' => __( 'Post excerpt.', 'stryke-wp' ),
						'type'        => 'string',
					),
					'status'         => array(
						'description' => __( 'Post status. Default: draft.', 'stryke-wp' ),
						'type'        => 'string',
						'default'     => 'draft',
					),
					'post_type'      => array(
						'description' => __( 'Post type: post or page.', 'stryke-wp' ),
						'type'        => 'string',
						'default'     => 'post',
					),
					'categories'     => array(
						'description' => __( 'Array of category IDs.', 'stryke-wp' ),
						'type'        => 'array',
						'items'       => array( 'type' => 'integer' ),
					),
					'tags'           => array(
						'description' => __( 'Array of tag names.', 'stryke-wp' ),
						'type'        => 'array',
						'items'       => array( 'type' => 'string' ),
					),
					'featured_image' => array(
						'description' => __( 'Attachment ID for featured image.', 'stryke-wp' ),
						'type'        => 'integer',
					),
					'meta'           => array(
						'description' => __( 'Key-value pairs of custom meta fields.', 'stryke-wp' ),
						'type'        => 'object',
					),
				),
			)
		);

		// PUT /stryke/v1/posts/{id}
		register_rest_route(
			self::NAMESPACE,
			'/posts/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this->content, 'handle_post_update' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id' => array(
						'validate_callback' => array( $this->content, 'validate_post_id' ),
						'sanitize_callback' => 'absint',
					),
					'title'          => array( 'type' => 'string' ),
					'content'        => array( 'type' => 'string' ),
					'excerpt'        => array( 'type' => 'string' ),
					'status'         => array( 'type' => 'string' ),
					'categories'     => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ) ),
					'tags'           => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ),
					'featured_image' => array( 'type' => 'integer' ),
					'meta'           => array( 'type' => 'object' ),
				),
			)
		);

		// DELETE /stryke/v1/posts/{id}
		register_rest_route(
			self::NAMESPACE,
			'/posts/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this->content, 'handle_post_delete' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id'    => array(
						'validate_callback' => array( $this->content, 'validate_post_id' ),
						'sanitize_callback' => 'absint',
					),
					'force' => array(
						'description'       => __( 'True to permanently delete instead of trashing.', 'stryke-wp' ),
						'type'              => 'boolean',
						'default'           => false,
						'sanitize_callback' => 'rest_sanitize_boolean',
					),
				),
			)
		);

		// GET /stryke/v1/posts/{id}/revisions
		register_rest_route(
			self::NAMESPACE,
			'/posts/(?P<id>\d+)/revisions',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_post_revisions' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id' => array(
						'validate_callback' => array( $this->content, 'validate_post_id' ),
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		// --- Media ---

		// GET /stryke/v1/media
		register_rest_route(
			self::NAMESPACE,
			'/media',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_media_list' ),
				'permission_callback' => $permission,
				'args'                => array(
					'per_page'  => array(
						'type'              => 'integer',
						'default'           => 20,
						'minimum'           => 1,
						'maximum'           => 100,
						'sanitize_callback' => 'absint',
					),
					'page'      => array(
						'type'              => 'integer',
						'default'           => 1,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
					),
					'search'    => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'mime_type' => array(
						'description'       => __( 'Filter by MIME type (e.g. image/jpeg).', 'stryke-wp' ),
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_mime_type',
					),
				),
			)
		);

		// POST /stryke/v1/media
		register_rest_route(
			self::NAMESPACE,
			'/media',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->content, 'handle_media_upload' ),
				'permission_callback' => $permission,
				'args'                => array(
					'file_data' => array(
						'description' => __( 'Base64-encoded file data.', 'stryke-wp' ),
						'type'        => 'string',
						'required'    => true,
					),
					'filename'  => array(
						'description' => __( 'Target filename including extension.', 'stryke-wp' ),
						'type'        => 'string',
						'required'    => true,
					),
					'mime_type' => array(
						'description' => __( 'MIME type of the file.', 'stryke-wp' ),
						'type'        => 'string',
					),
				),
			)
		);

		// DELETE /stryke/v1/media/{id}
		register_rest_route(
			self::NAMESPACE,
			'/media/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this->content, 'handle_media_delete' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id' => array(
						'validate_callback' => array( $this->content, 'validate_media_id' ),
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		// --- Terms / Taxonomies ---

		// GET /stryke/v1/terms
		register_rest_route(
			self::NAMESPACE,
			'/terms',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_terms_list' ),
				'permission_callback' => $permission,
				'args'                => array(
					'taxonomy'   => array(
						'description'       => __( 'Taxonomy: category, post_tag, or any public taxonomy.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'category',
						'sanitize_callback' => 'sanitize_key',
					),
					'search'     => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'parent'     => array(
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'hide_empty' => array(
						'type'              => 'boolean',
						'default'           => false,
						'sanitize_callback' => 'rest_sanitize_boolean',
					),
					'per_page'   => array(
						'type'              => 'integer',
						'default'           => 50,
						'minimum'           => 1,
						'maximum'           => 100,
						'sanitize_callback' => 'absint',
					),
					'offset'     => array(
						'type'              => 'integer',
						'default'           => 0,
						'sanitize_callback' => 'absint',
					),
				),
			)
		);

		// POST /stryke/v1/terms
		register_rest_route(
			self::NAMESPACE,
			'/terms',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->content, 'handle_term_create' ),
				'permission_callback' => $permission,
				'args'                => array(
					'taxonomy'    => array(
						'type'              => 'string',
						'default'           => 'category',
						'sanitize_callback' => 'sanitize_key',
					),
					'name'        => array(
						'description' => __( 'Term name.', 'stryke-wp' ),
						'type'        => 'string',
						'required'    => true,
					),
					'slug'        => array(
						'type'        => 'string',
					),
					'parent'      => array(
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'description' => array(
						'type'        => 'string',
					),
				),
			)
		);

		// PUT /stryke/v1/terms/{id}
		register_rest_route(
			self::NAMESPACE,
			'/terms/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this->content, 'handle_term_update' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id'          => array(
						'validate_callback' => array( $this->content, 'validate_term_id' ),
						'sanitize_callback' => 'absint',
					),
					'taxonomy'    => array(
						'type'              => 'string',
						'default'           => 'category',
						'sanitize_callback' => 'sanitize_key',
					),
					'name'        => array( 'type' => 'string' ),
					'slug'        => array( 'type' => 'string' ),
					'parent'      => array( 'type' => 'integer', 'sanitize_callback' => 'absint' ),
					'description' => array( 'type' => 'string' ),
				),
			)
		);

		// DELETE /stryke/v1/terms/{id}
		register_rest_route(
			self::NAMESPACE,
			'/terms/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this->content, 'handle_term_delete' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id'       => array(
						'validate_callback' => array( $this->content, 'validate_term_id' ),
						'sanitize_callback' => 'absint',
					),
					'taxonomy' => array(
						'type'              => 'string',
						'default'           => 'category',
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		// --- Comments ---

		// GET /stryke/v1/comments
		register_rest_route(
			self::NAMESPACE,
			'/comments',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_comments_list' ),
				'permission_callback' => $permission,
				'args'                => array(
					'per_page' => array(
						'type'              => 'integer',
						'default'           => 20,
						'minimum'           => 1,
						'maximum'           => 100,
						'sanitize_callback' => 'absint',
					),
					'page'     => array(
						'type'              => 'integer',
						'default'           => 1,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
					),
					'status'   => array(
						'description'       => __( 'Filter by status: approve, hold, spam, trash, all.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'all',
						'sanitize_callback' => 'sanitize_key',
					),
					'post_id'  => array(
						'description'       => __( 'Filter by post ID.', 'stryke-wp' ),
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
					'search'   => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		// POST /stryke/v1/comments/{id}/moderate
		register_rest_route(
			self::NAMESPACE,
			'/comments/(?P<id>\d+)/moderate',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->content, 'handle_comment_moderate' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id'     => array(
						'validate_callback' => array( $this->content, 'validate_comment_id' ),
						'sanitize_callback' => 'absint',
					),
					'action' => array(
						'description'       => __( 'Moderation action: approve, spam, or trash.', 'stryke-wp' ),
						'type'              => 'string',
						'required'          => true,
						'enum'              => array( 'approve', 'spam', 'trash' ),
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		// DELETE /stryke/v1/comments/{id}
		register_rest_route(
			self::NAMESPACE,
			'/comments/(?P<id>\d+)',
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this->content, 'handle_comment_delete' ),
				'permission_callback' => $permission,
				'args'                => array(
					'id'    => array(
						'validate_callback' => array( $this->content, 'validate_comment_id' ),
						'sanitize_callback' => 'absint',
					),
					'force' => array(
						'description'       => __( 'True to permanently delete instead of trashing.', 'stryke-wp' ),
						'type'              => 'boolean',
						'default'           => false,
						'sanitize_callback' => 'rest_sanitize_boolean',
					),
				),
			)
		);

		// --- Users (read-only) ---

		// GET /stryke/v1/users
		register_rest_route(
			self::NAMESPACE,
			'/users',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_users_list' ),
				'permission_callback' => $permission,
				'args'                => array(
					'per_page' => array(
						'type'              => 'integer',
						'default'           => 20,
						'minimum'           => 1,
						'maximum'           => 100,
						'sanitize_callback' => 'absint',
					),
					'page'     => array(
						'type'              => 'integer',
						'default'           => 1,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
					),
					'role'     => array(
						'description'       => __( 'Filter by user role.', 'stryke-wp' ),
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_key',
					),
					'search'   => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'orderby'  => array(
						'type'              => 'string',
						'default'           => 'registered',
						'sanitize_callback' => 'sanitize_key',
					),
					'order'    => array(
						'type'    => 'string',
						'default' => 'DESC',
						'enum'    => array( 'ASC', 'DESC' ),
					),
				),
			)
		);

		// --- Settings ---

		// GET /stryke/v1/settings
		register_rest_route(
			self::NAMESPACE,
			'/settings',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this->content, 'handle_settings_get' ),
				'permission_callback' => $permission,
			)
		);

		// PUT /stryke/v1/settings
		register_rest_route(
			self::NAMESPACE,
			'/settings',
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this->content, 'handle_settings_update' ),
				'permission_callback' => $permission,
				'args'                => array(
					'settings' => array(
						'description' => __( 'Object of key/value pairs. Only whitelisted options are accepted.', 'stryke-wp' ),
						'type'        => 'object',
						'required'    => true,
					),
				),
			)
		);

		// ===================================================================
		// Auto-Login (Elementor One-Click-Edit)
		// ===================================================================

		// POST /stryke/v1/auto-login
		register_rest_route(
			self::NAMESPACE,
			'/auto-login',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this->auto_login, 'handle_create_token' ),
				'permission_callback' => $permission,
				'args'                => array(
					'redirect_to' => array(
						'description'       => __( 'Internal path to redirect to after login (e.g. /wp-admin/post.php?post=123&action=elementor).', 'stryke-wp' ),
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
					),
					'user_role'   => array(
						'description'       => __( 'WordPress role of the user to log in as. Default: administrator.', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'administrator',
						'enum'              => array( 'administrator', 'editor' ),
						'sanitize_callback' => 'sanitize_key',
					),
					'context'     => array(
						'description'       => __( 'Context hint for audit logging (e.g. elementor_edit).', 'stryke-wp' ),
						'type'              => 'string',
						'default'           => 'generic',
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);
	}

	// -----------------------------------------------------------------------
	// Endpoint Callbacks — Read
	// -----------------------------------------------------------------------

	/**
	 * GET /stryke/v1/status
	 *
	 * @param \WP_REST_Request $request Incoming REST request.
	 * @return \WP_REST_Response
	 */
	public function handle_status( \WP_REST_Request $request ): \WP_REST_Response {

		if ( ! function_exists( 'wp_get_update_data' ) ) {
			require_once ABSPATH . 'wp-admin/includes/update.php';
		}

		$update_data     = wp_get_update_data();
		$counts          = $update_data['counts'] ?? array();
		$check_integrity = (bool) $request->get_param( 'check_integrity' );

		$body = array(
			'site_info'   => array(
				'name'         => get_bloginfo( 'name' ),
				'description'  => get_bloginfo( 'description' ),
				'url'          => get_site_url(),
				'admin_email'  => $this->mask_email( (string) get_option( 'admin_email' ) ),
				'language'     => get_locale(),
				'timezone'     => wp_timezone_string(),
				'is_multisite' => is_multisite(),
			),
			'environment' => array(
				'wp_version'       => get_bloginfo( 'version' ),
				'php_version'      => PHP_VERSION,
				'mysql_version'    => $this->get_mysql_version(),
				'server_software'  => $this->get_server_software(),
				'stryke_wp_version' => STRYKE_WP_VERSION,
				'is_debug'         => defined( 'WP_DEBUG' ) && WP_DEBUG,
				'wp_memory_limit'  => WP_MEMORY_LIMIT,
			),
			'health'      => array(
				'updates_total'   => absint( $counts['total']     ?? 0 ),
				'plugins_updates' => absint( $counts['plugins']   ?? 0 ),
				'themes_updates'  => absint( $counts['themes']    ?? 0 ),
				'wp_core_update'  => absint( $counts['wordpress'] ?? 0 ) > 0,
			),
			'core_update'  => $this->get_core_update_info( $check_integrity ),
			'generated_at' => current_time( 'c' ),
		);

		return new \WP_REST_Response( $body, 200 );
	}

	/**
	 * GET /stryke/v1/plugins
	 *
	 * @param \WP_REST_Request $request Incoming REST request.
	 * @return \WP_REST_Response
	 */
	public function handle_plugins( \WP_REST_Request $request ): \WP_REST_Response {

		$this->require_plugin_functions();

		$update_transient  = get_site_transient( 'update_plugins' );
		$updates_available = isset( $update_transient->response )
			? (array) $update_transient->response
			: array();

		$all_plugins   = get_plugins();
		$filter_status = $request->get_param( 'status' );
		$inventory     = array();

		foreach ( $all_plugins as $plugin_file => $plugin_data ) {
			$mapped = $this->map_plugin_data( $plugin_file, $plugin_data, $updates_available );

			if ( ! empty( $filter_status ) && $mapped['status'] !== $filter_status ) {
				continue;
			}

			$inventory[] = $mapped;
		}

		usort( $inventory, static fn( $a, $b ) => strcmp( $a['name'], $b['name'] ) );

		$body = array(
			'plugins'      => $inventory,
			'summary'      => array(
				'total'          => count( $all_plugins ),
				'active'         => count( array_filter( $inventory, static fn( $p ) => 'active' === $p['status'] ) ),
				'inactive'       => count( array_filter( $inventory, static fn( $p ) => 'inactive' === $p['status'] ) ),
				'network_active' => count( array_filter( $inventory, static fn( $p ) => 'network-active' === $p['status'] ) ),
				'updates'        => count( array_filter( $inventory, static fn( $p ) => $p['update_available'] ) ),
			),
			'generated_at' => current_time( 'c' ),
		);

		return new \WP_REST_Response( $body, 200 );
	}

	/**
	 * GET /stryke/v1/themes
	 *
	 * @param \WP_REST_Request $request Incoming REST request.
	 * @return \WP_REST_Response
	 */
	public function handle_themes( \WP_REST_Request $request ): \WP_REST_Response {

		$update_transient  = get_site_transient( 'update_themes' );
		$updates_available = isset( $update_transient->response )
			? (array) $update_transient->response
			: array();

		$active_stylesheet = get_option( 'stylesheet', '' );
		$active_template   = get_option( 'template', '' );
		$all_themes        = wp_get_themes( array( 'errors' => false ) );
		$filter_status     = $request->get_param( 'status' );
		$inventory         = array();

		foreach ( $all_themes as $theme_slug => $theme ) {
			$mapped = $this->map_theme_data( $theme_slug, $theme, $updates_available, $active_stylesheet, $active_template );

			if ( ! empty( $filter_status ) && $mapped['status'] !== $filter_status ) {
				continue;
			}

			$inventory[] = $mapped;
		}

		usort( $inventory, static fn( $a, $b ) => strcmp( $a['name'], $b['name'] ) );

		$body = array(
			'themes'       => $inventory,
			'summary'      => array(
				'total'    => count( $all_themes ),
				'active'   => count( array_filter( $inventory, static fn( $t ) => 'active' === $t['status'] ) ),
				'parent'   => count( array_filter( $inventory, static fn( $t ) => 'parent' === $t['status'] ) ),
				'inactive' => count( array_filter( $inventory, static fn( $t ) => 'inactive' === $t['status'] ) ),
				'updates'  => count( array_filter( $inventory, static fn( $t ) => $t['update_available'] ) ),
			),
			'generated_at' => current_time( 'c' ),
		);

		return new \WP_REST_Response( $body, 200 );
	}

	/**
	 * GET /stryke/v1/logs
	 *
	 * @param \WP_REST_Request $request Incoming REST request.
	 * @return \WP_REST_Response
	 */
	public function handle_logs( \WP_REST_Request $request ): \WP_REST_Response {

		$limit = absint( $request->get_param( 'limit' ) ?: ApiLogger::DEFAULT_LIMIT );
		$logs  = $this->logger->get_recent_logs( $limit );

		$body = array(
			'logs'            => $logs,
			'count'           => count( $logs ),
			'limit_requested' => $limit,
			'retention_days'  => ApiLogger::RETENTION_DAYS,
			'generated_at'    => current_time( 'c' ),
		);

		return new \WP_REST_Response( $body, 200 );
	}

	/**
	 * GET /stryke/v1/system/health
	 *
	 * @param \WP_REST_Request $request Incoming REST request (unused).
	 * @return \WP_REST_Response
	 */
	public function handle_system_health( \WP_REST_Request $request ): \WP_REST_Response {

		$body = array(
			'php'          => $this->get_php_stats(),
			'database'     => $this->get_db_stats(),
			'security'     => $this->get_wp_security_status(),
			'filesystem'   => $this->get_filesystem_status(),
			'generated_at' => current_time( 'c' ),
		);

		return new \WP_REST_Response( $body, 200 );
	}

	// -----------------------------------------------------------------------
	// Endpoint Callbacks — Backup
	// -----------------------------------------------------------------------

	/**
	 * POST /stryke/v1/backup/trigger
	 *
	 * Schedules an asynchronous local backup via WP-Cron.
	 * The Hub calls this endpoint on its own schedule (e.g. every 2 weeks).
	 *
	 * @param \WP_REST_Request $request Incoming REST request (unused).
	 * @return \WP_REST_Response
	 */
	public function handle_backup_trigger( \WP_REST_Request $request ): \WP_REST_Response {

		$cron_hook = \StrykeWP\Modules\Backup\BackupSettings::CRON_HOOK;

		// Check if a backup is already pending or running.
		$existing = wp_next_scheduled( $cron_hook, array( 'single' => true ) );
		if ( $existing ) {
			return new \WP_REST_Response(
				array(
					'success' => true,
					'status'  => 'already_pending',
					'message' => __( 'A backup is already scheduled or running.', 'stryke-wp' ),
				),
				202
			);
		}

		wp_schedule_single_event( time(), $cron_hook, array( 'single' => true ) );
		spawn_cron();

		return new \WP_REST_Response(
			array(
				'success' => true,
				'status'  => 'triggered',
				'message' => __( 'Backup has been scheduled and will run shortly.', 'stryke-wp' ),
			),
			202
		);
	}

	/**
	 * GET /stryke/v1/backup/status
	 *
	 * Returns the most recent backup log entry.
	 *
	 * @param \WP_REST_Request $request Incoming REST request (unused).
	 * @return \WP_REST_Response
	 */
	public function handle_backup_status( \WP_REST_Request $request ): \WP_REST_Response {
		global $wpdb;

		$table = \StrykeWP\Modules\Backup\BackupSettings::get_table_name();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$latest = $wpdb->get_row(
			"SELECT ID, timestamp, filename, filesize, status, google_drive_file_id FROM {$table} ORDER BY ID DESC LIMIT 1",
			ARRAY_A
		);

		if ( ! $latest ) {
			return new \WP_REST_Response(
				array(
					'success'      => true,
					'has_backups'  => false,
					'message'      => __( 'No backups found.', 'stryke-wp' ),
					'generated_at' => current_time( 'c' ),
				),
				200
			);
		}

		return new \WP_REST_Response(
			array(
				'success'      => true,
				'has_backups'  => true,
				'latest'       => array(
					'id'         => (int) $latest['ID'],
					'timestamp'  => sanitize_text_field( $latest['timestamp'] ),
					'filename'   => sanitize_text_field( $latest['filename'] ),
					'filesize'   => (int) $latest['filesize'],
					'status'     => sanitize_text_field( $latest['status'] ),
					'has_gdrive' => ! empty( $latest['google_drive_file_id'] ),
				),
				'generated_at' => current_time( 'c' ),
			),
			200
		);
	}

	// -----------------------------------------------------------------------
	// Health Sub-collectors
	// -----------------------------------------------------------------------

	/** @return array<string, mixed> */
	private function get_php_stats(): array {

		$memory_limit_raw   = ini_get( 'memory_limit' );
		$memory_limit_bytes = wp_convert_hr_to_bytes( $memory_limit_raw );
		$post_max_raw       = ini_get( 'post_max_size' );
		$post_max_bytes     = wp_convert_hr_to_bytes( $post_max_raw );

		return array(
			'version'             => PHP_VERSION,
			'memory_limit'        => $memory_limit_raw,
			'memory_limit_bytes'  => $memory_limit_bytes,
			'memory_limit_human'  => ( -1 === $memory_limit_bytes ) ? __( 'Unlimited', 'stryke-wp' ) : size_format( $memory_limit_bytes ),
			'max_execution_time'  => (int) ini_get( 'max_execution_time' ),
			'post_max_size'       => $post_max_raw,
			'post_max_size_bytes' => $post_max_bytes,
			'post_max_size_human' => size_format( $post_max_bytes ),
			'extensions'          => array(
				'curl'     => extension_loaded( 'curl' ),
				'openssl'  => extension_loaded( 'openssl' ),
				'imagick'  => extension_loaded( 'imagick' ),
				'mbstring' => extension_loaded( 'mbstring' ),
				'json'     => extension_loaded( 'json' ),
				'mysqli'   => extension_loaded( 'mysqli' ),
				'zip'      => extension_loaded( 'zip' ),
			),
		);
	}

	/** @return array<string, mixed> */
	private function get_db_stats(): array {
		global $wpdb;

		$table_name = ApiLogger::get_table_name();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$table_info = $wpdb->get_row(
			$wpdb->prepare(
				'SELECT ( data_length + index_length ) AS size_bytes, table_rows, table_collation
				FROM information_schema.TABLES
				WHERE table_schema = DATABASE() AND table_name = %s',
				$table_name
			)
		);

		$size_bytes = (int) ( $table_info->size_bytes ?? 0 );

		return array(
			'server_version' => $wpdb->db_version(),
			'charset'        => $wpdb->charset,
			'collation'      => $wpdb->collate,
			'log_table'      => array(
				'name'       => $table_name,
				'row_count'  => (int) ( $table_info->table_rows ?? 0 ),
				'size_bytes' => $size_bytes,
				'size_human' => $size_bytes > 0 ? size_format( $size_bytes ) : '0 B',
				'collation'  => $table_info->table_collation ?? 'unknown',
				'exists'     => null !== $table_info,
			),
		);
	}

	/** @return array<string, bool> */
	private function get_wp_security_status(): array {
		return array(
			'disallow_file_edit' => defined( 'DISALLOW_FILE_EDIT' ) && (bool) DISALLOW_FILE_EDIT,
			'disallow_file_mods' => defined( 'DISALLOW_FILE_MODS' ) && (bool) DISALLOW_FILE_MODS,
			'force_ssl_admin'    => defined( 'FORCE_SSL_ADMIN' )    && (bool) FORCE_SSL_ADMIN,
			'wp_debug'           => defined( 'WP_DEBUG' )           && (bool) WP_DEBUG,
			'wp_debug_log'       => defined( 'WP_DEBUG_LOG' )       && (bool) WP_DEBUG_LOG,
			'wp_debug_display'   => defined( 'WP_DEBUG_DISPLAY' )   && (bool) WP_DEBUG_DISPLAY,
		);
	}

	/** @return array<int, array<string, mixed>> */
	private function get_filesystem_status(): array {
		$upload_dir = wp_upload_dir( null, false );
		return array(
			array( 'name' => 'wp-content', 'writable' => is_writable( WP_CONTENT_DIR ) ),
			array( 'name' => 'plugins',    'writable' => is_writable( WP_PLUGIN_DIR ) ),
			array( 'name' => 'uploads',    'writable' => is_writable( $upload_dir['basedir'] ) ),
		);
	}

	// -----------------------------------------------------------------------
	// Core Update & Integrity Collectors
	// -----------------------------------------------------------------------

	/** @return array<string, mixed> */
	private function get_core_update_info( bool $check_integrity = false ): array {

		$current_version   = get_bloginfo( 'version' );
		$locale            = get_locale();
		$update_core       = get_site_transient( 'update_core' );
		$available_version = null;
		$update_type       = 'unknown';

		if ( isset( $update_core->updates ) && is_array( $update_core->updates ) ) {
			foreach ( $update_core->updates as $core_update ) {
				if ( 'upgrade' === ( $core_update->response ?? '' ) ) {
					$available_version = sanitize_text_field( $core_update->version ?? '' );
					$update_type       = 'upgrade';
					break;
				}
			}
			if ( null === $available_version ) {
				$update_type = 'latest';
			}
		}

		$result = array(
			'current_version'   => $current_version,
			'available_version' => $available_version,
			'update_type'       => $update_type,
			'update_available'  => 'upgrade' === $update_type,
			'locale'            => $locale,
		);

		if ( $check_integrity ) {
			$result['integrity'] = $this->check_core_integrity( $current_version, $locale );
		}

		return $result;
	}

	/** @return array<string, mixed> */
	private function check_core_integrity( string $version, string $locale ): array {

		if ( ! function_exists( 'get_core_checksums' ) ) {
			require_once ABSPATH . 'wp-admin/includes/update.php';
		}

		$checksums = get_core_checksums( $version, $locale );

		if ( ! is_array( $checksums ) || empty( $checksums ) ) {
			return array(
				'status'        => 'unavailable',
				'message'       => __( 'Could not retrieve checksums from WordPress.org. Check server internet connectivity.', 'stryke-wp' ),
				'is_dirty'      => null,
				'modified'      => array(),
				'files_checked' => 0,
				'checked_at'    => current_time( 'c' ),
			);
		}

		$modified      = array();
		$files_checked = 0;

		foreach ( $checksums as $relative_file => $expected_hash ) {
			$absolute_path = ABSPATH . $relative_file;

			if ( ! file_exists( $absolute_path ) || ! is_file( $absolute_path ) ) {
				continue;
			}

			++$files_checked;
			$actual_hash = md5_file( $absolute_path );

			if ( false === $actual_hash || $actual_hash !== $expected_hash ) {
				$modified[] = sanitize_text_field( $relative_file );
			}
		}

		$is_dirty = ! empty( $modified );

		return array(
			'status'        => $is_dirty ? 'modified' : 'clean',
			'is_dirty'      => $is_dirty,
			'modified'      => $modified,
			'files_checked' => $files_checked,
			'checked_at'    => current_time( 'c' ),
		);
	}

	// -----------------------------------------------------------------------
	// Data Mappers
	// -----------------------------------------------------------------------

	/** @return array<string, mixed> */
	private function map_plugin_data( string $plugin_file, array $plugin_data, array $updates_available ): array {

		$status = 'inactive';
		if ( is_multisite() && is_plugin_active_for_network( $plugin_file ) ) {
			$status = 'network-active';
		} elseif ( is_plugin_active( $plugin_file ) ) {
			$status = 'active';
		}

		$has_update  = array_key_exists( $plugin_file, $updates_available );
		$new_version = $has_update
			? sanitize_text_field( $updates_available[ $plugin_file ]->new_version ?? '' )
			: null;

		return array(
			'plugin_file'      => sanitize_text_field( $plugin_file ),
			'name'             => sanitize_text_field( $plugin_data['Name']       ?? '' ),
			'version'          => sanitize_text_field( $plugin_data['Version']    ?? '' ),
			'author'           => wp_strip_all_tags( $plugin_data['Author']       ?? '' ),
			'author_uri'       => esc_url_raw( $plugin_data['AuthorURI']          ?? '' ),
			'plugin_uri'       => esc_url_raw( $plugin_data['PluginURI']          ?? '' ),
			'text_domain'      => sanitize_key( $plugin_data['TextDomain']        ?? '' ),
			'requires_php'     => sanitize_text_field( $plugin_data['RequiresPHP'] ?? '' ),
			'requires_wp'      => sanitize_text_field( $plugin_data['RequiresWP']  ?? '' ),
			'status'           => $status,
			'update_available' => $has_update,
			'new_version'      => $new_version,
		);
	}

	/** @return array<string, mixed> */
	private function map_theme_data(
		string    $theme_slug,
		\WP_Theme $theme,
		array     $updates_available,
		string    $active_stylesheet,
		string    $active_template
	): array {

		if ( $theme_slug === $active_stylesheet ) {
			$status = 'active';
		} elseif ( $theme_slug === $active_template && $active_stylesheet !== $active_template ) {
			$status = 'parent';
		} else {
			$status = 'inactive';
		}

		$has_update  = array_key_exists( $theme_slug, $updates_available );
		$new_version = $has_update
			? sanitize_text_field( $updates_available[ $theme_slug ]['new_version'] ?? '' )
			: null;

		$parent_slug = $theme->get( 'Template' );

		return array(
			'stylesheet'       => sanitize_key( $theme_slug ),
			'name'             => sanitize_text_field( (string) $theme->get( 'Name' )     ),
			'version'          => sanitize_text_field( (string) $theme->get( 'Version' )  ),
			'author'           => wp_strip_all_tags( (string) $theme->get( 'Author' )     ),
			'author_uri'       => esc_url_raw( (string) $theme->get( 'AuthorURI' )        ),
			'theme_uri'        => esc_url_raw( (string) $theme->get( 'ThemeURI' )         ),
			'requires_php'     => sanitize_text_field( (string) $theme->get( 'RequiresPHP' ) ),
			'requires_wp'      => sanitize_text_field( (string) $theme->get( 'RequiresWP' )  ),
			'is_child_theme'   => ! empty( $parent_slug ),
			'parent_theme'     => ! empty( $parent_slug ) ? sanitize_key( $parent_slug ) : null,
			'status'           => $status,
			'update_available' => $has_update,
			'new_version'      => $new_version,
		);
	}

	// -----------------------------------------------------------------------
	// Private Helpers
	// -----------------------------------------------------------------------

	/** @return void */
	private function require_plugin_functions(): void {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
	}

	/** @return string */
	private function get_mysql_version(): string {
		global $wpdb;
		$version = $wpdb->db_version();
		return ! empty( $version ) ? $version : 'Unknown';
	}

	/** @return string */
	private function get_server_software(): string {
		if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
			return sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
		}
		return 'Unknown';
	}

	/**
	 * Masks an email address for safe exposure via API.
	 * "admin@example.com" → "a****@example.com"
	 */
	private function mask_email( string $email ): string {
		$parts = explode( '@', $email, 2 );
		if ( 2 !== count( $parts ) ) {
			return '***';
		}
		$local  = $parts[0];
		$domain = $parts[1];
		$masked = substr( $local, 0, 1 ) . str_repeat( '*', max( 1, strlen( $local ) - 1 ) );
		return $masked . '@' . $domain;
	}
}
