<?php

declare(strict_types=1);

namespace SiteFort\Modules\Scanner\Queue;

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

class ScanCron {

    public const HOOK = 'sitefort_scan_cron_check';
    public const SCHEDULED_SCAN_HOOK = 'sitefort_scheduled_scan_job';

    private const SUPPORTED_SCHEDULES = [
        'every_6_hours'  => true,
        'every_12_hours' => true,
        'daily'          => true,
        'weekly'         => true,
    ];

    public static function init(): void {
        self::schedule();

        // Keep settings reconciliation and ticker healing off frontend requests.
        if ( ! is_admin() && ! wp_doing_cron() && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
            return;
        }

        self::reconcile_scheduled_scan();

        // Self-heal a missing ticker for an active scan.
        if ( ! wp_next_scheduled( self::HOOK ) && ScanQueue::is_active() ) {
            self::ensure_active_scan_ticker();
        }
    }

    public static function schedule(): void {
        add_filter( 'cron_schedules', [ __CLASS__, 'add_cron_interval' ] );
        add_action( self::HOOK, [ __CLASS__, 'check_and_resume' ] );
        add_action( self::SCHEDULED_SCAN_HOOK, [ __CLASS__, 'handle_scheduled_scan' ] );
    }

    /**
     * Schedule the every-minute resume ticker; it exists only while a scan is active.
     */
    public static function ensure_active_scan_ticker(): void {
        if ( wp_next_scheduled( self::HOOK ) ) {
            return;
        }

        $scheduled = wp_schedule_event( time() + MINUTE_IN_SECONDS, 'every_minute', self::HOOK );
        if ( ! $scheduled || is_wp_error( $scheduled ) ) {
            wp_schedule_single_event( time() + MINUTE_IN_SECONDS, self::HOOK );
        }
    }

    public static function unschedule(): void {
        $timestamp = wp_next_scheduled( self::HOOK );
        if ( $timestamp ) {
            wp_unschedule_event( $timestamp, self::HOOK );
        }
    }

    public static function add_cron_interval( array $schedules ): array {
        $schedules['every_minute'] = [
            'interval' => MINUTE_IN_SECONDS,
            'display'  => 'Every Minute',
        ];

        return $schedules;
    }

    /**
     * @param array<string,mixed>|null $settings
     * @return true|\WP_Error
     */
    public static function reconcile_scheduled_scan( ?array $settings = null, ?bool $scheduled_scans_allowed = null, bool $force = false ) {
        if ( null === $settings ) {
            $settings = self::get_scanner_settings();
        }

        if ( ! is_array( $settings ) ) {
            self::clear_scheduled_scan();
            return true;
        }

        $schedule = self::normalize_schedule_name( $settings['schedule'] ?? 'none' );
        $allowed  = null === $scheduled_scans_allowed ? self::allows_scheduled_scans() : $scheduled_scans_allowed;

        if ( 'none' === $schedule || ! $allowed ) {
            self::clear_scheduled_scan();
            return true;
        }

        if ( ! self::is_supported_schedule( $schedule ) ) {
            self::clear_scheduled_scan();
            return new \WP_Error( 'sitefort_invalid_scan_schedule', 'Scanner schedule is not supported.' );
        }

        $next_run = self::next_run_timestamp( $schedule );
        $existing = wp_get_scheduled_event( self::SCHEDULED_SCAN_HOOK );
        if ( ! $force && $existing && empty( $existing->schedule ) && isset( $existing->timestamp ) ) {
            $existing_timestamp = (int) $existing->timestamp;
            if ( $existing_timestamp <= time() || $existing_timestamp === $next_run ) {
                return true;
            }
        }

        self::clear_scheduled_scan();

        $scheduled = wp_schedule_single_event(
            $next_run,
            self::SCHEDULED_SCAN_HOOK,
            [],
            true
        );

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

        if ( true !== $scheduled ) {
            return new \WP_Error( 'sitefort_scan_schedule_failed', 'WordPress could not schedule the next automated scan.' );
        }

        return true;
    }

