<?php
/**
 * Admin page: AuctionForge → Sync Status
 *
 * Single-page diagnostic + control panel exposing:
 *   - Status of every sync stream (last fired, status, count, error, watermark)
 *   - Manual-fire buttons for each cron event
 *   - One-shot utilities (reset watermark, reinstall keepalive, test connection,
 *     backfill bids from legacy tracking, etc.)
 *
 * Lives under "Lots → Sync Status" so it's reachable next to the existing AF
 * admin tools. Restricted to `manage_options`. All state-changing actions go
 * through admin-ajax with a CSRF nonce.
 */

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

class AF_Sync_Status_Page {

    const PAGE_SLUG = 'auctionforge-sync-status';
    const NONCE     = 'af_sync_status_nonce';

    /**
     * Map of UI label → cron hook + endpoint slug. Drives both the status
     * grid and the manual-fire buttons.
     */
    private static function streams() {
        return array(
            array('label' => 'User tracking',     'hook' => 'auctionforge_sync_user_tracking',     'option' => 'auctionforge_last_tracking_sync', 'schedule' => 'hourly'),
            array('label' => 'User profile',      'hook' => 'auctionforge_sync_user_profile',      'option' => 'auctionforge_last_sync-user-profile',   'schedule' => 'hourly'),
            array('label' => 'User interests',    'hook' => 'auctionforge_sync_user_interests',    'option' => 'auctionforge_last_sync-user-interests', 'schedule' => 'hourly'),
            array('label' => 'User activity',     'hook' => 'auctionforge_sync_user_activity',     'option' => 'auctionforge_last_sync-user-activity',  'schedule' => 'hourly'),
            array('label' => 'Lots',              'hook' => 'auctionforge_sync_lots',              'option' => 'auctionforge_last_sync-lots',           'schedule' => 'every 10 min'),
            array('label' => 'Bids + wins',       'hook' => 'auctionforge_sync_bids_wins',         'option' => 'auctionforge_last_sync-bids-wins',      'schedule' => 'hourly'),
            array('label' => 'Vulnerabilities',   'hook' => 'auctionforge_sync_vulnerabilities',   'option' => 'auctionforge_last_vuln_sync',           'schedule' => 'twice daily'),
            array('label' => 'Report health',     'hook' => 'auctionforge_report_health',          'option' => null,                                    'schedule' => 'twice daily'),
            array('label' => 'Check updates',     'hook' => 'auctionforge_check_updates',          'option' => null,                                    'schedule' => 'hourly'),
            array('label' => 'Fraud enforcement', 'hook' => 'auctionforge_sync_fraud_enforcement', 'option' => null,                                    'schedule' => 'every 10 min'),
        );
    }

    public static function init() {
        // Embedded as a panel inside the existing Sync Status admin page
        // (see admin/view/sync-status.php). We register only the AJAX
        // handlers here — the panel is rendered inline via render_panel().

        add_action('wp_ajax_af_sync_status_fire',                array(__CLASS__, 'ajax_fire'));
        add_action('wp_ajax_af_sync_status_reset_watermark',     array(__CLASS__, 'ajax_reset_watermark'));
        add_action('wp_ajax_af_sync_status_test_connection',     array(__CLASS__, 'ajax_test_connection'));
        add_action('wp_ajax_af_sync_status_clear_cron_lock',     array(__CLASS__, 'ajax_clear_cron_lock'));
        add_action('wp_ajax_af_sync_status_reinstall_keepalive', array(__CLASS__, 'ajax_reinstall_keepalive'));
        add_action('wp_ajax_af_sync_status_backfill_bids',       array(__CLASS__, 'ajax_backfill_bids'));
        add_action('wp_ajax_af_sync_status_run_all_due',         array(__CLASS__, 'ajax_run_all_due'));
    }

