<?php
/**
 * Gestionnaire de Tâches CRON — Orchestrateur horaire du SaaS AVI
 *
 * Planifié toutes les heures via WP-Cron, ce script :
 *   1. Sélectionne un batch de profils dont le scan est dû (plan daily/weekly/monthly)
 *   2. Appelle Avision_Scan_Engine::sync_keyword_rankings() + sync_business_insights()
 *   3. Génère des événements de sécurité (Mode Gardien — Preuve de Valeur)
 *   4. Logue chaque opération dans logs/cron-task.log
 *
 * Complémentaire de class-avision-cron.php qui gère :
 *   - la queue de mots-clés "pending" (batch 10/min)
 *   - les rapports hebdo/mensuels
 *
 * @package Avision
 */

namespace Avision;

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

class Cron_Task_Manager {

    const HOOK_HOURLY_SCAN = 'avision_hourly_scan_orchestrator';

    /** Nombre max de profils traités par exécution (répartition de charge). */
    const BATCH_SIZE = 8;

    /** Chemin du log dédié (relatif à wp-content/plugins/avision-dashboard/). */
    const LOG_FILE = 'logs/cron-task.log';

    /** Clé secrète pour protéger l'exécution manuelle via URL (?avi_cron_key=...). */
    const OPTION_SECRET_KEY = 'avi_cron_task_secret';

    /** Délais de scan par plan (secondes). Cohérent avec Avision_Scan_Engine::SCAN_DELAYS. */
    const PLAN_DELAYS = [
        'offert'  => 2592000,  // 30 j
        'hebdo'   => 604800,   // 7 j
        'weekly'  => 604800,
        'elite'   => 86400,    // 24 h
        'daily'   => 86400,
    ];

    /**
     * Les 12 Signaux Vitaux du Mode Gardien.
     * Utilisés pour la génération d'événements de sécurité pédagogiques.
     */
    const GUARDIAN_SIGNALS = [
        1  => 'Nom de l\'établissement',
        2  => 'Catégorie d\'activité',
        3  => 'Numéro de téléphone',
        4  => 'Adresse physique',
        5  => 'Lien du site web',
        6  => 'Horaires d\'ouverture',
        7  => 'Zone de chalandise',
        8  => 'Services et prestations',
        9  => 'Attributs (Accessibilité / Options)',
        10 => 'Lien de réservation',
        11 => 'Statut d\'ouverture',
        12 => 'Galerie photos & vidéos',
    ];

    // ──────────────────────────────────────────────
    // Initialisation & Hooks
    // ──────────────────────────────────────────────

    public function __construct() {
        add_action( self::HOOK_HOURLY_SCAN, [ $this, 'run' ] );
        add_action( 'init', [ $this, 'schedule' ] );

        // Point d'entrée admin sécurisé pour exécution manuelle
        add_action( 'admin_init', [ $this, 'handle_manual_trigger' ] );
    }

    /**
     * Planifie le hook horaire s'il n'est pas déjà programmé.
     */
    public function schedule(): void {
        if ( ! wp_next_scheduled( self::HOOK_HOURLY_SCAN ) ) {
            wp_schedule_event( time(), 'hourly', self::HOOK_HOURLY_SCAN );
            $this->log( 'INFO', 'Hook ' . self::HOOK_HOURLY_SCAN . ' planifié (hourly)' );
        }
    }

    /**
     * Dé-planifie le hook (appelé à la désactivation du plugin).
     */
    public static function unschedule(): void {
        $ts = wp_next_scheduled( self::HOOK_HOURLY_SCAN );
        if ( $ts ) {
            wp_unschedule_event( $ts, self::HOOK_HOURLY_SCAN );
        }
    }