    public static function handle_scheduled_scan(): void {
        $settings = self::get_scanner_settings();
        $schedule = is_array( $settings ) ? self::normalize_schedule_name( $settings['schedule'] ?? 'none' ) : 'none';
        if ( ! is_array( $settings ) || 'none' === $schedule ) {
            self::clear_scheduled_scan();
            return;
        }

        if ( ! self::is_supported_schedule( $schedule ) ) {
            self::clear_scheduled_scan();
            return;
        }

        if ( ! self::allows_scheduled_scans() ) {
            self::clear_scheduled_scan();
            return;
        }

        $next_schedule = self::reconcile_scheduled_scan( $settings, true, true );
        if ( is_wp_error( $next_schedule ) ) {
            sitefort_log( 'SiteFort: Scheduled scan next-run scheduling failed: ' . $next_schedule->get_error_message() );
        }

        if ( ScanQueue::is_active() ) {
            return;
        }

        $key_error = \SiteFort\Modules\Cloud\Service\CloudSettings::get_key_error();
        if ( $key_error ) {
            sitefort_log( 'SiteFort: Scheduled scan skipped - encryption key issue (' . ( $key_error['code'] ?? 'unknown' ) . '). Resolve via the SiteFort admin panel.' );
            return;
        }

        $cloud_settings = \SiteFort\Modules\Cloud\Service\CloudSettings::get_all();
        if ( empty( $cloud_settings['site_token'] ) || empty( $cloud_settings['site_secret'] ) ) {
            return;
        }

        $intensity = (string) ( $settings['scanIntensity'] ?? 'standard' );
        $type      = ( $intensity === 'deep' ) ? 'deep' : 'standard';
        $scan_id   = 'scan_' . wp_generate_uuid4();

        if ( ! class_exists( '\\SiteFort\\Modules\\Cloud\\Services\\CloudScanner' ) ) {
            require_once dirname( dirname( __DIR__ ) ) . '/Cloud/Services/CloudScanner.php';
        }

        if ( class_exists( '\\SiteFort\\Modules\\Cloud\\Services\\CloudScanner' ) ) {
            $cloud     = new \SiteFort\Modules\Cloud\Services\CloudScanner();
            $preflight = ScanWorker::run_preflight_check( $scan_id, $cloud );

            if ( is_wp_error( $preflight ) ) {
                sitefort_log( 'SiteFort: Scheduled scan aborted. Preflight failed: ' . $preflight->get_error_message() );
                return;
            }

            set_transient( 'sitefort_scan_preflight_' . $scan_id, true, HOUR_IN_SECONDS );
        }

        $queue = ScanQueue::create( $type, 'scheduled', $scan_id );
        if ( ! $queue ) {
            sitefort_log( 'SiteFort: Scheduled scan aborted. Queue creation failed.' );
            return;
        }

        ScanWorker::dispatch_next();
    }

    public static function check_and_resume(): void {
        // Cron ran our hook; clears any worker-not-responding flag.
        if ( ! class_exists( WorkerLiveness::class ) ) {
            require_once __DIR__ . '/WorkerLiveness.php';
        }
        WorkerLiveness::mark_background_alive();

        if ( ! ScanQueue::is_active() ) {
            // No scan to resume; drop the ticker (drains legacy recurring events too).
            self::unschedule();
            return;
        }

        if ( ! class_exists( TaskScheduler::class ) ) {
            require_once __DIR__ . '/TaskScheduler.php';
        }

        $queue   = ScanQueue::get();
        $scan_id = is_array( $queue ) ? (string) ( $queue['id'] ?? '' ) : '';
        if ( '' === $scan_id ) {
            return;
        }

        TaskScheduler::ensure_progress( $scan_id );
    }

