<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-link-checker.php
// Description: Broken link checker — nightly cron scan, per-post cache, AI replacement suggestions - NUCLEAR+++
// Last Edited: 2026-05-16
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

class LinkChecker
{
    public const CRON_HOOK      = 'amai_link_checker_nightly';
    private const META_KEY      = '_amai_broken_links';
    private const SCAN_TIMEOUT  = 8;
    private const USER_AGENT    = 'Mozilla/5.0 (compatible; AutoMarketerAI-LinkChecker/1.0)';

    // --------------------------------------------------
    // Register hooks
    // --------------------------------------------------
    public static function register(): void
    {
        add_action('admin_enqueue_scripts', [self::class, 'enqueueAssets']);
        add_action(self::CRON_HOOK,         [self::class, 'runNightlyScan']);
        add_action('wp_ajax_amai_link_checker_scan_post',   [self::class, 'handleScanPost']);
        add_action('wp_ajax_amai_link_checker_ai_replace',  [self::class, 'handleAiReplace']);
        add_action('wp_ajax_amai_link_checker_update_link', [self::class, 'handleUpdateLink']);
        add_action('wp_ajax_amai_link_checker_load',        [self::class, 'handleLoad']);

        if (!wp_next_scheduled(self::CRON_HOOK)) {
            wp_schedule_event(strtotime('tomorrow 03:00:00'), 'daily', self::CRON_HOOK);
        }
    }

    // --------------------------------------------------
    // Enqueue assets on this page only
    // --------------------------------------------------
    public static function enqueueAssets(string $hook): void
    {
        if ($hook !== 'automarketer-ai_page_automarketer-ai-content') {
            return;
        }
        wp_enqueue_style(
            'amai-link-checker',
            AMAI_PLUGIN_URL . 'assets/css/link-checker.css',
            [],
            (string) filemtime(AMAI_PLUGIN_DIR . 'assets/css/link-checker.css')
        );
        wp_enqueue_script(
            'amai-link-checker',
            AMAI_PLUGIN_URL . 'assets/js/amai-link-checker.js',
            [],
            (string) filemtime(AMAI_PLUGIN_DIR . 'assets/js/amai-link-checker.js'),
            true
        );
        wp_localize_script('amai-link-checker', 'amaiLinkChecker', [
            'ajaxurl'  => admin_url('admin-ajax.php'),
            'nonce'    => wp_create_nonce('amai_link_checker'),
            'adminUrl' => admin_url(),
        ]);
    }

    // --------------------------------------------------
    // Render the page — template handles HTML
    // --------------------------------------------------
    public static function render(): void
    {
        if (!in_array('content-optimizer', (array) get_option('amai_active_modules', []), true)) {
            wp_die('Content Optimizer module is not active.');
        }
        include AMAI_PLUGIN_DIR . 'templates/link-checker.php';
    }

    // --------------------------------------------------
    // Nightly cron — scan all published posts
    // --------------------------------------------------
    public static function runNightlyScan(): void
    {
        $posts = get_posts([
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'fields'         => 'ids',
        ]);
        foreach ($posts as $post_id) {
            self::scanPost((int) $post_id);
        }
    }

    // --------------------------------------------------
    // Scan a single post — extract + HEAD-check all external links
    // Stores results in postmeta _amai_broken_links as JSON
    // --------------------------------------------------
    public static function scanPost(int $post_id): array
    {
        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            return [];
        }

        $links  = self::extractExternalLinks($post->post_content);
        $broken = [];

        foreach ($links as $link) {
            $status = self::headCheck($link['url']);
            if ($status !== 200) {
                $broken[] = [
                    'url'    => $link['url'],
                    'anchor' => $link['anchor'],
                    'status' => $status,
                ];
            }
        }

        update_post_meta($post_id, self::META_KEY, wp_json_encode($broken));
        update_post_meta($post_id, '_amai_broken_links_scanned_at', gmdate('c'));

