<?php

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

class AIEO_Recompute_Queue {

    const TABLE_NAME      = 'aieo_recompute_queue';
    const SCHEMA_OPTION   = 'aieo_recompute_queue_schema_version';
    const SCHEMA_VERSION  = 1;

    const HOOK_TICK       = 'aieo_recompute_queue_tick';
    const BATCH_SIZE      = 100;
    const MAX_ATTEMPTS    = 5;

    const SURFACE_PRICES       = 'effective_prices';
    const SURFACE_RECOS        = 'effective_recos';
    const SURFACE_ELIGIBILITY  = 'effective_eligibility';

    
    private static $hooks_registered = false;

    public static function bootstrap() {
        self::maybe_install();
        if (self::$hooks_registered) { return; }
        self::$hooks_registered = true;

        
        
        add_action(self::HOOK_TICK, array(__CLASS__, 'tick'));

        
        
        
        
        
        
        
        
        
        
        add_filter('pre_as_schedule_recurring_action', array(__CLASS__, 'dedup_pre_schedule'), 5, 6);
        add_filter('pre_as_schedule_single_action',    array(__CLASS__, 'dedup_pre_schedule_single'), 5, 5);

        
        
        
        
        
        
        
        
        
        
        
        add_action('admin_init', array(__CLASS__, 'maybe_schedule_tick'), 30);
        add_action('init',       array(__CLASS__, 'maybe_schedule_tick'), 30);

        
        
        
        add_action('save_post_product',          array(__CLASS__, 'on_product_save'), 30, 1);
        add_action('woocommerce_update_product', array(__CLASS__, 'on_product_save'), 30, 1);

        
        
        add_action('aieo_dmm_cache_version_bumped', array(__CLASS__, 'on_rule_change'), 10, 1);
    }

    
    public static function maybe_schedule_tick() {
        static $checked = false;
        if ($checked) { return; }
        $checked = true;

        
        
        
        if (get_transient('aieo_recompute_schedule_check_lock')) {
            return;
        }
        set_transient('aieo_recompute_schedule_check_lock', 1, 5 * MINUTE_IN_SECONDS);

        if (function_exists('as_next_scheduled_action') && function_exists('as_schedule_recurring_action')) {
            
            
            
            
            if (get_option('aieo_recompute_dupe_cleanup_v1') !== 'done') {
                if (function_exists('as_unschedule_all_actions')) {
                    as_unschedule_all_actions(self::HOOK_TICK, array(), 'aieo');
                }
                update_option('aieo_recompute_dupe_cleanup_v1', 'done', false);
            }

            
            
            
            $next = as_next_scheduled_action(self::HOOK_TICK, null, 'aieo');
            if ($next !== false) {
                return; 
            }

            $interval = (int) get_option('aieo_recompute_tick_seconds', 60);
            if ($interval < 30) { $interval = 30; }
            as_schedule_recurring_action(time() + $interval, $interval, self::HOOK_TICK, array(), 'aieo');
            return;
        }
        
        if (!wp_next_scheduled(self::HOOK_TICK)) {
            wp_schedule_event(time() + 60, 'aieo_recompute_minute', self::HOOK_TICK);
        }
    }

    
    public static function dedup_pre_schedule($pre, $timestamp, $interval, $hook, $args = array(), $group = '') {
        if ($hook !== self::HOOK_TICK || $group !== 'aieo') {
            return $pre;
        }
        $existing = self::find_existing_tick_action();
        return ($existing > 0) ? $existing : $pre;
    }

    public static function dedup_pre_schedule_single($pre, $timestamp, $hook, $args = array(), $group = '') {
        if ($hook !== self::HOOK_TICK || $group !== 'aieo') {
            return $pre;
        }
        $existing = self::find_existing_tick_action();
        return ($existing > 0) ? $existing : $pre;
    }