    public static function cleanup(): void {
        self::unschedule();
        self::clear_scheduled_scan();

        $queue = ScanQueue::get();
        if ( $queue ) {
            ScanQueue::fail(
                'Plugin deactivated',
                [
                    'error_code' => 'plugin_deactivated',
                    'stage'      => 'plugin_lifecycle',
                    'retryable'  => false,
                    'status_code'=> 409,
                ]
            );
            ScanQueue::cleanup_task_queue( (string) ( $queue['id'] ?? '' ) );
        }
    }

    private static function clear_scheduled_scan(): void {
        wp_clear_scheduled_hook( self::SCHEDULED_SCAN_HOOK );
    }

    /**
     * @return array<string,mixed>|null
     */
    private static function get_scanner_settings(): ?array {
        if ( ! class_exists( 'SiteFort\\Modules\\Scanner\\Service\\ScannerSettings' ) ) {
            require_once dirname( __DIR__ ) . '/Service/ScannerSettings.php';
        }

        if ( ! class_exists( 'SiteFort\\Modules\\Scanner\\Service\\ScannerSettings' ) ) {
            return null;
        }

        return \SiteFort\Modules\Scanner\Service\ScannerSettings::get_all();
    }

    private static function allows_scheduled_scans(): bool {
        if ( ! class_exists( '\SiteFort\Modules\Cloud\Service\Entitlements' ) ) {
            require_once dirname( dirname( __DIR__ ) ) . '/Cloud/Service/Entitlements.php';
        }

        return class_exists( '\SiteFort\Modules\Cloud\Service\Entitlements' )
            && \SiteFort\Modules\Cloud\Service\Entitlements::allowsScheduledScans();
    }

    private static function normalize_schedule_name( $schedule ): string {
        $schedule = sanitize_key( (string) $schedule );
        if ( 'none' === $schedule || self::is_supported_schedule( $schedule ) ) {
            return $schedule;
        }

        return 'none';
    }

    private static function is_supported_schedule( string $schedule ): bool {
        return isset( self::SUPPORTED_SCHEDULES[ $schedule ] );
    }

    private static function next_run_timestamp( string $schedule ): int {
        $timezone = function_exists( 'wp_timezone' ) ? wp_timezone() : new \DateTimeZone( 'UTC' );
        $now      = new \DateTimeImmutable( 'now', $timezone );

        if ( 'every_6_hours' === $schedule ) {
            return self::next_interval_boundary( $now, 6 );
        }

        if ( 'every_12_hours' === $schedule ) {
            return self::next_interval_boundary( $now, 12 );
        }

        if ( 'weekly' === $schedule ) {
            return self::next_weekly_run( $now );
        }

        $candidate = $now->setTime( 2, 0, 0 );
        if ( $candidate <= $now ) {
            $candidate = $candidate->modify( '+1 day' );
        }

        return $candidate->getTimestamp();
    }

    private static function next_interval_boundary( \DateTimeImmutable $now, int $hours ): int {
        $candidate = $now->setTime( 0, 0, 0 );
        for ( $i = 0; $i < 8; $i++ ) {
            if ( $candidate > $now ) {
                return $candidate->getTimestamp();
            }
            $candidate = $candidate->modify( '+' . $hours . ' hours' );
        }

        return time() + ( $hours * HOUR_IN_SECONDS );
    }

    private static function next_weekly_run( \DateTimeImmutable $now ): int {
        $start_of_week = absint( get_option( 'start_of_week', 1 ) );
        if ( $start_of_week > 6 ) {
            $start_of_week = 1;
        }

        $current_day = (int) $now->format( 'w' );
        $days_until  = ( $start_of_week - $current_day + 7 ) % 7;
        $candidate   = $now->modify( '+' . $days_until . ' days' )->setTime( 2, 0, 0 );

        if ( $candidate <= $now ) {
            $candidate = $candidate->modify( '+7 days' );
        }

        return $candidate->getTimestamp();
    }
}
