<?php
/**
 * Speed module REST controller.
 *
 * @package MonitorCat\Modules\Speed
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Speed;

defined( 'ABSPATH' ) || exit;

use MonitorCat\Modules\Speed\Analyzer\AssetAnalyzer;
use MonitorCat\Modules\Speed\Analyzer\CacheAnalyzer;
use MonitorCat\Modules\Speed\Analyzer\CompressionAnalyzer;
use MonitorCat\Modules\Speed\Analyzer\DatabaseAnalyzer;
use MonitorCat\Modules\Speed\Analyzer\ImageOptAnalyzer;
use MonitorCat\Modules\Speed\Analyzer\PageWeightAnalyzer;
use MonitorCat\Modules\Speed\Analyzer\ResponseTimeAnalyzer;
use MonitorCat\Modules\Speed\Optimizer\DatabaseOptimizer;
use MonitorCat\Settings;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Handles REST API endpoints for the Speed module.
 */
final class RestController extends WP_REST_Controller {

	/**
	 * REST API namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'monitor-cat/v1';

	/**
	 * Resource base.
	 *
	 * @var string
	 */
	protected $rest_base = 'speed';

	/**
	 * Transient key for cached scan results.
	 *
	 * @var string
	 */
	private const TRANSIENT_KEY = 'monitorcat_speed_scan_results';

	/**
	 * Transient expiration in seconds (1 hour).
	 *
	 * @var int
	 */
	private const TRANSIENT_EXPIRATION = HOUR_IN_SECONDS;

	/**
	 * Allowed database cleanup types.
	 *
	 * @var string[]
	 */
	private const ALLOWED_CLEANUP_TYPES = array(
		'revisions',
		'auto_drafts',
		'trashed_posts',
		'spam_comments',
		'trashed_comments',
		'expired_transients',
		'optimize_tables',
	);

	/**
	 * Plugin settings.
	 *
	 * @var Settings
	 */
	private Settings $settings;

	/**
	 * Constructor.
	 *
	 * @param Settings $settings Plugin settings.
	 */
	public function __construct( Settings $settings ) {
		$this->settings = $settings;
	}

