<?php
if (!defined('ABSPATH')) exit;

class SS_Scanner {

    const TABLE = 'ss_scans';
    const JOB_LIMIT = 10;          // process up to 10 URLs at once
    const MAX_MEMORY_MB = 256;     // soft cap for memory use
    const RETRY_DELAY_SEC = 30;    // retry every 30s instead of 60
    const MAX_RETRIES = 3;

    public static function init() {
        add_filter('cron_schedules', [__CLASS__, 'register_fast_cron']);
        add_action('admin_init', [__CLASS__, 'maybe_schedule']);
        add_action('searchshifter_scanner_run', [__CLASS__, 'run_job']);
        register_activation_hook(SEARCHSHIFTER_PLUGIN_FILE, [__CLASS__, 'install']);
    }

    /** Create DB table */
    public static function install() {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE;
        $charset = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS $table (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            url TEXT NOT NULL,
            status VARCHAR(50) DEFAULT 'queued',
            attempts TINYINT DEFAULT 0,
            started_at DATETIME NULL,
            finished_at DATETIME NULL,
            log LONGTEXT NULL
        ) $charset;";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }

    /** Register faster cron (every 10s) */
    public static function register_fast_cron($schedules) {
        if (!isset($schedules['ten_seconds'])) {
            $schedules['ten_seconds'] = [
                'interval' => 10,
                'display'  => __('Every 10 Seconds', 'searchshifter')
            ];
        }
        return $schedules;
    }

    /** Ensure job schedule exists */
    public static function maybe_schedule() {
        if (!wp_next_scheduled('searchshifter_scanner_run')) {
            wp_schedule_event(time() + 10, 'ten_seconds', 'searchshifter_scanner_run');
        }
    }

    /** Run queued scans */
    public static function run_job() {
        global $wpdb;

        $table = $wpdb->prefix . self::TABLE;

        $running = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table read in cron/background job.
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is plugin-controlled.
                "SELECT COUNT(*) FROM $table WHERE status = %s",
                'running'
            )
        );



         if ( $running >= self::JOB_LIMIT ) {
            return;
        }

        $limit = self::JOB_LIMIT - $running;


        $jobs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table read in cron/background job.
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is plugin-controlled.
                "SELECT * FROM $table WHERE status = %s ORDER BY id ASC LIMIT %d",
                'queued',
                $limit
            )
        );


        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        if ( ! $jobs ) {
            return;
        }

        foreach ( $jobs as $job ) {
            self::process( $job, $table );
        }
    }


    /** Process one scan job */
    protected static function process( $job ) {
        global $wpdb;

        $table_name = $wpdb->prefix . self::TABLE;
        $job_id     = absint( $job->id );
        $job_url    = esc_url_raw( $job->url );

       $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update in cron/background job.
            $table_name,
            array(
                'status'     => 'running',
                'started_at' => current_time( 'mysql' ),
            ),
            array( 'id' => $job_id ),
            array( '%s', '%s' ),
            array( '%d' )
        );

        try {
            if ( memory_get_usage( true ) / 1048576 > self::MAX_MEMORY_MB ) {
                throw new Exception( 'Memory limit exceeded' );
            }

            usleep( 200000 );

            $post_id = url_to_postid( $job_url );

            if ( ! $post_id ) {
                $path = wp_parse_url( $job_url, PHP_URL_PATH );
                $slug = $path ? basename( untrailingslashit( $path ) ) : '';

            $post_id = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Core posts table lookup in cron/background job.
                    $wpdb->prepare(
                        "SELECT ID FROM {$wpdb->posts}
                        WHERE post_name = %s
                        AND post_status = 'publish'
                        LIMIT 1",
                        $slug
                    )
                );
            }

            $score = 60;
            $flags = array();

            if ( $post_id ) {
                $content = get_post_field( 'post_content', $post_id );

                if ( strpos( $content, 'ss-glossary-term' ) !== false ) {
                    $score += 10;
                    $flags[] = 'glossary linked';
                }

                // Check if schema exists either in the content or has been flagged for output
$has_schema =
    ( strpos( $content, 'application/ld+json' ) !== false ) ||
    get_post_meta( $post_id, '_ss_flag_add_schema', true );

if ( $has_schema ) {
    $score += 10;
    $flags[] = 'schema added';
}

                if (
                    stripos( $content, 'Frequently Asked Questions' ) !== false ||
                    stripos( $content, 'ss-qa' ) !== false
                ) {
                    $score += 10;
                    $flags[] = 'Q&A section found';
                }

                $rendered_content = apply_filters( 'the_content', $content );
$text = wp_strip_all_tags( html_entity_decode( $rendered_content ) );

preg_match_all( '/\b[\p{L}\p{N}]+\b/u', $text, $matches );
$word_count = count( $matches[0] );

if ( $word_count >= 300 ) {
    $score += 10;
    $flags[] = 'content length OK';
}
            }

            $score = min( 100, $score );

           $existing_log = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table read in cron/background job.
                $wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is plugin-controlled.
                    "SELECT log FROM $table_name WHERE id = %d",
                    $job_id
                )
            );

            $msg = sprintf(
                '✅ Scan complete for %s | Score: %d/100',
                $job_url,
                $score
            );

            if ( $flags ) {
                $msg .= ' | Found: ' . implode( ', ', $flags );
            }

            $updated_log = trim( ( $existing_log ?: '' ) . "\n" . $msg );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table update required.
            $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update in cron/background job.
                $table_name,
                array(
                    'status'      => 'done',
                    'finished_at' => current_time( 'mysql' ),
                    'log'         => $updated_log,
                ),
                array( 'id' => $job_id ),
                array( '%s', '%s', '%s' ),
                array( '%d' )
            );

        } catch ( Exception $e ) {

            $attempts = absint( $job->attempts ) + 1;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table update required.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching -- Cron-based job; caching unnecessary.
            $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update in cron job.
                $table_name,
                array(
                    'status'   => ( $attempts >= self::MAX_RETRIES ? 'failed' : 'queued' ),
                    'attempts' => $attempts,
                    'log'      => sanitize_text_field( $e->getMessage() ),
                ),
                array( 'id' => $job_id ),
                array( '%s', '%d', '%s' ),
                array( '%d' )
            );


        }
    }

}

// --- Optional “Force Run” button ---
add_action('admin_post_ss_run_now', function() {
    if (!current_user_can('manage_options')) wp_die('Permission denied');
    do_action( 'searchshifter_scanner_run' );
    wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=searchshifter-visibility-scanner'));
    exit;
});