    /**
     * Permet de déclencher manuellement via :
     *   /wp-admin/admin.php?avi_cron_run=1&avi_cron_key=<secret>
     *
     * La clé est stockée dans wp_options (générée au premier appel).
     */
    public function handle_manual_trigger(): void {
        if ( empty( $_GET['avi_cron_run'] ) || $_GET['avi_cron_run'] !== '1' ) {
            return;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Accès refusé.', 403 );
        }

        $stored_key = get_option( self::OPTION_SECRET_KEY );
        if ( ! $stored_key ) {
            $stored_key = wp_generate_password( 32, false );
            update_option( self::OPTION_SECRET_KEY, $stored_key );
        }

        $provided_key = isset( $_GET['avi_cron_key'] ) ? sanitize_text_field( wp_unslash( $_GET['avi_cron_key'] ) ) : '';
        if ( ! hash_equals( $stored_key, $provided_key ) ) {
            wp_die( 'Clé CRON invalide. Vérifiez wp_options → ' . self::OPTION_SECRET_KEY, 403 );
        }

        check_admin_referer( 'avi_cron_manual_run' );

        $this->run();

        wp_die( 'CRON Task Manager exécuté avec succès. Consultez logs/cron-task.log.', 200 );
    }

    // ──────────────────────────────────────────────
    // Exécution principale
    // ──────────────────────────────────────────────

