<?php
/**
 * Sera Bot Blocker - Pro Features
 *
 * Loaded only when SeraLicense::is_active() returns true.
 * Provides:
 *   - Pro bot registry merge (scanner / scraper / spam)
 *   - Fake trusted bot auto-escalation
 *   - Country-level blocking
 *   - Email digest (daily / weekly)
 *   - CSV / JSON log export
 *   - Custom 403 block page
 *
 * @package SeraBotBlocker
 */

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

class BotBlocker_Pro {

    /** @var bool Whether Pro is already initialised */
    private static bool $initialised = false;

    /**
     * Boot all Pro features. Called from the main plugin bootstrap
     * after license verification passes.
     */
    public static function init(): void {
        if ( self::$initialised ) {
            return;
        }
        self::$initialised = true;

        // Merge Pro registry entries into the detector
        add_filter( 'bot_blocker_registry', [ __CLASS__, 'merge_pro_registry' ] );

        // Fake trusted bot auto-escalation
        add_action( 'bot_blocker_verification_failed', [ __CLASS__, 'escalate_fake_trusted_bot' ], 10, 2 );

        // Country blocking - runs early in the engine pipeline
        add_action( 'bot_blocker_pre_enforce', [ __CLASS__, 'maybe_block_by_country' ], 5, 1 );

        // Email digest scheduler
        add_action( 'init', [ __CLASS__, 'schedule_digest' ] );
        add_action( 'bot_blocker_send_digest', [ __CLASS__, 'send_digest' ] );

        // Admin: export endpoint + settings UI additions
        add_action( 'admin_post_bot_blocker_export_csv',  [ __CLASS__, 'handle_export_csv' ] );
        add_action( 'admin_post_bot_blocker_export_json', [ __CLASS__, 'handle_export_json' ] );

        // REST endpoint for Pro settings
        add_action( 'rest_api_init', [ __CLASS__, 'register_rest_routes' ] );

        // Custom block page output hook
        add_filter( 'bot_blocker_block_response', [ __CLASS__, 'custom_block_page' ], 10, 2 );

        // Extend admin settings page with Pro fields
        add_action( 'bot_blocker_settings_pro_section', [ __CLASS__, 'render_pro_settings' ] );
    }

    /* ------------------------------------------------------------------
     * PRO REGISTRY MERGE
     * ------------------------------------------------------------------ */

    /**
     * Merge Pro registry entries into the main registry array.
     *
     * @param array $registry Existing registry entries.
     * @return array Merged registry.
     */
    public static function merge_pro_registry( array $registry ): array {
        $pro_file = plugin_dir_path( __DIR__ ) . 'data/bot-registry-pro.php';
        if ( file_exists( $pro_file ) ) {
            $pro_entries = require $pro_file;
            if ( is_array( $pro_entries ) ) {
                $registry = array_merge( $registry, $pro_entries );
            }
        }
        return $registry;
    }

    /* ------------------------------------------------------------------
     * FAKE TRUSTED BOT AUTO-ESCALATION
     * ------------------------------------------------------------------ */

    /**
     * When DNS verification fails for a bot claiming to be a trusted crawler
     * (e.g., Googlebot, Bingbot), automatically escalate to temporary_block.
     *
     * @param string $bot_name  The claimed bot name.
     * @param string $ip        The source IP address.
     */
    public static function escalate_fake_trusted_bot( string $bot_name, string $ip ): void {
        $settings = BotBlocker_Settings::get();
        if ( empty( $settings['pro_auto_escalate_fake_bots'] ) ) {
            return;
        }
        // Lock the IP for 60 minutes (set_lock third arg is minutes)
        BotBlocker_Storage::set_lock( $ip, 'fake_trusted_bot_escalation', 60 );
        // Log the escalation event
        BotBlocker_Storage::insert_event( [
            'ip_hash'            => BotBlocker_Storage::hash_ip( $ip ),
            'bot_name'           => $bot_name,
            'category'           => 'fake_trusted_bots',
            'action_taken'       => 'temporary_block',
            'verification_state' => 'failed',
            'path'               => isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '',
            'occurred_at'        => gmdate( 'Y-m-d H:i:s' ),
        ] );
    }

    /* ------------------------------------------------------------------
     * COUNTRY BLOCKING
     * ------------------------------------------------------------------ */