    private static function find_existing_tick_action() {
        global $wpdb;
        $lock_key = 'aieo_recompute_tick_sched';
        $got = (int) $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, 0)', $lock_key));
        if ($got !== 1) {
            
            
            return 1;
        }
        try {
            $existing = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT a.action_id
                   FROM {$wpdb->prefix}actionscheduler_actions a
              LEFT JOIN {$wpdb->prefix}actionscheduler_groups  g ON g.group_id = a.group_id
                  WHERE a.hook = %s
                    AND a.status IN ('pending', 'in-progress')
                    AND g.slug = %s
               ORDER BY a.action_id ASC
                  LIMIT 1",
                self::HOOK_TICK,
                'aieo'
            ));
            return $existing;
        } finally {
            $wpdb->query($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $lock_key));
        }
    }

    
    public static function enqueue($surface, $entity_id, $audience_key = '') {
        global $wpdb;
        if (!self::table_exists()) { return; }

        $entity_id = (int) $entity_id;
        if ($entity_id <= 0 || !is_string($surface) || $surface === '') { return; }

        $audience_key = is_string($audience_key) ? $audience_key : '';

        $table = self::table_name();
        $wpdb->query($wpdb->prepare(
            
            "INSERT IGNORE INTO {$table} (surface, entity_id, audience_key, enqueued_at, attempts)
             VALUES (%s, %d, %s, %s, 0)",
            $surface,
            $entity_id,
            $audience_key,
            current_time('mysql', true)
        ));
    }

    
    public static function tick() {
        global $wpdb;
        if (!self::table_exists()) { return; }
        $table = self::table_name();

        
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table}
             WHERE attempts < %d
             ORDER BY enqueued_at ASC
             LIMIT %d",
            self::MAX_ATTEMPTS,
            self::BATCH_SIZE
        ), ARRAY_A);

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

        foreach ($rows as $row) {
            $id       = (int) $row['id'];
            $surface  = (string) $row['surface'];
            $entity   = (int) $row['entity_id'];
            $audience = (string) $row['audience_key'];

            $error = null;
            try {
                self::process_one($surface, $entity, $audience);
            } catch (\Throwable $e) {
                $error = $e->getMessage();
            }

            if ($error === null) {
                $wpdb->delete($table, array('id' => $id), array('%d'));
            } else {
                $wpdb->update(
                    $table,
                    array('attempts' => (int) $row['attempts'] + 1, 'last_error' => $error),
                    array('id' => $id),
                    array('%d', '%s'),
                    array('%d')
                );
            }
        }
    }

    
    private static function process_one($surface, $entity_id, $audience_key) {
        switch ($surface) {
            case self::SURFACE_PRICES:
                if (!class_exists('AIEO_Effective_Prices')) { return; }
                if ($audience_key !== '') {
                    AIEO_Effective_Prices::recompute($entity_id, $audience_key);
                } else {
                    AIEO_Effective_Prices::recompute_all_audiences($entity_id);
                }
                return;
            case self::SURFACE_RECOS:
                if (class_exists('AIEO_Effective_Recos')) {
                    if ($audience_key !== '') {
                        AIEO_Effective_Recos::recompute($entity_id, $audience_key);
                    } else {
                        AIEO_Effective_Recos::recompute_all_audiences($entity_id);
                    }
                }
                return;
            case self::SURFACE_ELIGIBILITY:
                if (class_exists('AIEO_Effective_Eligibility')) {
                    if ($audience_key !== '') {
                        AIEO_Effective_Eligibility::recompute($entity_id, $audience_key);
                    } else {
                        AIEO_Effective_Eligibility::recompute_all_audiences($entity_id);
                    }
                }
                return;
        }
    }

    
    
    

    public static function on_product_save($product_id) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) { return; }
        self::enqueue(self::SURFACE_PRICES, $product_id, '');
        self::enqueue(self::SURFACE_RECOS, $product_id, '');
        self::enqueue(self::SURFACE_ELIGIBILITY, $product_id, '');
    }

    
    private static function surfaces_for_domain($domain) {
        switch ($domain) {
            case 'prices':
            case 'loyalty':
                return array(self::SURFACE_PRICES);
            case 'gifts':
            case 'needs':
            case 'shipping':
                return array(self::SURFACE_PRICES, self::SURFACE_ELIGIBILITY);
            case 'brands':
                
                
                return array(self::SURFACE_PRICES, self::SURFACE_ELIGIBILITY);
            case 'complementary':
                return array(self::SURFACE_ELIGIBILITY);
            default:
                return array();
        }
    }

    
    private static function bulk_enqueue_products_for_surface($surface) {
        global $wpdb;
        if (!self::table_exists()) { return 0; }
        $table = self::table_name();
        $now   = current_time('mysql', true);
        
        $sql = $wpdb->prepare(
            "INSERT IGNORE INTO {$table} (surface, entity_id, audience_key, enqueued_at, attempts)
             SELECT %s, ID, '', %s, 0
               FROM {$wpdb->posts}
              WHERE post_type = 'product' AND post_status = 'publish'",
            $surface,
            $now
        );
        return (int) $wpdb->query($sql);
    }

    public static function on_rule_change($domain) {
        
        
        
        
        
        
        $domain   = (string) $domain;
        $surfaces = self::surfaces_for_domain($domain);
        if (empty($surfaces)) {
            return;
        }
        
        update_option('aieo_effective_prices_sweep_needed', time(), 'no');

        foreach ($surfaces as $surface) {
            self::bulk_enqueue_products_for_surface($surface);
        }
    }

    
    
    

    public static function depth() {
        if (!self::table_exists()) { return 0; }
        global $wpdb;
        return (int) $wpdb->get_var('SELECT COUNT(*) FROM ' . self::table_name());
    }

    public static function depth_by_surface() {
        if (!self::table_exists()) { return array(); }
        global $wpdb;
        $rows = $wpdb->get_results('SELECT surface, COUNT(*) AS n FROM ' . self::table_name() . ' GROUP BY surface', ARRAY_A);
        $out = array();
        if (is_array($rows)) {
            foreach ($rows as $r) { $out[(string) $r['surface']] = (int) $r['n']; }
        }
        return $out;
    }

    
    
    

    public static function table_name() {
        global $wpdb;
        return $wpdb->prefix . self::TABLE_NAME;
    }

    public static function table_exists() {
        static $cached = null;
        if ($cached !== null) { return $cached; }
        global $wpdb;
        $found = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
            self::table_name()
        ));
        return $cached = ($found > 0);
    }

    public static function maybe_install() {
        $current = (int) get_option(self::SCHEMA_OPTION, 0);
        if ($current >= self::SCHEMA_VERSION) {
            
            
            
            
            
            
            if (!is_admin() && !wp_doing_cron()) {
                return;
            }
            if (self::table_exists()) {
                return;
            }
        }
        global $wpdb;
        $table   = self::table_name();
        $charset = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE {$table} (
            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            surface VARCHAR(32) NOT NULL,
            entity_id BIGINT(20) UNSIGNED NOT NULL,
            audience_key VARCHAR(64) NOT NULL DEFAULT '',
            enqueued_at DATETIME NOT NULL,
            attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
            last_error TEXT,
            PRIMARY KEY (id),
            UNIQUE KEY uq_pending (surface, entity_id, audience_key),
            KEY ix_enqueued (enqueued_at)
        ) {$charset};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
        update_option(self::SCHEMA_OPTION, self::SCHEMA_VERSION, 'no');
    }
}

add_filter('cron_schedules', function ($schedules) {
    if (!isset($schedules['aieo_recompute_minute'])) {
        $schedules['aieo_recompute_minute'] = array(
            'interval' => 60,
            'display'  => 'AIEO recompute (every minute)',
        );
    }
    return $schedules;
});