    /**
     * Render the diagnostic panel. Called from admin/view/sync-status.php
     * after the existing Sync Status content. Skips silently if the user
     * doesn't have manage_options.
     */
    public static function render_panel() {
        if (!current_user_can('manage_options')) return;

        $nonce = wp_create_nonce(self::NONCE);
        $server_url = get_option('auctionforge_update_server_url', '');
        $api_key    = get_option('auctionforge_update_api_key', '');
        $key_tail   = $api_key ? '…' . substr($api_key, -8) : '(empty)';

        $plugin_version = defined('AUCTIONFORGE_VERSION') ? AUCTIONFORGE_VERSION : '(undefined)';
        $db_version     = get_option('af_stats_db_version', 0);
        $keepalive_ver  = get_option('auctionforge_keepalive_installed_version', '(not installed)');
        $keepalive_at   = get_option('auctionforge_keepalive_installed_at', '');
        $reactivated    = (int) get_option('auctionforge_auto_reactivated_count', 0);
        $reactivated_at = get_option('auctionforge_auto_reactivated_at', '');
        $reactivate_failed = get_option('auctionforge_auto_reactivated_failed_at', '');

        ?>
        <hr style="margin:40px 0 20px 0; border:0; border-top:2px solid #c3c4c7;">
        <div>
            <h1 style="margin-bottom:6px;">Sync Diagnostics &amp; Manual Controls</h1>
            <p style="color:#666;">Live state of every wpdeploy sync stream + manual triggers + one-shot utilities. All actions are scoped to this site.</p>

            <h2>Connection</h2>
            <table class="widefat striped" style="max-width: 720px;">
                <tr><th style="width:200px;">Plugin version</th><td><code><?php echo esc_html($plugin_version); ?></code></td></tr>
                <tr><th>DB schema version</th><td><code><?php echo esc_html($db_version); ?></code> (target: 3)</td></tr>
                <tr><th>wpdeploy server</th><td><code><?php echo esc_html($server_url ?: '(not configured)'); ?></code></td></tr>
                <tr><th>API key</th><td><code><?php echo esc_html($key_tail); ?></code></td></tr>
                <tr><th>Keepalive</th><td>
                    v<?php echo esc_html($keepalive_ver); ?>
                    <?php if ($keepalive_at): ?> · installed <?php echo esc_html($keepalive_at); ?><?php endif; ?>
                    <?php if ($reactivated): ?>
                        · <strong style="color:#d63638;"><?php echo esc_html($reactivated); ?> auto-reactivation<?php echo $reactivated > 1 ? 's' : ''; ?></strong>
                        (last: <?php echo esc_html($reactivated_at); ?>)
                    <?php endif; ?>
                    <?php if ($reactivate_failed): ?>
                        · <strong style="color:#d63638;">install failed:</strong> <?php echo esc_html($reactivate_failed); ?>
                    <?php endif; ?>
                </td></tr>
            </table>

            <h2 style="margin-top:30px;">Sync streams</h2>
            <table class="widefat striped">
                <thead>
                    <tr>
                        <th>Stream</th>
                        <th>Schedule</th>
                        <th>Last fired</th>
                        <th>Status</th>
                        <th>Count</th>
                        <th>Next run</th>
                        <th>Watermark</th>
                        <th style="width:130px;">Action</th>
                    </tr>
                </thead>
                <tbody>
                <?php foreach (self::streams() as $s):
                    $next_ts = wp_next_scheduled($s['hook']);
                    $next    = $next_ts ? date('Y-m-d H:i', $next_ts) : '<em>not scheduled</em>';
                    $status  = $count = $at = $err = '';
                    if ($s['option']) {
                        $status = get_option($s['option'] . '_status', '');
                        $count  = get_option($s['option'] . '_count', '');
                        $at     = get_option($s['option'] . '_at', '');
                        $err    = get_option($s['option'] . '_error', '');
                    }
                    $watermark_key = self::watermark_key_for($s['hook']);
                    $watermark = $watermark_key ? get_option($watermark_key, '(none)') : '—';
                    $color = self::status_color($status);
                ?>
                    <tr>
                        <td><strong><?php echo esc_html($s['label']); ?></strong><br>
                            <small style="color:#666;"><code><?php echo esc_html($s['hook']); ?></code></small>
                        </td>
                        <td><?php echo esc_html($s['schedule']); ?></td>
                        <td><?php echo $at ? esc_html($at) : '<em>—</em>'; ?></td>
                        <td><span style="color:<?php echo esc_attr($color); ?>;">●</span> <?php echo esc_html($status ?: '—'); ?>
                            <?php if ($err): ?><br><small style="color:#d63638;"><?php echo esc_html(substr($err, 0, 80)); ?></small><?php endif; ?>
                        </td>
                        <td><?php echo $count !== '' ? esc_html($count) : '—'; ?></td>
                        <td><?php echo $next; // already escaped or em ?></td>
                        <td style="font-size:11px;"><?php echo esc_html($watermark); ?></td>
                        <td>
                            <button type="button" class="button button-secondary af-fire-btn" data-hook="<?php echo esc_attr($s['hook']); ?>">
                                Fire now
                            </button>
                            <?php if ($watermark_key): ?>
                                <button type="button" class="button button-link af-reset-watermark" data-key="<?php echo esc_attr($watermark_key); ?>" style="font-size:11px;">
                                    reset
                                </button>
                            <?php endif; ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>

            <h2 style="margin-top:30px;">Utilities</h2>
            <p>
                <button type="button" class="button" id="af-test-connection">Test wpdeploy connection</button>
                <button type="button" class="button" id="af-clear-cron-lock">Clear stuck cron lock</button>
                <button type="button" class="button" id="af-reinstall-keepalive">Reinstall keepalive</button>
                <button type="button" class="button" id="af-backfill-bids">Backfill bids from legacy tracking</button>
                <button type="button" class="button button-primary" id="af-run-all-due">Run all due events now</button>
            </p>
            <p style="color:#666;font-size:12px;">
                <strong>Test connection</strong> — calls <code>/api.php/info</code> with this site's API key.<br>
                <strong>Clear cron lock</strong> — deletes the <code>doing_cron</code> transient if it's stuck.<br>
                <strong>Reinstall keepalive</strong> — overwrites <code>wp-content/mu-plugins/auctionforge-keepalive.php</code> from the bundled template.<br>
                <strong>Backfill bids</strong> — replays <code>last_event_type='bid'</code> rows from <code>wp_auctionforge_user_tracking</code> into <code>wp_af_bid_events</code> so the bids-wins stream picks them up.<br>
                <strong>Run all due events</strong> — clears the cron lock and fires every <code>auctionforge_*</code> event whose timestamp is past due.
            </p>

            <h2 style="margin-top:30px;">Output</h2>
            <pre id="af-out" style="background:#fff;border:1px solid #ddd;padding:10px;min-height:80px;font-size:12px;white-space:pre-wrap;word-break:break-all;">Ready.</pre>
        </div>

        <script>
        (function(){
            const NONCE = <?php echo wp_json_encode($nonce); ?>;
            const out = document.getElementById('af-out');
            const log = (msg) => { out.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg; };

            const ajax = (action, data, label) => {
                log(label + '… (action=' + action + ')');
                const fd = new FormData();
                fd.append('action', action);
                fd.append('_ajax_nonce', NONCE);
                Object.entries(data || {}).forEach(([k, v]) => fd.append(k, v));
                return fetch(ajaxurl, { method: 'POST', credentials: 'same-origin', body: fd })
                    .then(r => r.json())
                    .then(d => {
                        log(label + ' →\n' + JSON.stringify(d, null, 2));
                        return d;
                    })
                    .catch(e => { log(label + ' ERR: ' + e.message); });
            };

            document.querySelectorAll('.af-fire-btn').forEach(b => {
                b.addEventListener('click', () => {
                    const hook = b.getAttribute('data-hook');
                    ajax('af_sync_status_fire', { hook }, 'Fire ' + hook).then(() => setTimeout(() => location.reload(), 1500));
                });
            });
            document.querySelectorAll('.af-reset-watermark').forEach(b => {
                b.addEventListener('click', () => {
                    if (!confirm('Reset watermark? Next sync will re-walk from the start.')) return;
                    const key = b.getAttribute('data-key');
                    ajax('af_sync_status_reset_watermark', { key }, 'Reset ' + key).then(() => setTimeout(() => location.reload(), 1500));
                });
            });
            document.getElementById('af-test-connection').onclick = () => ajax('af_sync_status_test_connection', {}, 'Test connection');
            document.getElementById('af-clear-cron-lock').onclick = () => ajax('af_sync_status_clear_cron_lock', {}, 'Clear cron lock');
            document.getElementById('af-reinstall-keepalive').onclick = () => ajax('af_sync_status_reinstall_keepalive', {}, 'Reinstall keepalive');
            document.getElementById('af-backfill-bids').onclick = () => {
                if (!confirm('Replay all bid events from legacy user_tracking into the new bid_events queue?')) return;
                ajax('af_sync_status_backfill_bids', {}, 'Backfill bids');
            };
            document.getElementById('af-run-all-due').onclick = () => ajax('af_sync_status_run_all_due', {}, 'Run all due events').then(() => setTimeout(() => location.reload(), 2000));
        })();
        </script>

        <style>
            .widefat th, .widefat td { padding: 8px 10px; }
            .af-fire-btn { font-size: 11px; padding: 0 8px; min-height: 24px; line-height: 22px; }
            .af-reset-watermark { padding: 0 4px; }
        </style>
        <?php
    }