    /**
     * Block or rate-limit requests from countries on the Pro block list.
     * Uses geo data already resolved by BotBlocker_Geo.
     *
     * @param array $context  Request context array passed by the engine.
     */
    public static function maybe_block_by_country( array $context ): void {
        $settings = BotBlocker_Settings::get();
        $blocked_countries = $settings['pro_blocked_countries'] ?? [];
        if ( empty( $blocked_countries ) || empty( $context['geo']['country'] ) ) {
            return;
        }
        $country = strtoupper( trim( $context['geo']['country'] ) );
        if ( ! in_array( $country, array_map( 'strtoupper', $blocked_countries ), true ) ) {
            return;
        }
        $action = $settings['pro_country_block_action'] ?? 'block';
        if ( 'rate_limit' === $action ) {
            // set_lock third arg is minutes, so pass 5
            BotBlocker_Storage::set_lock( $context['ip'], 'country_rate_limit', 5 );
        } else {
            do_action( 'bot_blocker_block_response', 'country_block', $context );
            self::send_block_response( $context );
        }
    }

    /**
     * Terminate the request with a 403 response.
     *
     * @param array $context Request context.
     */
    private static function send_block_response( array $context ): void {
        $settings    = BotBlocker_Settings::get();
        $custom_html = trim( $settings['pro_custom_block_html'] ?? '' );
        if ( $custom_html ) {
            status_header( 403 );
            nocache_headers();
            echo wp_kses_post( $custom_html );
            exit;
        }
        status_header( 403 );
        nocache_headers();
        wp_die(
            esc_html__( 'Access denied.', 'sera-bot-blocker' ),
            esc_html__( 'Forbidden', 'sera-bot-blocker' ),
            [ 'response' => 403 ]
        );
    }

    /* ------------------------------------------------------------------
     * CUSTOM BLOCK PAGE
     * ------------------------------------------------------------------ */

    /**
     * Filter hook: replace the default wp_die block page with custom HTML.
     *
     * @param mixed  $response Existing response (unused).
     * @param string $reason   Block reason string.
     * @return mixed
     */
    public static function custom_block_page( $response, string $reason ) {
        $settings    = BotBlocker_Settings::get();
        $custom_html = trim( $settings['pro_custom_block_html'] ?? '' );
        if ( ! $custom_html ) {
            return $response;
        }
        status_header( 403 );
        nocache_headers();
        echo wp_kses_post( str_replace( '{{reason}}', esc_html( $reason ), $custom_html ) );
        exit;
    }

    /* ------------------------------------------------------------------
     * EMAIL DIGEST
     * ------------------------------------------------------------------ */

    /**
     * Register the 'weekly' WP-Cron recurrence if it is not already present,
     * then schedule or clear the digest event based on settings.
     */
    public static function schedule_digest(): void {
        // Register 'weekly' recurrence if missing (not a WP built-in)
        add_filter( 'cron_schedules', static function ( $schedules ) {
            if ( ! isset( $schedules['weekly'] ) ) {
                $schedules['weekly'] = [
                    'interval' => WEEK_IN_SECONDS,
                    'display'  => __( 'Once Weekly', 'sera-bot-blocker' ),
                ];
            }
            return $schedules;
        } );

        $settings  = BotBlocker_Settings::get();
        $frequency = $settings['pro_digest_frequency'] ?? 'disabled';
        $hook      = 'bot_blocker_send_digest';

        if ( 'disabled' === $frequency ) {
            if ( wp_next_scheduled( $hook ) ) {
                wp_clear_scheduled_hook( $hook );
            }
            return;
        }

        $recurrence = ( 'weekly' === $frequency ) ? 'weekly' : 'daily';
        if ( ! wp_next_scheduled( $hook ) ) {
            wp_schedule_event( time(), $recurrence, $hook );
        }
    }

