<?php
/**
 * Low Credits Monitor for BoostBot.
 *
 * Checks purchased (accumulated) BoostCoin balance periodically and alerts
 * the WordPress administrator when it drops below a configurable threshold.
 *
 * Notifications:
 *  - WordPress admin notice (dismissible, reappears after 24 hours or new credits added)
 *  - Optional email to the site admin (sent once per threshold crossing)
 *
 * @package BoostBot
 * @since   1.0.0
 */

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

class BoostBot_Low_Credits_Monitor {

    /** WP-Cron hook name. */
    const CRON_HOOK = 'boostbot_low_credits_check';

    /** Transient: cached credit balance from the most recent API call. */
    const TRANSIENT_BALANCE = 'boostbot_credits_balance_cache';

    /** Transient: flag that email has been sent for this threshold crossing. */
    const TRANSIENT_EMAIL_SENT = 'boostbot_low_credits_email_sent';

    /** Transient: admin dismissed the notice (24-hour cooldown). */
    const TRANSIENT_DISMISSED = 'boostbot_low_credits_notice_dismissed';

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Bootstrap                                                          */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * Register all hooks.
     */
    public static function init() {
        // Schedule cron if not already scheduled.
        add_action( 'init', array( __CLASS__, 'schedule_cron' ) );

        // The actual periodic check.
        add_action( self::CRON_HOOK, array( __CLASS__, 'cron_check' ) );

        // Admin notices.
        add_action( 'admin_notices', array( __CLASS__, 'maybe_show_notice' ) );
        add_action( 'admin_notices', array( __CLASS__, 'maybe_show_connection_error_notice' ) );

        // AJAX handlers.
        add_action( 'wp_ajax_boostbot_dismiss_low_credits', array( __CLASS__, 'ajax_dismiss' ) );
        add_action( 'wp_ajax_boostbot_dismiss_connection_error', array( __CLASS__, 'ajax_dismiss_connection_error' ) );
    }

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Cron                                                                */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * Ensure the cron event exists.
     */
    public static function schedule_cron() {
        if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
            wp_schedule_event( time(), 'twicedaily', self::CRON_HOOK );
        }
    }

    /**
     * Remove the cron event (for deactivation).
     */
    public static function unschedule_cron() {
        $ts = wp_next_scheduled( self::CRON_HOOK );
        if ( $ts ) {
            wp_unschedule_event( $ts, self::CRON_HOOK );
        }
    }

    /**
     * Cron callback: fetch credits from backend and evaluate.
     */
    public static function cron_check() {
        $balance = self::fetch_balance_from_backend();
        if ( null === $balance ) {
            return; // Backend unreachable; skip silently.
        }

        self::store_balance( $balance );
        self::evaluate( $balance );
    }

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Public API — called from credits endpoint to keep cache warm       */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * Called by the credits endpoint after a successful status fetch.
     * Stores the balance and evaluates alerts inline (no extra HTTP call).
     *
     * @param float $remaining Purchased credits remaining.
     */
    public static function on_credits_fetched( float $remaining ) {
        self::store_balance( $remaining );
        self::evaluate( $remaining );
    }

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Internal helpers                                                    */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * Fetch the purchased credits balance from the backend.
     *
     * @return float|null Remaining purchased credits, or null on failure.
     */
    private static function fetch_balance_from_backend() {
        if ( ! class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
            return null;
        }

        $client  = new BoostBot_Credits_Backend_Client();
        $site_id = $client->ensure_site_id();

        if ( is_wp_error( $site_id ) ) {
            return null;
        }

        $status = $client->get_status( (string) $site_id );
        if ( is_wp_error( $status ) || ! is_array( $status ) ) {
            return null;
        }

        // boost_credits.remaining is the unified wallet (purchased credits).
        if ( isset( $status['boost_credits']['remaining'] ) ) {
            return (float) $status['boost_credits']['remaining'];
        }

        return null;
    }

    /**
     * Cache the balance in a transient (12-hour TTL).
     *
     * @param float $remaining
     */
    private static function store_balance( float $remaining ) {
        set_transient( self::TRANSIENT_BALANCE, $remaining, 12 * HOUR_IN_SECONDS );
    }

    /**
     * Evaluate whether alerts should fire.
     *
     * @param float $remaining
     */
    private static function evaluate( float $remaining ) {
        $threshold = (int) BoostBot_Settings::get( 'low_credits_threshold', 100 );

        // Threshold disabled.
        if ( $threshold <= 0 ) {
            return;
        }

        if ( $remaining >= $threshold ) {
            // Credits are above threshold — clear any previous alert flags so
            // the next time they drop below, we can alert again.
            delete_transient( self::TRANSIENT_EMAIL_SENT );
            delete_transient( self::TRANSIENT_DISMISSED );
            return;
        }

        // Below threshold — send email (once).
        $email_enabled = (bool) BoostBot_Settings::get( 'low_credits_email_enabled', true );
        if ( $email_enabled && ! get_transient( self::TRANSIENT_EMAIL_SENT ) ) {
            self::send_email( $remaining, $threshold );
            // Mark email as sent. TTL = 30 days (effectively "until credits rise above threshold").
            set_transient( self::TRANSIENT_EMAIL_SENT, 1, 30 * DAY_IN_SECONDS );
        }
    }

    /**
     * Send a low-credits email to the site admin.
     *
     * @param float $remaining
     * @param int   $threshold
     */
    private static function send_email( float $remaining, int $threshold ) {
        $admin_email = get_option( 'admin_email' );
        if ( empty( $admin_email ) ) {
            return;
        }

        $site_name = get_bloginfo( 'name' );
        $subject   = sprintf(
            /* translators: 1: site name */
            __( '[%s] התראה: יתרת BoostCoin נמוכה', 'boostbot' ),
            $site_name
        );

        $remaining_fmt = number_format( ceil( $remaining ) );
        $threshold_fmt = number_format( $threshold );

        $body = implode( "\n", array(
            sprintf( 'שלום מנהל %s,', $site_name ),
            '',
            sprintf( 'יתרת ה-BoostCoin הנרכשים באתר שלך ירדה מתחת לסף ההתראה.' ),
            '',
            sprintf( '  יתרה נוכחית: %s BoostCoin', $remaining_fmt ),
            sprintf( '  סף התראה: %s BoostCoin', $threshold_fmt ),
            '',
            'מומלץ לרכוש BoostCoin נוספים כדי למנוע הפסקת שירות.',
            '',
            sprintf( 'ניתן לרכוש בלוח הבקרה: %s', admin_url( 'admin.php?page=boostbot#/usage' ) ),
            '',
            'בברכה,',
            'BoostBot',
            '',
            '---',
            sprintf( 'ניתן לשנות את סף ההתראה או לבטל אימייל זה בהגדרות: %s', admin_url( 'admin.php?page=boostbot#/settings' ) ),
        ) );

        wp_mail( $admin_email, $subject, $body );
    }

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Admin notice                                                        */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * Show a dismissible admin notice when credits are low.
     */
    public static function maybe_show_notice() {
        $threshold = (int) BoostBot_Settings::get( 'low_credits_threshold', 100 );
        if ( $threshold <= 0 ) {
            return;
        }

        // Check if dismissed recently.
        if ( get_transient( self::TRANSIENT_DISMISSED ) ) {
            return;
        }

        $remaining = get_transient( self::TRANSIENT_BALANCE );
        if ( false === $remaining ) {
            return; // No cached balance yet.
        }

        $remaining = (float) $remaining;
        if ( $remaining >= $threshold ) {
            return;
        }

        // Only show on BoostBot admin pages or the WP dashboard.
        $screen = get_current_screen();
        if ( $screen && ! in_array( $screen->id, array( 'dashboard', 'toplevel_page_boostbot' ), true ) ) {
            return;
        }

        $remaining_fmt = number_format( ceil( $remaining ) );
        $threshold_fmt = number_format( $threshold );
        $purchase_url  = admin_url( 'admin.php?page=boostbot#/usage' );
        $settings_url  = admin_url( 'admin.php?page=boostbot#/settings' );

        printf(
            '<div class="notice notice-warning is-dismissible" id="boostbot-low-credits-notice" data-nonce="%s">'
            . '<p><strong>BoostBot:</strong> יתרת הקרדיטים הנרכשים נמוכה — <strong>%s</strong> קרדיטים (סף: %s). '
            . '<a href="%s">רכשו קרדיטים</a> | '
            . '<a href="%s">שנו הגדרות התראה</a>'
            . '</p></div>',
            esc_attr( wp_create_nonce( 'boostbot_dismiss_low_credits' ) ),
            esc_html( $remaining_fmt ),
            esc_html( $threshold_fmt ),
            esc_url( $purchase_url ),
            esc_url( $settings_url )
        );
    }

    /**
     * AJAX handler: dismiss the notice for 24 hours.
     */
    public static function ajax_dismiss() {
        check_ajax_referer( 'boostbot_dismiss_low_credits' );
        set_transient( self::TRANSIENT_DISMISSED, 1, DAY_IN_SECONDS );
        wp_send_json_success();
    }

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Backend connection error notice                                     */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * Show an admin notice when the backend API key is missing (connection broken).
     *
     * The transient `boostbot_backend_key_missing` is set by ensure_site_id() in
     * the credits clients when the site is registered but the API key is not available.
     */
    public static function maybe_show_connection_error_notice() {
        if ( ! get_transient( 'boostbot_backend_key_missing' ) ) {
            return;
        }

        // Check if dismissed recently.
        if ( get_transient( 'boostbot_connection_error_dismissed' ) ) {
            return;
        }

        // Only show on BoostBot admin pages or the WP dashboard.
        $screen = get_current_screen();
        if ( $screen && ! in_array( $screen->id, array( 'dashboard', 'toplevel_page_boostbot' ), true ) ) {
            return;
        }

        $settings_url = admin_url( 'admin.php?page=boostbot#/settings' );
        $debug_url    = rest_url( 'boostbot/v1/config/repair-connection' );

        printf(
            '<div class="notice notice-error is-dismissible" id="boostbot-connection-error-notice" data-nonce="%s">'
            . '<p><strong>BoostBot — שגיאת חיבור:</strong> האתר רשום בשרת אבל מפתח ההזדהות (API Key) חסר. '
            . 'שירותי AI, אינדוקס וקרדיטים לא יעבדו כראוי. '
            . '<a href="%s"><strong>תקן חיבור</strong></a>'
            . '</p></div>',
            esc_attr( wp_create_nonce( 'boostbot_dismiss_connection_error' ) ),
            esc_url( $settings_url )
        );
    }

    /**
     * AJAX handler: dismiss the connection error notice for 12 hours.
     */
    public static function ajax_dismiss_connection_error() {
        check_ajax_referer( 'boostbot_dismiss_connection_error' );
        set_transient( 'boostbot_connection_error_dismissed', 1, 12 * HOUR_IN_SECONDS );
        wp_send_json_success();
    }
}