    // ---- AJAX handlers ----

    public static function ajax_fire() {
        self::check();
        $hook = sanitize_text_field($_POST['hook'] ?? '');
        if (strpos($hook, 'auctionforge_') !== 0) {
            wp_send_json_error(['message' => 'Invalid hook']);
        }
        if (!has_action($hook)) {
            wp_send_json_error(['message' => "No listener registered for $hook"]);
        }
        delete_transient('doing_cron');
        do_action($hook);
        $opt = self::option_root_for_hook($hook);
        $resp = ['fired' => $hook];
        if ($opt) {
            $resp['status'] = get_option($opt . '_status');
            $resp['count']  = get_option($opt . '_count');
            $resp['at']     = get_option($opt . '_at');
            $resp['error']  = get_option($opt . '_error');
        }
        wp_send_json_success($resp);
    }

    public static function ajax_reset_watermark() {
        self::check();
        $key = sanitize_text_field($_POST['key'] ?? '');
        if (!preg_match('/^auctionforge_[a-z_-]+_(sync_)?watermark$/', $key)) {
            wp_send_json_error(['message' => 'Invalid key']);
        }
        delete_option($key);
        wp_send_json_success(['cleared' => $key]);
    }

    public static function ajax_test_connection() {
        self::check();
        $url = rtrim(get_option('auctionforge_update_server_url'), '/') . '/api.php/info';
        $resp = wp_remote_get($url, array(
            'timeout' => 10,
            'headers' => array('X-API-Key' => get_option('auctionforge_update_api_key')),
        ));
        if (is_wp_error($resp)) {
            wp_send_json_error(['message' => $resp->get_error_message()]);
        }
        wp_send_json_success(array(
            'http' => wp_remote_retrieve_response_code($resp),
            'body' => json_decode(wp_remote_retrieve_body($resp), true),
        ));
    }