    /**
     * Build and send the digest email to the admin.
     * Uses get_recent_events() which returns OBJECT rows.
     */
    public static function send_digest(): void {
        $settings  = BotBlocker_Settings::get();
        $frequency = $settings['pro_digest_frequency'] ?? 'disabled';
        if ( 'disabled' === $frequency ) {
            return;
        }

        $period_minutes = ( 'weekly' === $frequency ) ? ( 7 * 24 * 60 ) : ( 24 * 60 );
        $rows           = BotBlocker_Storage::get_recent_events( $period_minutes, 500 );

        if ( empty( $rows ) ) {
            return;
        }

        $total   = count( $rows );
        $blocked = count( array_filter( $rows, static function ( $r ) {
            $action = is_array( $r ) ? ( $r['action_taken'] ?? '' ) : ( $r->action_taken ?? '' );
            return in_array( $action, [ 'block', 'temporary_block' ], true );
        } ) );

        $by_cat = [];
        foreach ( $rows as $row ) {
            $cat = is_array( $row ) ? ( $row['category'] ?? 'unknown' ) : ( $row->category ?? 'unknown' );
            $by_cat[ $cat ] = ( $by_cat[ $cat ] ?? 0 ) + 1;
        }
        arsort( $by_cat );

        $period_label = ( 'weekly' === $frequency ) ? 'week' : '24 hours';
        $site_name    = get_bloginfo( 'name' );
        $site_url     = get_site_url();

        $subject = sprintf(
            '[%s] Bot Blocker Digest - %d events in the last %s',
            esc_html( $site_name ),
            $total,
            $period_label
        );

        $body  = "<h2>Bot Blocker Digest</h2>\n";
        $body .= '<p>Here is a summary of bot activity on <a href="' . esc_url( $site_url ) . '">' . esc_html( $site_name ) . '</a> over the last ' . esc_html( $period_label ) . ".</p>\n";
        $body .= "<table cellpadding='6' cellspacing='0' border='1' style='border-collapse:collapse;font-family:sans-serif;font-size:14px'>\n";
        $body .= "<tr><th>Metric</th><th>Value</th></tr>\n";
        $body .= "<tr><td>Total events</td><td>" . (int) $total . "</td></tr>\n";
        $body .= "<tr><td>Blocked / rate-limited</td><td>" . (int) $blocked . "</td></tr>\n";
        $body .= "</table>\n<br>\n";
        $body .= "<h3>By Category</h3>\n<ul>\n";
        foreach ( $by_cat as $cat => $count ) {
            $body .= '<li><strong>' . esc_html( $cat ) . '</strong>: ' . (int) $count . "</li>\n";
        }
        $body .= "</ul>\n";
        $body .= '<p><a href="' . esc_url( admin_url( 'admin.php?page=bot-blocker-monitor' ) ) . '">View full monitor &rarr;</a></p>';

        $to      = ! empty( $settings['pro_digest_email'] ) ? $settings['pro_digest_email'] : get_option( 'admin_email' );
        $headers = [ 'Content-Type: text/html; charset=UTF-8' ];

        wp_mail( $to, $subject, $body, $headers );
    }

    /* ------------------------------------------------------------------
     * CSV / JSON EXPORT
     * ------------------------------------------------------------------ */

    /**
     * Handle admin-post action: export logs as CSV.
     * Uses search_logs() which returns ['items' => rows, 'total' => n].
     */
    public static function handle_export_csv(): void {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Forbidden', 403 );
        }
        check_admin_referer( 'bot_blocker_export' );

        $result = BotBlocker_Storage::search_logs( [ 'per_page' => 10000 ] );
        $rows   = $result['items'] ?? [];

        header( 'Content-Type: text/csv; charset=utf-8' );
        header( 'Content-Disposition: attachment; filename="bot-blocker-export-' . gmdate( 'Y-m-d' ) . '.csv"' );
        header( 'Pragma: no-cache' );