	/**
	 * Register REST API routes.
	 */
	public function register(): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/scan',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'handle_scan' ),
				'permission_callback' => array( $this, 'check_permission' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/results',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_results' ),
				'permission_callback' => array( $this, 'check_permission' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/db-cleanup',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'handle_db_cleanup' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'type' => array(
						'required'          => true,
						'type'              => 'string',
						'enum'              => self::ALLOWED_CLEANUP_TYPES,
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/db-stats',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_db_stats' ),
				'permission_callback' => array( $this, 'check_permission' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/settings',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'handle_get_settings' ),
					'permission_callback' => array( $this, 'check_permission' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'handle_update_settings' ),
					'permission_callback' => array( $this, 'check_permission' ),
					'args'                => $this->get_settings_args(),
				),
			)
		);
	}

	/**
	 * Check if the current user has permission to access Speed endpoints.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return bool True if the user has manage_options capability.
	 */
	public function check_permission( WP_REST_Request $request ): bool {
		return current_user_can( 'manage_options' );
	}

	/**
	 * Handle the speed scan request.
	 *
	 * Runs all analyzers, aggregates results, and caches them in a transient.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response
	 */
	public function handle_scan( WP_REST_Request $request ): WP_REST_Response {
		// Pre-fetch home page once and share across analyzers to avoid 7+ self-HTTP requests.
		$home_response = wp_remote_get(
			home_url( '/' ),
			array(
				'timeout'   => 10,
				'sslverify' => false,
			)
		);

		$home_html = is_wp_error( $home_response ) ? '' : wp_remote_retrieve_body( $home_response );

		$head_response = wp_remote_head(
			home_url( '/' ),
			array(
				'timeout'   => 5,
				'sslverify' => false,
			)
		);

		// Set up analyzers with shared responses.
		$page_weight = new PageWeightAnalyzer( $this->settings );
		$image_opt   = new ImageOptAnalyzer( $this->settings );
		$assets      = new AssetAnalyzer( $this->settings );
		$database    = new DatabaseAnalyzer();

		$cache = new CacheAnalyzer();
		$cache->set_prefetched_head_response( $head_response );

		// CompressionAnalyzer needs its own request with Accept-Encoding header
		// to test compression support, so it is not given the shared response.
		$compression = new CompressionAnalyzer();

		$response_time = new ResponseTimeAnalyzer();
		$response_time->set_prefetched_head_response( $head_response );

		$analyzers = array(
			'page_weight'   => array( $page_weight, $home_html ),
			'image_opt'     => array( $image_opt, null ),
			'assets'        => array( $assets, $home_html ),
			'database'      => array( $database, null ),
			'cache'         => array( $cache, null ),
			'compression'   => array( $compression, null ),
			'response_time' => array( $response_time, null ),
		);

		$results     = array();
		$total_score = 0;
		$count       = 0;

		foreach ( $analyzers as $key => list( $analyzer, $html_arg ) ) {
			$result          = null !== $html_arg ? $analyzer->analyze( $html_arg ) : $analyzer->analyze();
			$results[ $key ] = $result;
			$total_score    += $result['score'];
			++$count;
		}

		$overall_score  = (int) round( $total_score / $count );
		$overall_status = $this->determine_overall_status( $overall_score );

		$data = array(
			'overall_score'  => $overall_score,
			'overall_status' => $overall_status,
			'timestamp'      => gmdate( 'c' ),
			'analyzers'      => $results,
			'optimizations'  => $this->get_active_optimizations(),
		);

		set_transient( self::TRANSIENT_KEY, $data, self::TRANSIENT_EXPIRATION );

		return new WP_REST_Response(
			array(
				'success' => true,
				'data'    => $data,
			),
			200
		);
	}

	/**
	 * Handle the speed results request.
	 *
	 * Returns cached scan results from transient.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response
	 */
	public function handle_results( WP_REST_Request $request ): WP_REST_Response {
		$data = get_transient( self::TRANSIENT_KEY );

		if ( false === $data ) {
			return new WP_REST_Response(
				array(
					'success' => true,
					'data'    => null,
					'message' => __( 'No scan results available. Run a scan first.', 'monitor-cat' ),
				),
				200
			);
		}

		return new WP_REST_Response(
			array(
				'success' => true,
				'data'    => $data,
			),
			200
		);
	}

	/**
	 * Handle the database cleanup request.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response
	 */
	public function handle_db_cleanup( WP_REST_Request $request ): WP_REST_Response {
		$type = sanitize_key( (string) $request->get_param( 'type' ) );

		if ( empty( $type ) || ! in_array( $type, self::ALLOWED_CLEANUP_TYPES, true ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => sprintf(
						/* translators: %s: comma-separated list of allowed cleanup types */
						__( 'Invalid cleanup type. Allowed types: %s.', 'monitor-cat' ),
						implode( ', ', self::ALLOWED_CLEANUP_TYPES )
					),
				),
				400
			);
		}

		$optimizer = new DatabaseOptimizer();
		$method    = 'clean_' . $type;

		// Map special cases.
		if ( 'optimize_tables' === $type ) {
			$method = 'optimize_tables';
		}

		if ( ! method_exists( $optimizer, $method ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Cleanup method not found.', 'monitor-cat' ),
				),
				400
			);
		}

		$result = $optimizer->$method();

		return new WP_REST_Response(
			array(
				'success' => true,
				'data'    => $result,
			),
			200
		);
	}

	/**
	 * Handle the database stats request.
	 *
	 * Returns current database bloat counts without performing cleanup.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response
	 */
	public function handle_db_stats( WP_REST_Request $request ): WP_REST_Response {
		$analyzer = new DatabaseAnalyzer();
		$stats    = $analyzer->get_stats();

		return new WP_REST_Response(
			array(
				'success' => true,
				'data'    => $stats,
			),
			200
		);
	}

	/**
	 * Handle GET speed settings.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response
	 */
	public function handle_get_settings( WP_REST_Request $request ): WP_REST_Response {
		return new WP_REST_Response(
			array(
				'success' => true,
				'data'    => array(
					'optimize_head_cleanup'     => (bool) $this->settings->get( 'speed.optimize_head_cleanup', true ),
					'optimize_scripts'          => (bool) $this->settings->get( 'speed.optimize_scripts', true ),
					'remove_jquery_migrate'     => (bool) $this->settings->get( 'speed.remove_jquery_migrate', true ),
					'optimize_preload'          => (bool) $this->settings->get( 'speed.optimize_preload', true ),
					'optimize_lazy_media'       => (bool) $this->settings->get( 'speed.optimize_lazy_media', true ),
					'optimize_heartbeat'        => (bool) $this->settings->get( 'speed.optimize_heartbeat', true ),
					'optimize_query_strings'    => (bool) $this->settings->get( 'speed.optimize_query_strings', true ),
					'optimize_embeds'           => (bool) $this->settings->get( 'speed.optimize_embeds', true ),
					'optimize_frontend_cleanup' => (bool) $this->settings->get( 'speed.optimize_frontend_cleanup', true ),
					'db_auto_cleanup'           => (bool) $this->settings->get( 'speed.db_auto_cleanup', false ),
				),
			),
			200
		);
	}

	/**
	 * Handle POST speed settings update.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response
	 */
	public function handle_update_settings( WP_REST_Request $request ): WP_REST_Response {
		$allowed_keys = array(
			'optimize_head_cleanup',
			'optimize_scripts',
			'remove_jquery_migrate',
			'optimize_preload',
			'optimize_lazy_media',
			'optimize_heartbeat',
			'optimize_query_strings',
			'optimize_embeds',
			'optimize_frontend_cleanup',
			'db_auto_cleanup',
		);

		$updated = array();

		foreach ( $allowed_keys as $key ) {
			$value = $request->get_param( $key );
			if ( null !== $value ) {
				$bool_value = rest_sanitize_boolean( $value );
				$this->settings->set( 'speed.' . $key, $bool_value );
				$updated[ $key ] = $bool_value;
			}
		}

		return new WP_REST_Response(
			array(
				'success' => true,
				'data'    => $updated,
				'message' => __( 'Settings saved. Changes take effect on the next page load.', 'monitor-cat' ),
			),
			200
		);
	}

	/**
	 * Get list of active speed optimizations for the UI banner.
	 *
	 * @return array<int, array{key: string, label: string, icon: string, color: string, active: bool}>
	 */
	private function get_active_optimizations(): array {
		return array(
			array(
				'key'    => 'optimize_head_cleanup',
				'label'  => __( 'Head Cleanup', 'monitor-cat' ),
				'detail' => __( 'Removes emoji scripts, version tag, RSD, WLW, shortlinks', 'monitor-cat' ),
				'icon'   => 'fa-broom-wide',
				'color'  => 'primary',
				'active' => (bool) $this->settings->get( 'speed.optimize_head_cleanup', true ),
			),
			array(
				'key'    => 'optimize_scripts',
				'label'  => __( 'Script Defer', 'monitor-cat' ),
				'detail' => __( 'Adds defer to scripts, removes jQuery Migrate', 'monitor-cat' ),
				'icon'   => 'fa-bolt',
				'color'  => 'cyan',
				'active' => (bool) $this->settings->get( 'speed.optimize_scripts', true ),
			),
			array(
				'key'    => 'optimize_preload',
				'label'  => __( 'Resource Hints', 'monitor-cat' ),
				'detail' => __( 'Auto preconnect to external CDNs and font servers', 'monitor-cat' ),
				'icon'   => 'fa-link',
				'color'  => 'lavender',
				'active' => (bool) $this->settings->get( 'speed.optimize_preload', true ),
			),
			array(
				'key'    => 'optimize_lazy_media',
				'label'  => __( 'Lazy Media', 'monitor-cat' ),
				'detail' => __( 'Lazy loads iframes, async image decoding', 'monitor-cat' ),
				'icon'   => 'fa-images',
				'color'  => 'mint',
				'active' => (bool) $this->settings->get( 'speed.optimize_lazy_media', true ),
			),
			array(
				'key'    => 'optimize_heartbeat',
				'label'  => __( 'Heartbeat Control', 'monitor-cat' ),
				'detail' => __( 'Disables frontend heartbeat, slows admin heartbeat', 'monitor-cat' ),
				'icon'   => 'fa-heart-pulse',
				'color'  => 'coral',
				'active' => (bool) $this->settings->get( 'speed.optimize_heartbeat', true ),
			),
			array(
				'key'    => 'optimize_query_strings',
				'label'  => __( 'Query String Removal', 'monitor-cat' ),
				'detail' => __( 'Removes ?ver= from CSS/JS for better CDN caching', 'monitor-cat' ),
				'icon'   => 'fa-link-slash',
				'color'  => 'primary',
				'active' => (bool) $this->settings->get( 'speed.optimize_query_strings', true ),
			),
			array(
				'key'    => 'optimize_embeds',
				'label'  => __( 'Disable Embeds', 'monitor-cat' ),
				'detail' => __( 'Removes wp-embed.min.js (~10KB saved per page)', 'monitor-cat' ),
				'icon'   => 'fa-code',
				'color'  => 'lavender',
				'active' => (bool) $this->settings->get( 'speed.optimize_embeds', true ),
			),
			array(
				'key'    => 'optimize_frontend_cleanup',
				'label'  => __( 'Frontend Cleanup', 'monitor-cat' ),
				'detail' => __( 'Removes Dashicons CSS (~46KB) for logged-out visitors', 'monitor-cat' ),
				'icon'   => 'fa-scissors',
				'color'  => 'mint',
				'active' => (bool) $this->settings->get( 'speed.optimize_frontend_cleanup', true ),
			),
			array(
				'key'    => 'db_auto_cleanup',
				'label'  => __( 'Auto DB Cleanup', 'monitor-cat' ),
				'detail' => __( 'Weekly cleanup of revisions, drafts, spam, and transients', 'monitor-cat' ),
				'icon'   => 'fa-database',
				'color'  => 'cyan',
				'active' => (bool) $this->settings->get( 'speed.db_auto_cleanup', false ),
			),
		);
	}

	/**
	 * Get REST args schema for the settings update endpoint.
	 *
	 * @return array<string, array{type: string, sanitize_callback: string}>
	 */
	private function get_settings_args(): array {
		$keys = array(
			'optimize_head_cleanup',
			'optimize_scripts',
			'remove_jquery_migrate',
			'optimize_preload',
			'optimize_lazy_media',
			'optimize_heartbeat',
			'optimize_query_strings',
			'optimize_embeds',
			'optimize_frontend_cleanup',
			'db_auto_cleanup',
		);

		$args = array();
		foreach ( $keys as $key ) {
			$args[ $key ] = array(
				'type'              => 'boolean',
				'sanitize_callback' => 'rest_sanitize_boolean',
			);
		}

		return $args;
	}

	/**
	 * Determine overall status from the aggregate score.
	 *
	 * @param int $score The overall score.
	 * @return string 'good', 'warning', or 'critical'.
	 */
	private function determine_overall_status( int $score ): string {
		if ( $score >= 80 ) {
			return 'good';
		}

		if ( $score >= 50 ) {
			return 'warning';
		}

		return 'critical';
	}
}