    public static function ajax_clear_cron_lock() {
        self::check();
        delete_transient('doing_cron');
        delete_option('_transient_doing_cron');
        delete_option('_transient_timeout_doing_cron');
        wp_send_json_success(['cleared' => 'doing_cron']);
    }

    public static function ajax_reinstall_keepalive() {
        self::check();
        $installer = dirname(__DIR__) . '/keepalive/class-keepalive-installer.php';
        if (!file_exists($installer)) {
            wp_send_json_error(['message' => 'Installer file missing']);
        }
        require_once $installer;
        delete_option('auctionforge_keepalive_installed_version'); // force re-write
        $ok = AF_Keepalive_Installer::maybe_install();
        wp_send_json_success(array(
            'installed' => $ok,
            'version' => get_option('auctionforge_keepalive_installed_version'),
            'at' => get_option('auctionforge_keepalive_installed_at'),
            'error' => get_option('auctionforge_keepalive_install_error'),
        ));
    }

    public static function ajax_backfill_bids() {
        self::check();
        global $wpdb;
        $tracking = $wpdb->prefix . 'auctionforge_user_tracking';
        $events   = $wpdb->prefix . 'af_bid_events';

        if ($wpdb->get_var("SHOW TABLES LIKE '$tracking'") !== $tracking) {
            wp_send_json_error(['message' => 'No legacy user_tracking table on this site']);
        }
        if ($wpdb->get_var("SHOW TABLES LIKE '$events'") !== $events) {
            // Try to create it via the bids-sync class
            if (class_exists('AF_Bids_Sync') && method_exists('AF_Bids_Sync', 'ensure_table')) {
                AF_Bids_Sync::ensure_table();
            } else {
                wp_send_json_error(['message' => 'AF_Bids_Sync not loaded']);
            }
        }

        $rows = $wpdb->get_results(
            "SELECT email, last_seen FROM $tracking
              WHERE last_event_type = 'bid' AND email <> ''
              ORDER BY last_seen ASC LIMIT 5000"
        );
        $inserted = 0;
        foreach ($rows as $r) {
            // We don't have lot_post_id from legacy data — leave NULL.
            $r2 = $wpdb->query($wpdb->prepare(
                "INSERT IGNORE INTO $events (user_email, lot_post_id, bid_amount, currency, is_winning, occurred_at)
                 VALUES (%s, 0, NULL, 'GBP', 0, %s)",
                strtolower(trim($r->email)),
                $r->last_seen
            ));
            if ($r2) $inserted++;
        }
        wp_send_json_success(array(
            'scanned' => count($rows),
            'inserted_into_queue' => $inserted,
            'next_step' => 'Run "Bids + wins" sync to ship them to wpdeploy',
        ));
    }