        $out = fopen( 'php://output', 'w' );
        fputcsv( $out, [ 'occurred_at', 'category', 'bot_name', 'action_taken', 'verification_state', 'country', 'city', 'path' ] );
        foreach ( $rows as $row ) {
            fputcsv( $out, [
                $row['occurred_at']        ?? '',
                $row['category']           ?? '',
                $row['bot_name']           ?? '',
                $row['action_taken']       ?? '',
                $row['verification_state'] ?? '',
                $row['country']            ?? '',
                $row['city']               ?? '',
                $row['path']               ?? '',
            ] );
        }
        fclose( $out );
        exit;
    }

    /**
     * Handle admin-post action: export logs as JSON.
     */
    public static function handle_export_json(): void {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Forbidden', 403 );
        }
        check_admin_referer( 'bot_blocker_export' );

        $result = BotBlocker_Storage::search_logs( [ 'per_page' => 10000 ] );
        $rows   = $result['items'] ?? [];

        header( 'Content-Type: application/json; charset=utf-8' );
        header( 'Content-Disposition: attachment; filename="bot-blocker-export-' . gmdate( 'Y-m-d' ) . '.json"' );
        header( 'Pragma: no-cache' );

        $out = [];
        foreach ( $rows as $row ) {
            $out[] = [
                'occurred_at'        => $row['occurred_at']        ?? '',
                'category'           => $row['category']           ?? '',
                'bot_name'           => $row['bot_name']           ?? '',
                'action_taken'       => $row['action_taken']       ?? '',
                'verification_state' => $row['verification_state'] ?? '',
                'country'            => $row['country']            ?? '',
                'city'               => $row['city']               ?? '',
                'path'               => $row['path']               ?? '',
            ];
        }
        echo wp_json_encode( $out, JSON_PRETTY_PRINT );
        exit;
    }

    /* ------------------------------------------------------------------
     * REST ROUTES (Pro settings save/read)
     * ------------------------------------------------------------------ */

    public static function register_rest_routes(): void {
        register_rest_route( 'bot-blocker/v1', '/pro-settings', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ __CLASS__, 'rest_get_pro_settings' ],
                'permission_callback' => fn() => current_user_can( 'manage_options' ),
            ],
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ __CLASS__, 'rest_save_pro_settings' ],
                'permission_callback' => fn() => current_user_can( 'manage_options' ),
            ],
        ] );
    }

    public static function rest_get_pro_settings( WP_REST_Request $request ): WP_REST_Response {
        $settings = BotBlocker_Settings::get();
        return new WP_REST_Response( [
            'pro_blocked_countries'       => $settings['pro_blocked_countries'] ?? [],
            'pro_country_block_action'    => $settings['pro_country_block_action'] ?? 'block',
            'pro_digest_frequency'        => $settings['pro_digest_frequency'] ?? 'disabled',
            'pro_digest_email'            => $settings['pro_digest_email'] ?? '',
            'pro_custom_block_html'       => $settings['pro_custom_block_html'] ?? '',
            'pro_auto_escalate_fake_bots' => $settings['pro_auto_escalate_fake_bots'] ?? false,
        ], 200 );
    }

    public static function rest_save_pro_settings( WP_REST_Request $request ): WP_REST_Response {
        $body     = $request->get_json_params();
        $settings = BotBlocker_Settings::get();

        if ( isset( $body['pro_blocked_countries'] ) && is_array( $body['pro_blocked_countries'] ) ) {
            $settings['pro_blocked_countries'] = array_map( 'sanitize_text_field', $body['pro_blocked_countries'] );
        }
        if ( isset( $body['pro_country_block_action'] ) ) {
            $settings['pro_country_block_action'] = in_array( $body['pro_country_block_action'], [ 'block', 'rate_limit' ], true )
                ? $body['pro_country_block_action'] : 'block';
        }
        if ( isset( $body['pro_digest_frequency'] ) ) {
            $settings['pro_digest_frequency'] = in_array( $body['pro_digest_frequency'], [ 'disabled', 'daily', 'weekly' ], true )
                ? $body['pro_digest_frequency'] : 'disabled';
        }
        if ( isset( $body['pro_digest_email'] ) ) {
            $settings['pro_digest_email'] = sanitize_email( $body['pro_digest_email'] );
        }
        if ( isset( $body['pro_custom_block_html'] ) ) {
            $settings['pro_custom_block_html'] = wp_kses_post( $body['pro_custom_block_html'] );
        }
        if ( isset( $body['pro_auto_escalate_fake_bots'] ) ) {
            $settings['pro_auto_escalate_fake_bots'] = (bool) $body['pro_auto_escalate_fake_bots'];
        }

        // BotBlocker_Settings::update() is the correct method name
        BotBlocker_Settings::update( $settings );
        return new WP_REST_Response( [ 'saved' => true ], 200 );
    }

    /* ------------------------------------------------------------------
     * ADMIN UI - Pro settings section rendered inside the main settings page
     * ------------------------------------------------------------------ */

    public static function render_pro_settings(): void {
        // Fields are now rendered in the main settings form.
    }
}