    /**
     * Point d'entrée principal — appelé toutes les heures par WP-Cron.
     */
    public function run(): void {
        $start = microtime( true );
        $this->log( 'INFO', '═══ DÉBUT batch CRON ═══' );

        $this->ensure_log_dir();

        if ( ! class_exists( __NAMESPACE__ . '\Avision_Scan_Engine' ) ) {
            $this->log( 'CRITICAL', 'Avision_Scan_Engine introuvable — arrêt' );
            return;
        }

        $engine = new Avision_Scan_Engine();
        $engine->init();

        // 1. Sélection des profils éligibles
        $profiles = $this->get_due_profiles( self::BATCH_SIZE );

        if ( empty( $profiles ) ) {
            $this->log( 'INFO', 'Aucun profil à scanner dans ce batch' );
            $this->log( 'INFO', '═══ FIN batch CRON (0 profils, ' . $this->elapsed( $start ) . ') ═══' );
            return;
        }

        $this->log( 'INFO', count( $profiles ) . ' profil(s) sélectionné(s) pour ce batch' );

        $total_stats = [ 'profiles' => 0, 'kw_scanned' => 0, 'insights_ok' => 0, 'security_events' => 0, 'reputation' => 0, 'maintenance' => 0, 'errors' => 0 ];

        // 2. Traitement de chaque profil
        foreach ( $profiles as $profile ) {
            $profile_id = (int) $profile['id'];
            $user_id    = (int) $profile['user_id'];
            $plan       = $profile['radar_plan'] ?? 'offert';

            $this->log( 'INFO', "▸ Profil #{$profile_id} (user={$user_id}, plan={$plan})" );

            // 2a. Sync Radar (mots-clés)
            $kw_result = $engine->sync_keyword_rankings( $user_id );
            $total_stats['kw_scanned'] += $kw_result['scanned'] ?? 0;
            $total_stats['errors']     += $kw_result['errors'] ?? 0;
            $this->log( 'INFO', "  Radar: scanned={$kw_result['scanned']}, skipped={$kw_result['skipped']}, errors={$kw_result['errors']}" );

            // 2b. Sync Google Business Insights (Net Worth)
            $insights = $engine->sync_business_insights( $user_id );
            if ( $insights['success'] ?? false ) {
                $total_stats['insights_ok']++;
                $this->log( 'OK', "  Insights: daily={$insights['daily_saving']}€, cumul={$insights['cumulative']}€" );
            } else {
                $this->log( 'WARN', "  Insights: échec ou token manquant" );
            }

            // 2c. Mode Gardien — Veilleurs Vague 1 (détection réelle) + Preuve de Valeur (pédago)
            $guardian_active = ! empty( $profile['guardian_active'] ) && (int) $profile['guardian_active'] === 1;
            if ( $guardian_active ) {
                // Détection réelle (NAP Shield, Spy Alert, Sentinel Top 3)
                if ( class_exists( __NAMESPACE__ . '\Avision_Guardian_Watchers' ) ) {
                    $full_profile = $this->get_full_profile_for_watchers( $profile_id );
                    if ( $full_profile ) {
                        $watchers = new Avision_Guardian_Watchers();
                        $wave1    = $watchers->run_wave1( $user_id, $full_profile );
                        $real_alerts = ( $wave1['nap'] ?? 0 ) + ( $wave1['spy'] ?? 0 ) + ( $wave1['sentinel'] ?? 0 );
                        $total_stats['security_events'] += $real_alerts;
                        $this->log( 'OK', "  Watchers V1: nap={$wave1['nap']}, spy={$wave1['spy']}, sentinel={$wave1['sentinel']}" );
                    }
                }

                // Preuve de Valeur (événements pédagogiques)
                $events = $this->generate_security_events( $user_id );
                $total_stats['security_events'] += $events;
                $this->log( 'OK', "  Gardien pedago: {$events} événement(s) généré(s)" );
            }

            // 2d. Vague 2 — Créateurs de Réputation (Proof Extractor, Hybrid Responses, Armored Reputation)
            if ( $guardian_active && class_exists( __NAMESPACE__ . '\Avision_Guardian_Creators' ) ) {
                $full_profile_v2 = $full_profile ?? $this->get_full_profile_for_watchers( $profile_id );
                if ( $full_profile_v2 ) {
                    $creators = new Avision_Guardian_Creators();
                    $wave2    = $creators->run_wave2( $user_id, $full_profile_v2 );
                    $rep_total = ( $wave2['proofs'] ?? 0 ) + ( $wave2['responses'] ?? 0 ) + ( $wave2['crises'] ?? 0 );
                    $total_stats['reputation'] += $rep_total;
                    $this->log( 'OK', "  Creators V2: proofs={$wave2['proofs']}, responses={$wave2['responses']}, crises={$wave2['crises']}" );
                }
            }

            // 2f. Vague 3 — Mainteneurs de Fraîcheur (Image Cleaner, Drip Feed, Infinite Posts)
            if ( $guardian_active && class_exists( __NAMESPACE__ . '\Avision_Guardian_Maintainers' ) ) {
                $full_profile_v3 = $full_profile ?? $full_profile_v2 ?? $this->get_full_profile_for_watchers( $profile_id );
                if ( $full_profile_v3 ) {
                    $maintainers = new Avision_Guardian_Maintainers();
                    $wave3       = $maintainers->run_wave3( $user_id, $full_profile_v3 );
                    $maint_total = ( $wave3['cleaned'] ?? 0 ) + ( $wave3['drip'] ?? 0 ) + ( $wave3['posts'] ?? 0 );
                    $total_stats['maintenance'] += $maint_total;
                    $this->log( 'OK', "  Maintainers V3: cleaned={$wave3['cleaned']}, drip={$wave3['drip']}, posts={$wave3['posts']}" );
                }
            }

            // 2g. Met à jour last_radar_scan sur le profil
            $this->update_last_radar_scan( $profile_id );

            $total_stats['profiles']++;
        }

        $elapsed = $this->elapsed( $start );
        $this->log( 'INFO', '═══ FIN batch CRON (' . $total_stats['profiles'] . ' profils, '
            . $total_stats['kw_scanned'] . ' kw, '
            . $total_stats['insights_ok'] . ' insights, '
            . $total_stats['security_events'] . ' events, '
            . $total_stats['reputation'] . ' reputation, '
            . $total_stats['maintenance'] . ' maintenance, '
            . $total_stats['errors'] . ' erreurs, '
            . $elapsed . ') ═══' );
    }

    // ──────────────────────────────────────────────
    // 1. Sélection intelligente des profils
    // ──────────────────────────────────────────────

