<?php
defined('ABSPATH') || exit;

class FixLinks_Crawler {

    private const BATCH_SIZE  = 15;
    private const QUEUE_LIMIT = 2000;

    public static function run_scheduled(): void {
        self::start_crawl_queue();
    }

    public static function process_batch_cron(): void {
        if (get_option('fixlinks_crawl_status') !== 'running') return;
        self::process_batch();
    }

    public static function ajax_run(): void {
        check_ajax_referer('fixlinks_nonce', 'nonce');
        if (!current_user_can('manage_options')) wp_die('Forbidden');
        $total     = self::start_crawl_queue();
        $found     = self::process_batch();
        $remaining = FixLinks_DB::count_queue('pending');
        $status    = $remaining > 0 ? 'running' : 'complete';
        if ($status === 'complete') {
            update_option('fixlinks_crawl_status', 'complete');
            update_option('fixlinks_last_crawl', current_time('mysql'));
        }
        wp_send_json_success(['total' => $total, 'found' => $found, 'status' => $status, 'remaining' => $remaining]);
    }

    public static function ajax_progress(): void {
        check_ajax_referer('fixlinks_nonce', 'nonce');
        $total     = (int)get_option('fixlinks_crawl_total', 0);
        $remaining = FixLinks_DB::count_queue('pending');
        $done      = max(0, $total - $remaining);
        wp_send_json_success([
            'status'    => get_option('fixlinks_crawl_status', 'idle'),
            'total'     => $total,
            'done'      => $done,
            'remaining' => $remaining,
            'percent'   => $total > 0 ? min(100, (int)round($done / $total * 100)) : 0,
            'found'     => FixLinks_DB::count_broken('pending'),
        ]);
    }

    private static function start_crawl_queue(): int {
        FixLinks_DB::clear_crawl_queue();
        update_option('fixlinks_crawl_status', 'running');
        $posts = get_posts(['post_type' => ['post', 'page'], 'post_status' => 'publish', 'numberposts' => -1, 'fields' => 'ids']);
        $seen  = [];
        $batch = [];
        $total = 0;
        foreach ($posts as $pid) {
            if ($total >= self::QUEUE_LIMIT) break;
            $permalink = get_permalink($pid);
            foreach (self::extract_links(get_post_field('post_content', $pid)) as $link) {
                if (!self::is_external($link['url'])) continue;
                $norm = rtrim($link['url'], '/');
                if (isset($seen[$norm])) continue;
                $seen[$norm] = true;
                $batch[]     = ['url' => $link['url'], 'source_page' => $permalink, 'anchor_text' => $link['anchor']];
                $total++;
                if (count($batch) >= 100) {
                    FixLinks_DB::insert_queue_batch($batch);
                    $batch = [];
                }
            }
        }
        if (!empty($batch)) FixLinks_DB::insert_queue_batch($batch);
        update_option('fixlinks_crawl_total', $total);
        return $total;
    }

    public static function process_batch(): int {
        $items = FixLinks_DB::get_pending_queue(self::BATCH_SIZE);
        $found = 0;
        foreach ($items as $item) {
            $status = self::check_url($item->url);
            $broken = self::is_broken($status);
            FixLinks_DB::mark_queue_item((int)$item->id, $broken ? 'broken' : 'done', $status);
            if ($broken) {
                FixLinks_DB::upsert_link($item->url, $item->source_page, $item->anchor_text, $status);
                $found++;
            }
        }
        update_option('fixlinks_crawl_done', FixLinks_DB::count_queue('done') + FixLinks_DB::count_queue('broken'));
        if (FixLinks_DB::count_queue('pending') === 0) {
            update_option('fixlinks_crawl_status', 'complete');
            update_option('fixlinks_last_crawl', current_time('mysql'));
        }
        return $found;
    }

    private static function extract_links(string $content): array {
        $links = [];
        preg_match_all('/<a[^>]+href=["\']([^"\'#][^"\']*)["\'][^>]*>(.*?)<\/a>/si', $content, $m, PREG_SET_ORDER);
        foreach ($m as $match) {
            $url = esc_url_raw(trim($match[1]));
            if ($url && filter_var($url, FILTER_VALIDATE_URL)) {
                $links[] = ['url' => $url, 'anchor' => wp_strip_all_tags($match[2])];
            }
        }
        return $links;
    }

    private static function is_external(string $url): bool {
        $sh = wp_parse_url( get_site_url(), PHP_URL_HOST );
        $uh = wp_parse_url( $url, PHP_URL_HOST );
        return $uh && strtolower($uh) !== strtolower($sh ?? '');
    }

    private static function check_url(string $url): int {
        $r = wp_remote_head($url, [
            'timeout'    => 8,
            'redirection'=> 5,
            'sslverify'  => false,
            'user-agent' => 'FixLinksAI-Bot/3.0 (+https://fixlinks.getinfotoyou.com)',
        ]);
        if (is_wp_error($r)) return 0;
        return (int)wp_remote_retrieve_response_code($r);
    }

    private static function is_broken(int $s): bool {
        return in_array($s, [0, 404, 410], true) || ($s >= 500 && $s < 600);
    }

    public static function get_site_urls(int $limit = 200): array {
        $posts = get_posts(['post_type' => ['post', 'page'], 'post_status' => 'publish', 'numberposts' => $limit, 'fields' => 'ids']);
        return array_map('get_permalink', $posts);
    }
}