        return $broken;
    }

    // --------------------------------------------------
    // Extract all external links from post content
    // Returns [['url' => ..., 'anchor' => ...], ...]
    // --------------------------------------------------
    private static function extractExternalLinks(string $content): array
    {
        $home    = home_url();
        $results = [];

        preg_match_all('/<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is', $content, $matches, PREG_SET_ORDER);

        foreach ($matches as $match) {
            $url = trim($match[1]);
            if (
                (str_starts_with($url, 'http://') || str_starts_with($url, 'https://'))
                && !str_starts_with($url, $home)
            ) {
                $results[] = [
                    'url'    => $url,
                    'anchor' => wp_strip_all_tags($match[2]),
                ];
            }
        }

        return $results;
    }

    // --------------------------------------------------
    // HEAD-check a URL — returns HTTP status code or 0 on failure
    // --------------------------------------------------
    private static function headCheck(string $url): int
    {
        $response = wp_remote_head($url, [
            'timeout'    => self::SCAN_TIMEOUT,
            'user-agent' => self::USER_AGENT,
            'sslverify'  => false,
            'redirection' => 5,
        ]);

        if (is_wp_error($response)) {
            return 0;
        }

        return (int) wp_remote_retrieve_response_code($response);
    }

    // --------------------------------------------------
    // Get all broken links across all published posts
    // Returns [['post_id' => ..., 'post_title' => ..., 'edit_url' => ..., 'scanned_at' => ..., 'links' => [...]], ...]
    // --------------------------------------------------
    public static function getAllBrokenLinks(): array
    {
        global $wpdb;

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT pm.post_id, pm.meta_value, p.post_title
                 FROM {$wpdb->postmeta} pm
                 JOIN {$wpdb->posts} p ON p.ID = pm.post_id
                 WHERE pm.meta_key = %s
                   AND p.post_status = 'publish'
                   AND p.post_type = 'post'
                 ORDER BY p.post_title ASC",
                self::META_KEY
            )
        );

        $result = [];

        foreach ($rows as $row) {
            $links = json_decode((string) $row->meta_value, true);
            if (!is_array($links) || empty($links)) {
                continue;
            }
            $scanned_at = (string) get_post_meta((int) $row->post_id, '_amai_broken_links_scanned_at', true);
            $result[]   = [
                'post_id'     => (int) $row->post_id,
                'post_title'  => (string) $row->post_title,
                'edit_url'    => get_edit_post_link((int) $row->post_id, 'raw'),
                'scanned_at'  => $scanned_at,
                'links'       => $links,
            ];
        }

        return $result;
    }

    // --------------------------------------------------
    // AJAX — load all broken links for page render
    // --------------------------------------------------
    public static function handleLoad(): void
    {
        check_ajax_referer('amai_link_checker', 'nonce');
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $data         = self::getAllBrokenLinks();
        $last_scan    = (string) get_option('amai_link_checker_last_scan', '');
        $total_broken = array_sum(array_map(fn($r) => count($r['links']), $data));

        wp_send_json_success([
            'rows'         => $data,
            'total_broken' => $total_broken,
            'last_scan'    => $last_scan,
        ]);
    }

    // --------------------------------------------------
    // AJAX — scan a single post on demand
    // --------------------------------------------------
    public static function handleScanPost(): void
    {
        check_ajax_referer('amai_link_checker', 'nonce');
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $post_id = isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0;
        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid post ID.']);
        }

        $broken = self::scanPost($post_id);

        wp_send_json_success([
            'post_id' => $post_id,
            'broken'  => $broken,
        ]);
    }

    // --------------------------------------------------
    // AJAX — AI replacement suggestion for a broken URL
    // --------------------------------------------------
    // --------------------------------------------------
    // Check if a URL is live -- returns true if HTTP status is 2xx or 3xx
    // --------------------------------------------------
    private static function isUrlLive(string $url): bool
    {
        $response = wp_remote_head($url, [
            'timeout'     => 8,
            'redirection' => 3,
            'user-agent'  => 'Mozilla/5.0 (compatible; AutoMarketerAI/1.0)',
            'sslverify'   => false,
        ]);
        if (is_wp_error($response)) {
            return false;
        }
        $code = (int) wp_remote_retrieve_response_code($response);
        return $code >= 200 && $code < 400;
    }

    public static function handleAiReplace(): void
    {
        check_ajax_referer('amai_link_checker', 'nonce');
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $post_id    = isset($_POST['post_id'])    ? (int) $_POST['post_id']                          : 0;
        $broken_url = isset($_POST['broken_url']) ? sanitize_text_field((string) $_POST['broken_url']) : '';
        $anchor     = isset($_POST['anchor'])     ? sanitize_text_field((string) $_POST['anchor'])     : '';

        if ($post_id <= 0 || $broken_url === '') {
            wp_send_json_error(['message' => 'Missing required fields.']);
        }

        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            wp_send_json_error(['message' => 'Post not found.']);
        }

        // Load the active AI module
        $active_modules = (array) get_option('amai_active_modules', []);
        $ai_id          = '';
        foreach ($active_modules as $mod) {
            if (str_starts_with((string) $mod, 'ai-')) {
                $ai_id = (string) $mod;
                break;
            }
        }

        if ($ai_id === '') {
            wp_send_json_error(['message' => 'No active AI provider found.']);
        }

        require_once AMAI_PLUGIN_DIR . 'modules/' . $ai_id . '.php';
        $class = 'AutoMarketerAI\\Modules\\' . ucfirst(substr($ai_id, 3));

        if (!class_exists($class)) {
            wp_send_json_error(['message' => 'AI module class not found.']);
        }

        $key_option = 'amai_api_key_' . $ai_id;
        $api_key    = KeyVault::decrypt((string) get_option($key_option, ''));

        if ($api_key === '') {
            wp_send_json_error(['message' => 'AI API key not configured.']);
        }

        $module = new $class();
        $model  = $module->get_default_model();

        $topic   = get_bloginfo('name');
        $excerpt = wp_strip_all_tags(wp_trim_words($post->post_content, 60));

        $banned_lc = [
            'wikipedia.org','facebook.com','twitter.com','x.com','instagram.com',
            'linkedin.com','tiktok.com','youtube.com','sproutsocial.com','hubspot.com',
            'buffer.com','hootsuite.com','later.com','medium.com','forbes.com',
            'entrepreneur.com','inc.com','socialmediaexaminer.com','marketingprofs.com',
            'pewresearch.org','score.org',
        ];

        $tried_urls = [];
        $result     = null;

        for ($attempt = 1; $attempt <= 3; $attempt++) {
            $exclusion = count($tried_urls) > 0
                ? ' Do NOT suggest any of these URLs which were already tried and failed: ' . implode(', ', $tried_urls) . '.'
                : '';

            $prompt = 'A blog post titled "' . $post->post_title . '" has a broken outbound link that needs replacing.'
                . ' Broken URL: ' . $broken_url
                . ' Anchor text: "' . $anchor . '"'
                . ' Post context: "' . $excerpt . '"'
                . $exclusion
                . ' Suggest the single best replacement URL -- a specific page (not a homepage) on any well-known, authoritative domain that is directly relevant to the anchor text and post topic.'
                . ' The replacement must not be from: wikipedia.org, sproutsocial.com, hubspot.com, buffer.com, hootsuite.com, later.com, socialmediaexaminer.com, marketingprofs.com, forbes.com, entrepreneur.com, inc.com, medium.com, linkedin.com, pewresearch.org, sba.gov, score.org, or any social media platform. Only suggest domains that serve pages publicly without requiring login or blocking automated requests.'
                . ' Avoid dated research reports or pages with years in the URL. Choose evergreen content.'
                . ' Respond with ONLY a JSON object: {"url":"https://example.com/page","source":"Example Site","reason":"One sentence why this is a good replacement."}';

            $raw   = $module->generate_raw($prompt, $api_key, $model, 256);
            $clean = trim((string) preg_replace('/^```json|^```|```$/m', '', trim($raw)));
            $json  = json_decode($clean, true);

            if (!is_array($json) || empty($json['url'])) {
                continue;
            }

            $suggested_url = sanitize_url((string) $json['url']);

            if (!str_starts_with($suggested_url, 'https://') && !str_starts_with($suggested_url, 'http://')) {
                continue;
            }

            // Check banned domains
            $url_host  = strtolower((string) parse_url($suggested_url, PHP_URL_HOST));
            $is_banned = false;
            foreach ($banned_lc as $banned) {
                if ($url_host === $banned || str_ends_with($url_host, '.' . $banned)) {
                    $is_banned = true;
                    break;
                }
            }
            if ($is_banned) {
                $tried_urls[] = $suggested_url;
                continue;
            }

            // Verify the URL is actually live
            if (!self::isUrlLive($suggested_url)) {
                $tried_urls[] = $suggested_url;
                continue;
            }

            $result = [
                'suggested_url' => $suggested_url,
                'source'        => sanitize_text_field((string) ($json['source'] ?? '')),
                'reason'        => sanitize_text_field((string) ($json['reason'] ?? '')),
            ];
            break;
        }

        if ($result === null) {
            wp_send_json_error(['message' => 'Could not find a valid replacement link after 3 attempts. Try again.']);
        }

        wp_send_json_success($result);
    }

    // --------------------------------------------------
    // AJAX — update a broken link in post content with a new URL
    // --------------------------------------------------
    public static function handleUpdateLink(): void
    {
        check_ajax_referer('amai_link_checker', 'nonce');
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $post_id    = isset($_POST['post_id'])    ? (int) $_POST['post_id']                            : 0;
        $broken_url = isset($_POST['broken_url']) ? sanitize_text_field((string) $_POST['broken_url']) : '';
        $new_url    = isset($_POST['new_url'])    ? sanitize_url((string) $_POST['new_url'])            : '';

        if ($post_id <= 0 || $broken_url === '' || $new_url === '') {
            wp_send_json_error(['message' => 'Missing required fields.']);
        }

        if (!str_starts_with($new_url, 'http://') && !str_starts_with($new_url, 'https://')) {
            wp_send_json_error(['message' => 'New URL must begin with http:// or https://.']);
        }

        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            wp_send_json_error(['message' => 'Post not found.']);
        }

        // Replace all instances of the broken URL in post content
        $escaped_broken = preg_quote($broken_url, '/');
        $new_content    = preg_replace(
            '/href=["\']' . $escaped_broken . '["\']/',
            'href="' . esc_url($new_url) . '"',
            $post->post_content
        );

        if ($new_content === $post->post_content) {
            wp_send_json_error(['message' => 'URL not found in post content. It may have already been updated.']);
        }

        $result = wp_update_post([
            'ID'           => $post_id,
            'post_content' => $new_content,
        ], true);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => 'Failed to update post: ' . $result->get_error_message()]);
        }

        // Re-scan post so the broken links cache reflects the fix
        $remaining = self::scanPost($post_id);

        wp_send_json_success([
            'post_id'   => $post_id,
            'remaining' => $remaining,
        ]);
    }

    // --------------------------------------------------
    // Get broken link count for a single post (for analyzer warning)
    // --------------------------------------------------
    public static function getBrokenLinkCount(int $post_id): int
    {
        $raw = get_post_meta($post_id, self::META_KEY, true);
        if ($raw === '' || $raw === false) {
            return 0;
        }
        $links = json_decode((string) $raw, true);
        return is_array($links) ? count($links) : 0;
    }
}