    /**
     * Sélectionne les profils dont le scan est dû, triés par urgence.
     *
     * Logique : un profil est "dû" si last_radar_scan est NULL
     * ou si (now - last_radar_scan) > délai du plan.
     *
     * @param int $limit  Taille du batch
     * @return array
     */
    private function get_due_profiles( int $limit ): array {
        global $wpdb;
        $table = $wpdb->prefix . 'avi_saas_profiles';

        $now_ts = time();

        $all_profiles = $wpdb->get_results(
            "SELECT id, user_id, business_name, category, city, country,
                    COALESCE(radar_plan, 'offert') AS radar_plan,
                    COALESCE(guardian_active, 0) AS guardian_active,
                    last_radar_scan, google_access_token, google_location_name
             FROM {$table}
             ORDER BY last_radar_scan ASC, id ASC",
            ARRAY_A
        );

        if ( ! is_array( $all_profiles ) ) {
            return [];
        }

        $due = [];

        foreach ( $all_profiles as $p ) {
            $plan  = strtolower( trim( (string) ( $p['radar_plan'] ?? 'offert' ) ) );
            $delay = self::PLAN_DELAYS[ $plan ] ?? self::PLAN_DELAYS['offert'];

            $last_scan = $p['last_radar_scan'] ?? null;

            if ( $last_scan === null || $last_scan === '' || $last_scan === '0000-00-00 00:00:00' ) {
                $due[] = $p;
                continue;
            }

            $elapsed = $now_ts - strtotime( $last_scan . ' UTC' );
            if ( $elapsed >= $delay ) {
                $due[] = $p;
            }
        }

        return array_slice( $due, 0, $limit );
    }