    public static function ajax_run_all_due() {
        self::check();
        delete_transient('doing_cron');
        $crons = _get_cron_array() ?: array();
        $fired = array();
        $now = time();
        foreach ($crons as $ts => $hooks) {
            if ($ts > $now) continue;
            foreach ($hooks as $hook => $events) {
                if (strpos($hook, 'auctionforge_') !== 0) continue;
                foreach ($events as $sig => $event) {
                    do_action_ref_array($hook, $event['args'] ?? array());
                    wp_unschedule_event($ts, $hook, $event['args'] ?? array());
                    if (!empty($event['schedule'])) {
                        wp_schedule_event($now + ($event['interval'] ?? 3600), $event['schedule'], $hook, $event['args'] ?? array());
                    }
                    $fired[] = $hook;
                }
            }
        }
        wp_send_json_success(array('fired' => array_values(array_unique($fired)), 'count' => count($fired)));
    }

    // ---- helpers ----

    private static function check() {
        if (!current_user_can('manage_options')) wp_send_json_error(['message' => 'Unauthorized'], 403);
        check_ajax_referer(self::NONCE);
    }

    private static function option_root_for_hook($hook) {
        switch ($hook) {
            case 'auctionforge_sync_user_tracking':   return 'auctionforge_last_tracking_sync';
            case 'auctionforge_sync_user_profile':    return 'auctionforge_last_sync-user-profile';
            case 'auctionforge_sync_user_interests':  return 'auctionforge_last_sync-user-interests';
            case 'auctionforge_sync_user_activity':   return 'auctionforge_last_sync-user-activity';
            case 'auctionforge_sync_lots':            return 'auctionforge_last_sync-lots';
            case 'auctionforge_sync_bids_wins':       return 'auctionforge_last_sync-bids-wins';
            case 'auctionforge_sync_vulnerabilities': return 'auctionforge_last_vuln_sync';
        }
        return null;
    }

    private static function watermark_key_for($hook) {
        switch ($hook) {
            case 'auctionforge_sync_user_tracking':   return 'auctionforge_last_tracking_sync';
            case 'auctionforge_sync_user_profile':    return 'auctionforge_profile_sync_watermark';
            case 'auctionforge_sync_user_interests':  return 'auctionforge_interests_sync_watermark';
            case 'auctionforge_sync_lots':            return 'auctionforge_lots_sync_watermark';
        }
        return null;
    }

    private static function status_color($status) {
        if ($status === 'success') return '#1abc46';
        if ($status === 'idle' || $status === 'no_new_records' || $status === 'no_vulnerabilities_found') return '#3498db';
        if ($status === 'api_error' || strpos((string) $status, 'error') !== false) return '#d63638';
        return '#999';
    }
}
