<?php
/**
 * Main Plugin Class — Singleton Orchestrator
 *
 * Bootstraps every subsystem in the correct order.
 * Nothing runs until this class is instantiated.
 *
 * @package GuardianShield
 */

namespace GuardianShield;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class Plugin {

	/** @var self|null Singleton instance */
	private static ?self $instance = null;

	// Sub-system references
	public Firewall  $firewall;
	public Scanner   $scanner;
	public Logger    $logger;
	public LoginSecurity $login_security;
	public RateLimiter   $rate_limiter;
	public FileMonitor   $file_monitor;
	public XmlRpcGuard  $xmlrpc_guard;
	public SecurityHeaders $security_headers;
	public Mailer        $mailer;
	public RestApiGuard  $rest_guard;
	public BotDetector   $bot_detector;

	// ── Singleton access ────────────────────────
	public static function get_instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/** Private constructor — use get_instance(). */
	private function __construct() {
		$this->boot();
	}

	// ── System Boot ─────────────────────────────
	private function boot(): void {
		// 1. Ensure log/quarantine dirs exist.
		$this->ensure_directories();

		// 2. Load shared helpers first.
		require_once GS_INCLUDES_DIR . 'functions.php';

		// 3. Instantiate subsystems (order matters).
		$this->logger           = new Logger();
		$this->rate_limiter     = new RateLimiter( $this->logger );
		$this->bot_detector     = new BotDetector( $this->logger );
		$this->firewall         = new Firewall( $this->logger, $this->rate_limiter, $this->bot_detector );
		$this->login_security   = new LoginSecurity( $this->logger );
		$this->xmlrpc_guard     = new XmlRpcGuard( $this->logger, $this->rate_limiter );
		$this->security_headers = new SecurityHeaders();
		$this->scanner          = new Scanner( $this->logger );
		$this->file_monitor     = new FileMonitor( $this->logger );
		$this->mailer           = new Mailer();
		$this->rest_guard       = new RestApiGuard( $this->logger, $this->rate_limiter );

		// 4. Wire up hooks for each subsystem.
		$this->register_hooks();

		// 5. Admin UI (only in wp-admin context).
		if ( is_admin() ) {
			new Admin\Dashboard( $this );
		}
	}

	// ── Hook Registration ────────────────────────
	private function register_hooks(): void {
		// Firewall — runs on 'init' with priority 1 to beat everything else.
		add_action( 'init', [ $this->firewall, 'run' ], 1 );

		// Security headers — send before any output.
		add_action( 'send_headers', [ $this->security_headers, 'send' ] );

		// Login security hooks.
		$this->login_security->register_hooks();

		// XML-RPC protection.
		$this->xmlrpc_guard->register_hooks();

		// REST API guard.
		$this->rest_guard->register_hooks();

		// Bot detector passive hooks.
		$this->bot_detector->register_hooks();

		// Scheduled cron scans.
		add_action( 'gs_scheduled_malware_scan',    [ $this->scanner,      'run_scheduled_scan' ] );
		add_action( 'gs_scheduled_integrity_check', [ $this->file_monitor, 'run_scheduled_check' ] );

		// Plugin action links.
		add_filter( 'plugin_action_links_' . GS_PLUGIN_BASE, [ $this, 'add_action_links' ] );

		// Clean transients on WP cron heartbeat.
		add_action( 'gs_cleanup', [ $this, 'run_cleanup' ] );
	}

	// ── Action Links ─────────────────────────────
	public function add_action_links( array $links ): array {
		$settings = sprintf(
			'<a href="%s">%s</a>',
			esc_url( admin_url( 'admin.php?page=guardian-shield' ) ),
			esc_html__( 'Settings', 'guardian-shield' )
		);
		array_unshift( $links, $settings );
		return $links;
	}

	// ── Maintenance Cleanup ──────────────────────
	public function run_cleanup(): void {
		global $wpdb;
		$table = $wpdb->prefix . 'gs_attack_logs';
		$days  = (int) gs_get_option( 'log_retention_days', 30 );

		// Delete logs older than retention period.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM `{$table}` WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)",
				$days
			)
		);

		// Also purge expired transients (WP sometimes misses these).
		$wpdb->query(
			"DELETE FROM {$wpdb->options}
			 WHERE option_name LIKE '_transient_timeout_gs_%'
			   AND option_value < UNIX_TIMESTAMP()"
		);
	}

	// ── Directory Scaffolding ────────────────────
	private function ensure_directories(): void {
		$dirs = [ GS_LOG_DIR, GS_QUARANTINE ];
		foreach ( $dirs as $dir ) {
			if ( ! is_dir( $dir ) ) {
				wp_mkdir_p( $dir );
				// Drop an .htaccess and index.php to prevent direct browsing.
				file_put_contents( $dir . '.htaccess', 'Deny from all' );
				file_put_contents( $dir . 'index.php', '<?php // Silence is golden.' );
			}
		}
	}

	/** Prevent cloning of the singleton. */
	private function __clone() {}
}