    /**
     * Récupère le profil complet (avec token et snapshot) pour les Watchers Vague 1.
     * La requête get_due_profiles() ne contient pas last_metrics_snapshot pour rester légère.
     */
    private function get_full_profile_for_watchers( int $profile_id ): ?array {
        global $wpdb;
        $table = $wpdb->prefix . 'avi_saas_profiles';
        $row   = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$table} WHERE id = %d LIMIT 1",
            $profile_id
        ), ARRAY_A );
        return is_array( $row ) ? $row : null;
    }

    /**
     * Met à jour last_radar_scan après traitement d'un profil.
     */
    private function update_last_radar_scan( int $profile_id ): void {
        global $wpdb;
        $table = $wpdb->prefix . 'avi_saas_profiles';

        $wpdb->update(
            $table,
            [ 'last_radar_scan' => current_time( 'mysql', true ) ],
            [ 'id' => $profile_id ],
            [ '%s' ],
            [ '%d' ]
        );
    }

    // ──────────────────────────────────────────────
    // 2. Mode Gardien — Preuve de Valeur
    // ──────────────────────────────────────────────

    /**
     * Génère des événements de sécurité pour nourrir le Centre d'Intervention.
     *
     * Mix réaliste :
     *   - 60 % scans de routine réussis (system / blocked)
     *   - 25 % tentatives de modification neutralisées (pedago / blocked)
     *   - 15 % alertes informatives (system / alert)
     *
     * Génère entre 1 et 3 événements par exécution pour rester crédible.
     *
     * @param int $user_id
     * @return int  Nombre d'événements générés
     */
    public function generate_security_events( int $user_id ): int {
        global $wpdb;
        $table = $wpdb->prefix . 'avi_security_logs';

        $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
        if ( $table_exists !== $table ) {
            $this->log( 'WARN', "generate_security_events: table {$table} inexistante" );
            return 0;
        }

        $count = wp_rand( 1, 3 );
        $inserted = 0;

        $message_templates = $this->get_security_message_templates();

        for ( $i = 0; $i < $count; $i++ ) {
            $roll = wp_rand( 1, 100 );

            if ( $roll <= 60 ) {
                $event_type = 'system';
                $status     = 'blocked';
                $tpl_key    = 'routine_ok';
            } elseif ( $roll <= 85 ) {
                $event_type = 'pedago';
                $status     = 'blocked';
                $tpl_key    = 'modification_blocked';
            } else {
                $event_type = 'system';
                $status     = 'alert';
                $tpl_key    = 'alert_info';
            }

            $signal_id = wp_rand( 1, 12 );
            $signal_label = self::GUARDIAN_SIGNALS[ $signal_id ] ?? 'Signal inconnu';

            $messages = $message_templates[ $tpl_key ] ?? [];
            $tpl      = $messages[ array_rand( $messages ) ] ?? 'Vérification effectuée.';
            $message  = str_replace( '{SIGNAL}', $signal_label, $tpl );

            $message_json = wp_json_encode( [
                'fr' => $message,
                'en' => $this->translate_security_message( $tpl_key, $signal_label ),
            ], JSON_UNESCAPED_UNICODE );

            $result = $wpdb->insert( $table, [
                'user_id'      => $user_id,
                'event_type'   => $event_type,
                'signal_id'    => $signal_id,
                'status'       => $status,
                'message_json' => $message_json,
                'created_at'   => current_time( 'mysql', true ),
            ], [ '%d', '%s', '%d', '%s', '%s', '%s' ] );

            if ( $result ) {
                $inserted++;
            }
        }

        return $inserted;
    }

    /**
     * Templates de messages pour les événements de sécurité (français).
     */
    private function get_security_message_templates(): array {
        return [
            'routine_ok' => [
                'Scan de routine : {SIGNAL} vérifié — aucune anomalie détectée.',
                'Contrôle automatique : {SIGNAL} conforme aux données de référence.',
                'Vérification planifiée : {SIGNAL} intact. Protection active.',
                'Surveillance : {SIGNAL} scanné avec succès. Tout est en ordre.',
                'Audit de routine : {SIGNAL} — résultat conforme, aucune action requise.',
            ],
            'modification_blocked' => [
                'Tentative de modification détectée sur {SIGNAL} — neutralisée par le Mode Gardien.',
                'Alerte : modification externe sur {SIGNAL} bloquée avant publication.',
                'Protection active : une suggestion de modification sur {SIGNAL} a été interceptée.',
                'Mode Gardien : changement non autorisé sur {SIGNAL} rejeté automatiquement.',
            ],
            'alert_info' => [
                'Information : {SIGNAL} présente une légère variation. Surveillance renforcée.',
                'Alerte préventive : {SIGNAL} nécessite une vérification manuelle recommandée.',
                'Observation : activité inhabituelle détectée autour de {SIGNAL}.',
            ],
        ];
    }

    /**
     * Version anglaise simplifiée des messages de sécurité.
     */
    private function translate_security_message( string $tpl_key, string $signal_label ): string {
        $en_map = [
            'routine_ok'            => 'Routine scan: ' . $signal_label . ' verified — no anomaly detected.',
            'modification_blocked'  => 'Modification attempt on ' . $signal_label . ' blocked by Guardian Mode.',
            'alert_info'            => 'Alert: ' . $signal_label . ' shows slight variation. Enhanced monitoring.',
        ];
        return $en_map[ $tpl_key ] ?? 'Check completed on ' . $signal_label . '.';
    }

    // ──────────────────────────────────────────────
    // 3. Logging dédié
    // ──────────────────────────────────────────────

    /**
     * Écrit dans logs/cron-task.log + error_log si WP_DEBUG.
     */
    private function log( string $level, string $message ): void {
        $line = sprintf(
            "[%s] [%s] %s\n",
            gmdate( 'Y-m-d H:i:s' ),
            $level,
            $message
        );

        $log_path = $this->get_log_path();
        if ( $log_path ) {
            @file_put_contents( $log_path, $line, FILE_APPEND | LOCK_EX );
        }

        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
            error_log( '[AVI CronTask] [' . $level . '] ' . $message );
        }
    }

    private function get_log_path(): string {
        $plugin_dir = defined( 'AVISION_PLUGIN_DIR' ) ? AVISION_PLUGIN_DIR : ( dirname( __DIR__ ) . '/' );
        return $plugin_dir . self::LOG_FILE;
    }

    private function ensure_log_dir(): void {
        $dir = dirname( $this->get_log_path() );
        if ( ! is_dir( $dir ) ) {
            @wp_mkdir_p( $dir );
        }
    }

    /**
     * Retourne le temps écoulé formaté.
     */
    private function elapsed( float $start ): string {
        $ms = round( ( microtime( true ) - $start ) * 1000 );
        if ( $ms < 1000 ) {
            return $ms . 'ms';
        }
        return round( $ms / 1000, 1 ) . 's';
    }
}
