<?php
/**
 * DBA_SEO_Sniper_Rank_Tracker — Weekly position monitoring
 *
 * Runs once per week via WP-Cron:
 *   1. Loops through all saved Sniper keywords
 *   2. Scrapes Google page 1+2 for each
 *   3. Finds damalion.com position (or "not ranked")
 *   4. Stores history: week → position → URL
 *   5. Dashboard shows trend arrows, sparklines, and position changes
 *
 * Rate-limiting: 3-5 second delay between keywords.
 * Max 20 keywords per run (safety cap).
 *
 * @since 2.16.0
 */
if ( ! defined( 'ABSPATH' ) ) exit;

class DBA_SEO_Sniper_Rank_Tracker {

    const CRON_HOOK   = 'dba_sniper_weekly_rank_check';
    const HISTORY_KEY = 'dba_sniper_rank_history';
    const MAX_HISTORY = 52; // 1 year of weekly snapshots per keyword
    const MAX_PER_RUN = 20;
    const OUR_DOMAIN  = 'damalion.com';

    private static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) self::$instance = new self();
        return self::$instance;
    }

    private function __construct() {
        add_action( self::CRON_HOOK, array( $this, 'run_weekly' ) );
        add_action( 'wp_ajax_dba_sniper_check_rank',     array( $this, 'ajax_check_single' ) );
        add_action( 'wp_ajax_dba_sniper_check_rank_all', array( $this, 'ajax_check_all' ) );
    }

    /* ══════════════════════════════════════════════════════════════
     *  CRON SCHEDULING
     * ══════════════════════════════════════════════════════════════ */

    public static function schedule() {
        // Register "weekly" interval if WP doesn't have it
        add_filter( 'cron_schedules', function( $s ) {
            if ( ! isset( $s['weekly'] ) ) {
                $s['weekly'] = array( 'interval' => 604800, 'display' => 'Once Weekly' );
            }
            return $s;
        } );

        if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
            $next = strtotime( 'next monday 06:00:00 UTC' );
            wp_schedule_event( $next, 'weekly', self::CRON_HOOK );
            error_log( 'DBA Sniper Rank Tracker: Scheduled weekly — next run ' . date( 'Y-m-d H:i', $next ) . ' UTC' );
        }
    }

    public static function unschedule() {
        wp_clear_scheduled_hook( self::CRON_HOOK );
    }

    /* ══════════════════════════════════════════════════════════════
     *  WEEKLY CRON RUN — check all tracked keywords
     * ══════════════════════════════════════════════════════════════ */

    public function run_weekly() {
        $keywords = get_option( 'dba_sniper_keywords', array() );
        if ( empty( $keywords ) ) {
            error_log( 'DBA Sniper Rank Tracker: No keywords to check.' );
            return;
        }

        $count = 0;
        error_log( 'DBA Sniper Rank Tracker: ═══ Weekly run — ' . count( $keywords ) . ' keywords ═══' );

        foreach ( $keywords as $kw_key => $data ) {
            if ( $count >= self::MAX_PER_RUN ) {
                error_log( 'DBA Sniper Rank Tracker: Hit cap of ' . self::MAX_PER_RUN . '. Remaining next week.' );
                break;
            }

            $kw   = $data['keyword'] ?? '';
            $lang = $data['language'] ?? 'en';
            if ( empty( $kw ) ) continue;

            $result = $this->check_keyword( $kw, $lang );

            if ( is_wp_error( $result ) ) {
                error_log( 'DBA Sniper Rank: "' . $kw . '" ERROR — ' . $result->get_error_message() );
            } else {
                $pos = $result['position'] > 0 ? '#' . $result['position'] : 'NOT FOUND';
                $arrow = $result['trend']['arrow'] ?? '—';
                error_log( 'DBA Sniper Rank: "' . $kw . '" (' . $lang . ') → ' . $pos . ' ' . $arrow );
            }

            $count++;

            // 3-5 second delay between keywords
            if ( $count < min( count( $keywords ), self::MAX_PER_RUN ) ) {
                sleep( rand( 3, 5 ) );
            }
        }

        // Store last run timestamp
        update_option( 'dba_sniper_last_rank_check', current_time( 'mysql' ), false );
        error_log( 'DBA Sniper Rank Tracker: ═══ Done — checked ' . $count . ' keywords ═══' );
    }

    /* ══════════════════════════════════════════════════════════════
     *  AJAX: Check single keyword rank (manual from dashboard)
     * ══════════════════════════════════════════════════════════════ */

    public function ajax_check_single() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );

        $keyword  = sanitize_text_field( wp_unslash( $_POST['keyword'] ?? '' ) );
        $language = sanitize_key( $_POST['language'] ?? 'en' );
        if ( empty( $keyword ) ) wp_send_json_error( 'No keyword.' );

        @set_time_limit( 60 );

        $result = $this->check_keyword( $keyword, $language );
        if ( is_wp_error( $result ) ) wp_send_json_error( $result->get_error_message() );

        wp_send_json_success( $result );
    }

    /* ══════════════════════════════════════════════════════════════
     *  AJAX: Check ALL keywords at once (manual "Check All" button)
     * ══════════════════════════════════════════════════════════════ */

    public function ajax_check_all() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );

        @set_time_limit( 300 );

        $keywords = get_option( 'dba_sniper_keywords', array() );
        if ( empty( $keywords ) ) wp_send_json_error( 'No keywords tracked.' );

        $results = array();
        $count   = 0;

        foreach ( $keywords as $kw_key => $data ) {
            if ( $count >= self::MAX_PER_RUN ) break;

            $kw   = $data['keyword'] ?? '';
            $lang = $data['language'] ?? 'en';
            if ( empty( $kw ) ) continue;

            $r = $this->check_keyword( $kw, $lang );
            $results[ $kw_key ] = is_wp_error( $r )
                ? array( 'error' => $r->get_error_message(), 'keyword' => $kw, 'language' => $lang )
                : $r;

            $count++;
            if ( $count < min( count( $keywords ), self::MAX_PER_RUN ) ) {
                sleep( rand( 3, 5 ) );
            }
        }

        update_option( 'dba_sniper_last_rank_check', current_time( 'mysql' ), false );
        wp_send_json_success( array( 'checked' => $count, 'results' => $results ) );
    }

    /* ══════════════════════════════════════════════════════════════
     *  CORE: Check rank for a single keyword
     * ══════════════════════════════════════════════════════════════ */

    public function check_keyword( $keyword, $language ) {
        $serp_data = DBA_SEO_Sniper_Crawler::crawl_serp( $keyword, $language );
        if ( is_wp_error( $serp_data ) ) return $serp_data;
        $serp = $serp_data['organic'] ?? $serp_data; // backward compat

        // Find damalion.com in results
        $our_position = 0;
        $our_url      = '';

        foreach ( $serp as $r ) {
            if ( stripos( $r['domain'] ?? '', self::OUR_DOMAIN ) !== false ) {
                $our_position = $r['position'];
                $our_url      = $r['url'] ?? '';
                break;
            }
        }

        $today  = date( 'Y-m-d' );
        $kw_key = sanitize_key( $keyword . '_' . $language );

        // Store in history
        $this->store_history( $kw_key, array(
            'date'     => $today,
            'position' => $our_position,
            'url'      => $our_url,
        ) );

        // Update keyword entry
        $keywords = get_option( 'dba_sniper_keywords', array() );
        if ( isset( $keywords[ $kw_key ] ) ) {
            $keywords[ $kw_key ]['last_rank']       = $our_position;
            $keywords[ $kw_key ]['last_rank_url']    = $our_url;
            $keywords[ $kw_key ]['last_rank_check']  = current_time( 'mysql' );
            update_option( 'dba_sniper_keywords', $keywords, false );
        }

        $trend = self::compute_trend( $kw_key );

        return array(
            'keyword'  => $keyword,
            'language' => $language,
            'position' => $our_position,
            'url'      => $our_url,
            'date'     => $today,
            'trend'    => $trend,
        );
    }

    /* ══════════════════════════════════════════════════════════════
     *  HISTORY STORAGE
     * ══════════════════════════════════════════════════════════════ */

    private function store_history( $kw_key, $entry ) {
        $all = get_option( self::HISTORY_KEY, array() );
        if ( ! isset( $all[ $kw_key ] ) ) $all[ $kw_key ] = array();

        // Remove duplicate for same date
        $all[ $kw_key ] = array_values( array_filter( $all[ $kw_key ], function( $h ) use ( $entry ) {
            return ( $h['date'] ?? '' ) !== $entry['date'];
        } ) );

        $all[ $kw_key ][] = $entry;

        // Trim old entries
        if ( count( $all[ $kw_key ] ) > self::MAX_HISTORY ) {
            $all[ $kw_key ] = array_slice( $all[ $kw_key ], -self::MAX_HISTORY );
        }

        update_option( self::HISTORY_KEY, $all, false );
    }

    /**
     * Get raw history for a keyword.
     */
    public static function get_history( $kw_key ) {
        $all = get_option( self::HISTORY_KEY, array() );
        return $all[ $kw_key ] ?? array();
    }

    /* ══════════════════════════════════════════════════════════════
     *  TREND CALCULATION
     * ══════════════════════════════════════════════════════════════ */

    /**
     * Compute trend: direction, change, arrow, color, sparkline data.
     *
     * @param string $kw_key
     * @return array
     */
    public static function compute_trend( $kw_key ) {
        $entries = self::get_history( $kw_key );

        $result = array(
            'direction' => 'new',
            'change'    => 0,
            'arrow'     => '●',
            'color'     => '#888',
            'label'     => 'New',
            'current'   => 0,
            'previous'  => 0,
            'best'      => 0,
            'worst'     => 0,
            'sparkline' => array(),
        );

        if ( empty( $entries ) ) return $result;

        // Extract positions for sparkline (last 12 weeks)
        $recent = array_slice( $entries, -12 );
        $result['sparkline'] = array_map( function( $e ) {
            return $e['position'] ?? 0;
        }, $recent );

        $current = end( $entries )['position'] ?? 0;
        $result['current'] = $current;

        // Best/worst positions (excluding 0 = not ranked)
        $ranked_positions = array_filter( array_column( $entries, 'position' ), function( $p ) { return $p > 0; } );
        $result['best']  = ! empty( $ranked_positions ) ? min( $ranked_positions ) : 0;
        $result['worst'] = ! empty( $ranked_positions ) ? max( $ranked_positions ) : 0;

        if ( count( $entries ) < 2 ) {
            $result['label'] = $current > 0 ? '#' . $current . ' (first check)' : 'Not ranked';
            return $result;
        }

        $previous = $entries[ count( $entries ) - 2 ]['position'] ?? 0;
        $result['previous'] = $previous;

        // Determine direction (lower position = better rank)
        if ( $current === 0 && $previous === 0 ) {
            $result['direction'] = 'stable';
            $result['arrow']     = '—';
            $result['color']     = '#888';
            $result['label']     = 'Not ranked';
        } elseif ( $current === 0 && $previous > 0 ) {
            $result['direction'] = 'lost';
            $result['arrow']     = '↓↓';
            $result['color']     = '#dc2626';
            $result['label']     = 'Lost (was #' . $previous . ')';
            $result['change']    = 0;
        } elseif ( $current > 0 && $previous === 0 ) {
            $result['direction'] = 'entered';
            $result['arrow']     = '🆕';
            $result['color']     = '#059669';
            $result['label']     = 'Entered at #' . $current;
            $result['change']    = $current;
        } elseif ( $current < $previous ) {
            $change = $previous - $current;
            $result['direction'] = 'up';
            $result['arrow']     = '↑' . ( $change >= 3 ? '↑' : '' );
            $result['color']     = '#059669';
            $result['label']     = '+' . $change . ( $change === 1 ? ' pos' : ' pos' );
            $result['change']    = $change;
        } elseif ( $current > $previous ) {
            $change = $current - $previous;
            $result['direction'] = 'down';
            $result['arrow']     = '↓' . ( $change >= 3 ? '↓' : '' );
            $result['color']     = '#dc2626';
            $result['label']     = '-' . $change . ' pos';
            $result['change']    = -$change;
        } else {
            $result['direction'] = 'stable';
            $result['arrow']     = '→';
            $result['color']     = '#2563eb';
            $result['label']     = 'Stable';
            $result['change']    = 0;
        }

        return $result;
    }

    /**
     * Render HTML badge for keyword table.
     *
     * @param string $kw_key
     * @return string HTML
     */
    public static function render_rank_badge( $kw_key ) {
        $trend   = self::compute_trend( $kw_key );
        $current = $trend['current'];

        // Position number
        if ( $current > 0 ) {
            $bg = $current <= 3 ? '#dcfce7' : ( $current <= 10 ? '#dbeafe' : '#fef2f2' );
            $fg = $current <= 3 ? '#166534' : ( $current <= 10 ? '#1e40af' : '#991b1b' );
            $pos_html = '<span style="background:' . $bg . ';color:' . $fg . ';padding:3px 10px;border-radius:4px;font-weight:bold;font-size:15px">#' . $current . '</span>';
        } else {
            $pos_html = '<span style="color:#999;font-size:13px">Not ranked</span>';
        }

        // Trend arrow
        $trend_html = '<span style="color:' . esc_attr( $trend['color'] ) . ';font-size:13px;margin-left:6px" title="' . esc_attr( $trend['label'] ) . '">'
            . esc_html( $trend['arrow'] ) . ' <small>' . esc_html( $trend['label'] ) . '</small></span>';

        // Mini sparkline (CSS-only bar chart)
        $spark_html = '';
        if ( count( $trend['sparkline'] ) >= 2 ) {
            $spark_html = self::render_sparkline( $trend['sparkline'] );
        }

        return $pos_html . $trend_html . $spark_html;
    }

    /**
     * Render a tiny CSS sparkline of position history.
     */
    private static function render_sparkline( $positions ) {
        if ( empty( $positions ) ) return '';

        // Invert positions for visual (lower position = taller bar = better)
        $max_pos = max( max( $positions ), 20 );
        $html = '<span style="display:inline-flex;align-items:end;gap:1px;height:20px;margin-left:8px;vertical-align:middle" title="Last ' . count( $positions ) . ' checks">';

        foreach ( $positions as $idx => $pos ) {
            if ( $pos === 0 ) {
                // Not ranked = tiny red dot
                $html .= '<span style="width:3px;height:2px;background:#dc2626;border-radius:1px"></span>';
            } else {
                $height = max( 2, round( ( 1 - ( $pos / $max_pos ) ) * 20 ) );
                $color  = $pos <= 3 ? '#059669' : ( $pos <= 10 ? '#2563eb' : '#f59e0b' );
                // Latest bar highlighted
                if ( $idx === count( $positions ) - 1 ) {
                    $color = $pos <= 3 ? '#059669' : ( $pos <= 10 ? '#2563eb' : '#dc2626' );
                }
                $html .= '<span style="width:3px;height:' . $height . 'px;background:' . $color . ';border-radius:1px"></span>';
            }
        }

        $html .= '</span>';
        return $html;
    }

    /**
     * Get next scheduled check time.
     */
    public static function get_next_check() {
        $next = wp_next_scheduled( self::CRON_HOOK );
        if ( ! $next ) return null;
        return date( 'Y-m-d H:i', $next );
    }
}
