<?php
// Класс с основной логикой агрегации (bootstrap и ежедневная)
class COS_Aggregation_Aggregator {

    //Размер батча
    const COS_AGG_BOOTSTRAP_BATCH_SIZE = 10000;

    /**
     * Очистка старых данных из основной таблицы по cron
     */
    public static function cron_cleanup() {
        // В будущем: Проверка настроек!
        // ...здесь логика удаления записей старше 60 дней...
    }

/**
 * Основной метод Bootstrap-агрегации (обработка 1 батча по одной дате)
 */
    public static function run_bootstrap() {
        global $wpdb;

        // 1. Проверка: уже завершено?
        if ( get_option('cos_agg_bootstrap_completed') ) {
            COS_Aggregation_Helpers::add_notice('Bootstrap-агрегация уже завершена.', 'info');
            return;
        }

        // 2. Определяем таблицы
        $wallet_table        = $wpdb->prefix . 'cos_wallet_transactions';
        $user_game_table     = $wpdb->prefix . COS_Aggregation_Install::TABLE_USER_GAME_DAILY;
        $casino_daily_table  = $wpdb->prefix . COS_Aggregation_Install::TABLE_CASINO_DAILY;
        $top_winners_table   = $wpdb->prefix . COS_Aggregation_Install::TABLE_TOP_WINNERS;
        $admin_table         = $wpdb->prefix . COS_Aggregation_Install::TABLE_ADMIN_TRANSACTIONS;
        $champions_table     = $wpdb->prefix . COS_Aggregation_Install::TABLE_GAME_CHAMPIONS;
        $user_balance_table  = $wpdb->prefix . COS_Aggregation_Install::TABLE_USER_BALANCE_DAILY;

        // 3. С какой даты продолжаем? (Опция. Если не задана — ищем минимальную дату)
        $last_date = get_option('cos_agg_last_bootstrap_date', '');
        $today = date('Y-m-d');
        if ( empty($last_date) ) {
            $last_date = $wpdb->get_var("SELECT MIN(DATE(date)) FROM $wallet_table");
            if ( empty($last_date) ) {
                update_option('cos_agg_bootstrap_completed', true);
                update_option('cos_agg_bootstrap_progress', 100);
                return;
            }
            update_option('cos_agg_last_bootstrap_date', $last_date);
        }

        // Не трогаем сегодняшний день!
        if ( $last_date >= $today ) {
            update_option('cos_agg_bootstrap_completed', true);
            update_option('cos_agg_bootstrap_progress', 100);
            COS_Aggregation_Cron::unschedule_bootstrap_cron();
            COS_Aggregation_Helpers::add_notice('Bootstrap-агрегация завершена!', 'success');
            return;
        }

        // 4. Узнаём, с какого transaction_id продолжаем этот день
        $last_tid = (int)get_option('cos_agg_last_bootstrap_tid', 0);

        // 5. Находим максимальный transaction_id за этот день (чтобы понимать конец дня)
        $max_tid_in_day = (int)$wpdb->get_var(
            $wpdb->prepare("SELECT MAX(transaction_id) FROM $wallet_table WHERE DATE(date) = %s", $last_date)
        );
        if ( !$max_tid_in_day ) {
            // Нет записей за этот день — двигаем дату вперёд, сбрасываем last_tid
            update_option('cos_agg_last_bootstrap_date', date('Y-m-d', strtotime($last_date . ' +1 day')));
            update_option('cos_agg_last_bootstrap_tid', 0);
            return;
        }

        // 6. Сохраняем агрегирующие структуры в transient, чтобы не терять между батчами
        $agg = get_transient('cos_agg_batch_tmp_' . $last_date);
        if (!$agg) {
            $agg = [
                'user_game_day_stats' => [],
                'casino_daily_stats'  => [],
                'top_win_by_game_date' => [],
                'top_mult_by_game_date' => [],
                'admin_transactions'  => [],
                'user_last_balance'   => [],
            ];
        }

        // 7. Собираем батч за этот день, начиная с last_tid
        $batch_size = self::COS_AGG_BOOTSTRAP_BATCH_SIZE;

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM $wallet_table WHERE DATE(date) = %s AND transaction_id > %d ORDER BY transaction_id ASC LIMIT %d",
                $last_date, $last_tid, $batch_size
            ),
            ARRAY_A
        );

        // Все ставки (debit) по game_round_id
        $bets_by_round = [];
        foreach ($rows as $row) {
            if ($row['action'] === 'debit') {
                $bets_by_round[ $row['game_round_id'] ] = [
                    'amount'   => (int)$row['amount'],
                    'user_id'  => (int)$row['user_id'],
                    'game'     => $row['game'],
                    'provider' => $row['created_by'],
                    'date'     => date('Y-m-d', strtotime($row['date']))
                ];
            }
        }

        // --- ОБРАБОТКА БАТЧА --- (копим только за этот день)
        foreach ($rows as $row) {
            $user_id   = (int) $row['user_id'];
            $game      = $row['game'];
            $provider  = $row['created_by'];
            $date      = date('Y-m-d', strtotime($row['date']));
            $action    = $row['action'];
            $amount    = (int) $row['amount'];
            $multiplier= isset($row['multiplier']) ? (float)$row['multiplier'] : 0;
            $balance   = isset($row['balance']) ? (int)$row['balance'] : null;

            // --- 0. Админские транзакции отдельно ---
            if ($game === 'Casino Administration') {
                $agg['admin_transactions'][] = [
                    'user_id'    => $user_id,
                    'action'     => $action,
                    'status'     => $row['status'],
                    'details'    => $row['details'],
                    'created_by' => $provider,
                    'amount'     => $amount,
                    'date'       => $row['date'],
                ];
                continue;
            }

            // --- 1. User-Game-Day агрегаты ---
            $key = "{$user_id}|{$game}|{$provider}|{$date}";
            if ( !isset($agg['user_game_day_stats'][$key]) ) {
                $agg['user_game_day_stats'][$key] = [
                    'user_id'   => $user_id,
                    'game'      => $game,
                    'provider'  => $provider,
                    'date'      => $date,
                    'spins'     => 0,
                    'total_bet' => 0,
                    'total_win' => 0,
                    'net_win'   => 0,
                    'max_win'   => 0,
                    'max_multiplier' => 0,
                    'rtp'       => 0,
                ];
            }
            if ( $action === 'debit' ) {
                $agg['user_game_day_stats'][$key]['spins']++;
                $agg['user_game_day_stats'][$key]['total_bet'] += $amount;
                $agg['user_game_day_stats'][$key]['net_win']   -= $amount;
            }
            if ( $action === 'credit' ) {
                $agg['user_game_day_stats'][$key]['total_win'] += $amount;
                $agg['user_game_day_stats'][$key]['net_win']   += $amount;
                $agg['user_game_day_stats'][$key]['max_win'] = max($agg['user_game_day_stats'][$key]['max_win'], $amount);
                $agg['user_game_day_stats'][$key]['max_multiplier'] = max($agg['user_game_day_stats'][$key]['max_multiplier'], $multiplier);
            }

            // --- 2. Casino-Daily агрегаты ---
            if ( !isset($agg['casino_daily_stats'][$date]) ) {
                $agg['casino_daily_stats'][$date] = [
                    'date'        => $date,
                    'total_spins' => 0,
                    'total_bet'   => 0,
                    'total_win'   => 0,
                ];
            }
            if ( $action === 'debit' ) {
                $agg['casino_daily_stats'][$date]['total_spins']++;
                $agg['casino_daily_stats'][$date]['total_bet'] += $amount;
            }
            if ( $action === 'credit' ) {
                $agg['casino_daily_stats'][$date]['total_win'] += $amount;
            }

            // --- 3. Top по win (по игре за день) ---
            if ( $action === 'credit' ) {
                $key_tw = "{$game}|{$date}";
                $bet = 0;
                if (!empty($row['game_round_id']) && isset($bets_by_round[$row['game_round_id']])) {
                    $bet = $bets_by_round[$row['game_round_id']]['amount'];
                }
                if (
                    !isset($agg['top_win_by_game_date'][$key_tw]) ||
                    $amount > $agg['top_win_by_game_date'][$key_tw]['win_amount']
                ) {
                    $agg['top_win_by_game_date'][$key_tw] = [
                        'date'       => $date,
                        'game'       => $game,
                        'provider'   => $provider,
                        'user_id'    => $user_id,
                        'win_amount' => $amount,
                        'bet_amount' => $bet,
                        'multiplier' => $multiplier,
                        'type'       => 'win',
                    ];
                }
            }
            // --- 4. Top по max multiplier (по игре за день) ---
            if ( $action === 'credit' ) {
                $key_tm = "{$game}|{$date}";
                $bet = 0;
                if (!empty($row['game_round_id']) && isset($bets_by_round[$row['game_round_id']])) {
                    $bet = $bets_by_round[$row['game_round_id']]['amount'];
                }
                if (
                    !isset($agg['top_mult_by_game_date'][$key_tm]) ||
                    $multiplier > $agg['top_mult_by_game_date'][$key_tm]['multiplier']
                ) {
                    $agg['top_mult_by_game_date'][$key_tm] = [
                        'date'       => $date,
                        'game'       => $game,
                        'provider'   => $provider,
                        'user_id'    => $user_id,
                        'win_amount' => $amount,
                        'bet_amount' => $bet,
                        'multiplier' => $multiplier,
                        'type'       => 'multiplier',
                    ];
                }
            }

            // --- 5. Баланс по дням ---
            $balance_key = $user_id . '|' . $date;
            $agg['user_last_balance'][$balance_key] = $balance;
            // (остальное как есть)
        }

        // Если не дошли до конца дня — сохраняем агрегаты и выходим (следующий батч)
        if ( !empty($rows) && end($rows)['transaction_id'] < $max_tid_in_day ) {
            set_transient('cos_agg_batch_tmp_' . $last_date, $agg, 2 * HOUR_IN_SECONDS);
            update_option('cos_agg_last_bootstrap_tid', end($rows)['transaction_id']);
            // Прогресс тут считать не нужно, т.к. день не завершён.
error_log('241. transient saved '.$last_date );
            return;
        }

        // --- ДОЗАПОЛНЕНИЕ: RTP для user_game_day ---
        foreach ($agg['user_game_day_stats'] as &$stat) {
            $stat['rtp'] = ($stat['total_bet'] > 0)
                ? round($stat['total_win'] / $stat['total_bet'], 4)
                : 0;
        }
        unset($stat);

        // --- ДОЗАПОЛНЕНИЕ: Champions (топ-3) --- (смотри твой код)
        $champion_rows = [];

        // 1. max_win: топ-3 по каждому game/date
        $grouped = [];
        foreach ($agg['top_win_by_game_date'] as $row) {
            $key = $row['game'] . '|' . $row['date'];
            $grouped[$key][] = $row;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['win_amount'] <=> $a['win_amount']);
            $place = 1;
            foreach ($players as $row) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $row['date'],
                    'game'       => $row['game'],
                    'provider'   => $row['provider'],
                    'type'       => 'max_win',
                    'place'      => $place,
                    'user_id'    => $row['user_id'],
                    'win_amount' => $row['win_amount'],
                    'bet_amount' => $row['bet_amount'],
                    'multiplier' => $row['multiplier'],
                    'spins'      => null,
                ];
                $place++;
            }
        }

        // 2. max_multiplier: топ-3 по каждому game/date
        $grouped = [];
        foreach ($agg['top_mult_by_game_date'] as $row) {
            $key = $row['game'] . '|' . $row['date'];
            $grouped[$key][] = $row;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['multiplier'] <=> $a['multiplier']);
            $place = 1;
            foreach ($players as $row) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $row['date'],
                    'game'       => $row['game'],
                    'provider'   => $row['provider'],
                    'type'       => 'max_multiplier',
                    'place'      => $place,
                    'user_id'    => $row['user_id'],
                    'win_amount' => $row['win_amount'],
                    'bet_amount' => $row['bet_amount'],
                    'multiplier' => $row['multiplier'],
                    'spins'      => null,
                ];
                $place++;
            }
        }

        // 3. total_bets: топ-3 по каждому game/date из user_game_day_stats
        $grouped = [];
        foreach ($agg['user_game_day_stats'] as $stat) {
            $key = $stat['game'] . '|' . $stat['date'];
            $grouped[$key][] = $stat;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['total_bet'] <=> $a['total_bet']);
            $place = 1;
            foreach ($players as $stat) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $stat['date'],
                    'game'       => $stat['game'],
                    'provider'   => $stat['provider'],
                    'type'       => 'total_bets',
                    'place'      => $place,
                    'user_id'    => $stat['user_id'],
                    'win_amount' => $stat['max_win'],
                    'bet_amount' => $stat['total_bet'],
                    'multiplier' => $stat['max_multiplier'],
                    'spins'      => $stat['spins'],
                ];
                $place++;
            }
        }

        // 4. total_spins: топ-3 по каждому game/date из user_game_day_stats
        $grouped = [];
        foreach ($agg['user_game_day_stats'] as $stat) {
            $key = $stat['game'] . '|' . $stat['date'];
            $grouped[$key][] = $stat;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['spins'] <=> $a['spins']);
            $place = 1;
            foreach ($players as $stat) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $stat['date'],
                    'game'       => $stat['game'],
                    'provider'   => $stat['provider'],
                    'type'       => 'total_spins',
                    'place'      => $place,
                    'user_id'    => $stat['user_id'],
                    'win_amount' => $stat['max_win'],
                    'bet_amount' => $stat['total_bet'],
                    'multiplier' => $stat['max_multiplier'],
                    'spins'      => $stat['spins'],
                ];
                $place++;
            }
        }

        // --- ВСТАВКА В ТАБЛИЦЫ ---
        foreach ($agg['user_game_day_stats'] as $stat) {
            $wpdb->replace($user_game_table, $stat);
        }
        foreach ($agg['casino_daily_stats'] as $stat) {
            $wpdb->replace($casino_daily_table, $stat);
        }
        foreach ($agg['top_win_by_game_date'] as $row) {
            $wpdb->replace($top_winners_table, $row);
        }
        foreach ($agg['top_mult_by_game_date'] as $row) {
            $wpdb->replace($top_winners_table, $row);
        }
        foreach ($agg['admin_transactions'] as $row) {
            $wpdb->insert($admin_table, $row);
        }
        foreach ($champion_rows as $row) {
            $wpdb->replace($champions_table, $row);
        }
        foreach ($agg['user_last_balance'] as $key => $end_balance) {
            list($user_id, $date) = explode('|', $key);
            if ($end_balance !== null) {
                $wpdb->replace($user_balance_table, [
                    'user_id'     => $user_id,
                    'date'        => $date,
                    'end_balance' => $end_balance,
                ]);
            }
        }

        // Очищаем временный агрегат, двигаем дату, сбрасываем last_tid
        delete_transient('cos_agg_batch_tmp_' . $last_date);
error_log('396. transient deleted '.$last_date );
        $next_date = date('Y-m-d', strtotime($last_date . ' +1 day'));
        update_option('cos_agg_last_bootstrap_date', $next_date);
        update_option('cos_agg_last_bootstrap_tid', 0);

        // Считаем прогресс по датам (не по id!)
        $min_date = $wpdb->get_var("SELECT MIN(DATE(date)) FROM $wallet_table");
        $max_date = $wpdb->get_var("SELECT MAX(DATE(date)) FROM $wallet_table");
        $total_days = (strtotime($max_date) - strtotime($min_date)) / 86400 + 1;
        $days_done = (strtotime($next_date) - strtotime($min_date)) / 86400;
        $progress = min(100, round(100 * $days_done / $total_days, 2));
        update_option('cos_agg_bootstrap_progress', $progress);

        if ($next_date >= $today) {
            update_option('cos_agg_bootstrap_completed', true);
            update_option('cos_agg_bootstrap_progress', 100);
            COS_Aggregation_Cron::unschedule_bootstrap_cron();
            COS_Aggregation_Helpers::add_notice('Bootstrap-агрегация завершена!', 'success');
        }
    }

/**
 * Ежедневная агрегация “за вчера” (инициализирует батчевую обработку)
 */
public static function cron_daily_aggregate() {
    // Проверка разрешения из настроек
    if ( ! get_option('cos_agg_cron_enabled', true) ) return;
    if ( ! get_option('cos_agg_bootstrap_completed') ) return;

    // Не запускаем, если уже в процессе
    $batch_date = get_option('cos_agg_daily_batch_date');
    $batch_completed = get_option('cos_agg_daily_batch_completed', true);
    if ( $batch_date && !$batch_completed ) {
        error_log('COS Aggregation: Дневная агрегация за ' . $batch_date . ' ещё не завершена!');
        return;
    }

    // Определяем дату “вчера”
    $yesterday = date('Y-m-d', strtotime('-1 day', current_time('timestamp')));

    // Запускаем новую дневную агрегацию
    update_option('cos_agg_daily_batch_date', $yesterday);
    update_option('cos_agg_daily_batch_last_tid', 0);
    update_option('cos_agg_daily_batch_completed', false);
    update_option('cos_agg_daily_batch_progress', 0);

    // Планируем крон-батч (каждые 10 минут)
    if ( ! wp_next_scheduled('cos_agg_daily_aggregate_batch') ) {
        wp_schedule_event( time() + 60, 'ten_minutes', 'cos_agg_daily_aggregate_batch' );
    }
}

    /**
     * Батчевый обработчик агрегации за день (крон или ручной запуск)
     */
    public static function cron_daily_aggregate_batch() {
        global $wpdb;

        // 1. Чтение прогресса
        $batch_date = get_option('cos_agg_daily_batch_date');
        $last_tid   = (int) get_option('cos_agg_daily_batch_last_tid', 0);
        $completed  = get_option('cos_agg_daily_batch_completed', false);

        //Максимальный transaction_id за этот день (чтобы понять конец дня)
        $max_tid_in_day = (int) $wpdb->get_var(
            $wpdb->prepare("SELECT MAX(transaction_id) FROM $wallet_table WHERE DATE(date) = %s", $batch_date)
        );

        if ( !$batch_date || $completed ) return;

        // 2. Определяем таблицы
        $wallet_table        = $wpdb->prefix . 'cos_wallet_transactions';
        $user_game_table     = $wpdb->prefix . COS_Aggregation_Install::TABLE_USER_GAME_DAILY;
        $casino_daily_table  = $wpdb->prefix . COS_Aggregation_Install::TABLE_CASINO_DAILY;
        $top_winners_table   = $wpdb->prefix . COS_Aggregation_Install::TABLE_TOP_WINNERS;
        $admin_table         = $wpdb->prefix . COS_Aggregation_Install::TABLE_ADMIN_TRANSACTIONS;
        $champions_table     = $wpdb->prefix . COS_Aggregation_Install::TABLE_GAME_CHAMPIONS;
        $user_balance_table  = $wpdb->prefix . COS_Aggregation_Install::TABLE_USER_BALANCE_DAILY;

        $batch_size = defined('COS_AGG_DAILY_BATCH_SIZE') ? COS_AGG_DAILY_BATCH_SIZE : 10000;

        // 3. Выборка батча за конкретный день
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM $wallet_table WHERE transaction_id > %d AND DATE(date) = %s ORDER BY transaction_id ASC LIMIT %d",
                $last_tid, $batch_date, $batch_size
            ),
            ARRAY_A
        );

        if ( empty($rows) ) {
            // Всё обработано — завершаем
            update_option('cos_agg_daily_batch_completed', true);
            update_option('cos_agg_daily_batch_progress', 100);

            // Останавливаем батч-крон
            wp_clear_scheduled_hook('cos_agg_daily_aggregate_batch');
            error_log('COS Aggregation: Дневная агрегация за ' . $batch_date . ' завершена!');
            return;
        }

        // Сохраняем агрегирующие структуры в transient, чтобы не терять между батчами
        $agg = get_transient('cos_agg_daily_batch_tmp_' . $batch_date);
        if (!$agg) {
            $agg = [
                'user_game_day_stats' => [],
                'casino_daily_stats'  => [],
                'top_win_by_game_date' => [],
                'top_mult_by_game_date' => [],
                'admin_transactions'  => [],
                'user_last_balance'   => [],
            ];
        }

        // Все ставки (debit) по game_round_id
        $bets_by_round = [];
        foreach ($rows as $row) {
            if ($row['action'] === 'debit') {
                $bets_by_round[ $row['game_round_id'] ] = [
                    'amount'   => (int)$row['amount'],
                    'user_id'  => (int)$row['user_id'],
                    'game'     => $row['game'],
                    'provider' => $row['created_by'],
                    'date'     => date('Y-m-d', strtotime($row['date']))
                ];
            }
        }

        // --- ОБРАБОТКА БАТЧА ---
        foreach ($rows as $row) {
            $user_id   = (int) $row['user_id'];
            $game      = $row['game'];
            $provider  = $row['created_by'];
            $date      = date('Y-m-d', strtotime($row['date']));
            $action    = $row['action'];
            $amount    = (int) $row['amount'];
            $multiplier= isset($row['multiplier']) ? (float)$row['multiplier'] : 0;
            $balance   = isset($row['balance']) ? (int)$row['balance'] : null;

            // --- 0. Админские транзакции отдельно ---
            if ($game === 'Casino Administration') {
                $agg['admin_transactions'][] = [
                    'user_id'    => $user_id,
                    'action'     => $action,
                    'status'     => $row['status'],
                    'details'    => $row['details'],
                    'created_by' => $provider,
                    'amount'     => $amount,
                    'date'       => $row['date'],
                ];
                continue;
            }

            // --- 1. User-Game-Day агрегаты ---
            $key = "{$user_id}|{$game}|{$provider}|{$date}";
            if ( !isset($agg['user_game_day_stats'][$key]) ) {
                $agg['user_game_day_stats'][$key] = [
                    'user_id'   => $user_id,
                    'game'      => $game,
                    'provider'  => $provider,
                    'date'      => $date,
                    'spins'     => 0,
                    'total_bet' => 0,
                    'total_win' => 0,
                    'net_win'   => 0,
                    'max_win'   => 0,
                    'max_multiplier' => 0,
                    'rtp'       => 0,
                ];
            }
            if ( $action === 'debit' ) {
                $agg['user_game_day_stats'][$key]['spins']++;
                $agg['user_game_day_stats'][$key]['total_bet'] += $amount;
                $agg['user_game_day_stats'][$key]['net_win']   -= $amount;
            }
            if ( $action === 'credit' ) {
                $agg['user_game_day_stats'][$key]['total_win'] += $amount;
                $agg['user_game_day_stats'][$key]['net_win']   += $amount;
                $agg['user_game_day_stats'][$key]['max_win'] = max($agg['user_game_day_stats'][$key]['max_win'], $amount);
                $agg['user_game_day_stats'][$key]['max_multiplier'] = max($agg['user_game_day_stats'][$key]['max_multiplier'], $multiplier);
            }

            // --- 2. Casino-Daily агрегаты ---
            if ( !isset($agg['casino_daily_stats'][$date]) ) {
                $agg['casino_daily_stats'][$date] = [
                    'date'        => $date,
                    'total_spins' => 0,
                    'total_bet'   => 0,
                    'total_win'   => 0,
                ];
            }
            if ( $action === 'debit' ) {
                $agg['casino_daily_stats'][$date]['total_spins']++;
                $agg['casino_daily_stats'][$date]['total_bet'] += $amount;
            }
            if ( $action === 'credit' ) {
                $agg['casino_daily_stats'][$date]['total_win'] += $amount;
            }

            // --- 3. Top по win (по игре за день) ---
            if ( $action === 'credit' ) {
                $key_tw = "{$game}|{$date}";
                $bet = 0;
                if (!empty($row['game_round_id']) && isset($bets_by_round[$row['game_round_id']])) {
                    $bet = $bets_by_round[$row['game_round_id']]['amount'];
                }
                if (
                    !isset($agg['top_win_by_game_date'][$key_tw]) ||
                    $amount > $agg['top_win_by_game_date'][$key_tw]['win_amount']
                ) {
                    $agg['top_win_by_game_date'][$key_tw] = [
                        'date'       => $date,
                        'game'       => $game,
                        'provider'   => $provider,
                        'user_id'    => $user_id,
                        'win_amount' => $amount,
                        'bet_amount' => $bet,
                        'multiplier' => $multiplier,
                        'type'       => 'win',
                    ];
                }
            }
            // --- 4. Top по max multiplier (по игре за день) ---
            if ( $action === 'credit' ) {
                $key_tm = "{$game}|{$date}";
                $bet = 0;
                if (!empty($row['game_round_id']) && isset($bets_by_round[$row['game_round_id']])) {
                    $bet = $bets_by_round[$row['game_round_id']]['amount'];
                }
                if (
                    !isset($agg['top_mult_by_game_date'][$key_tm]) ||
                    $multiplier > $agg['top_mult_by_game_date'][$key_tm]['multiplier']
                ) {
                    $agg['top_mult_by_game_date'][$key_tm] = [
                        'date'       => $date,
                        'game'       => $game,
                        'provider'   => $provider,
                        'user_id'    => $user_id,
                        'win_amount' => $amount,
                        'bet_amount' => $bet,
                        'multiplier' => $multiplier,
                        'type'       => 'multiplier',
                    ];
                }
            }

            // --- 5. Баланс по дням ---
            $balance_key = $user_id . '|' . $date;
            $agg['user_last_balance'][$balance_key] = $balance;
        }

        // Если не дошли до конца дня — сохраняем агрегаты и выходим (следующий батч)
        if ( !empty($rows) && end($rows)['transaction_id'] < $max_tid_in_day ) {
            set_transient('cos_agg_daily_batch_tmp_' . $batch_date, $agg, 2 * HOUR_IN_SECONDS);
            update_option('cos_agg_daily_batch_last_tid', end($rows)['transaction_id']);

            // --- ПРОГРЕСС ---
            // Подсчёт общего числа транзакций за этот день)
            $total_for_day = (int) $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*) FROM $wallet_table WHERE DATE(date) = %s",
                    $batch_date
                )
            );
            $progress = $total_for_day ? round((end($rows)['transaction_id'] - $last_tid) / $total_for_day * 100, 2) : 0;
            update_option('cos_agg_daily_batch_progress', $progress);
            return;
        }

        // --- ДОЗАПОЛНЕНИЕ: RTP для user_game_day ---
        foreach ($agg['user_game_day_stats'] as &$stat) {
            $stat['rtp'] = ($stat['total_bet'] > 0)
                ? round($stat['total_win'] / $stat['total_bet'], 4)
                : 0;
        }
        unset($stat);

        // --- ДОЗАПОЛНЕНИЕ: Champions (топ-3)
        $champion_rows = [];

        // 1. max_win: топ-3 по каждому game/date
        $grouped = [];
        foreach ($agg['top_win_by_game_date'] as $row) {
            $key = $row['game'] . '|' . $row['date'];
            $grouped[$key][] = $row;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['win_amount'] <=> $a['win_amount']);
            $place = 1;
            foreach ($players as $row) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $row['date'],
                    'game'       => $row['game'],
                    'provider'   => $row['provider'],
                    'type'       => 'max_win',
                    'place'      => $place,
                    'user_id'    => $row['user_id'],
                    'win_amount' => $row['win_amount'],
                    'bet_amount' => $row['bet_amount'],
                    'multiplier' => $row['multiplier'],
                    'spins'      => null,
                ];
                $place++;
            }
        }

        // 2. max_multiplier: топ-3 по каждому game/date
        $grouped = [];
        foreach ($agg['top_mult_by_game_date'] as $row) {
            $key = $row['game'] . '|' . $row['date'];
            $grouped[$key][] = $row;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['multiplier'] <=> $a['multiplier']);
            $place = 1;
            foreach ($players as $row) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $row['date'],
                    'game'       => $row['game'],
                    'provider'   => $row['provider'],
                    'type'       => 'max_multiplier',
                    'place'      => $place,
                    'user_id'    => $row['user_id'],
                    'win_amount' => $row['win_amount'],
                    'bet_amount' => $row['bet_amount'],
                    'multiplier' => $row['multiplier'],
                    'spins'      => null,
                ];
                $place++;
            }
        }

        // 3. total_bets: топ-3 по каждому game/date из user_game_day_stats
        $grouped = [];
        foreach ($agg['user_game_day_stats'] as $stat) {
            $key = $stat['game'] . '|' . $stat['date'];
            $grouped[$key][] = $stat;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['total_bet'] <=> $a['total_bet']);
            $place = 1;
            foreach ($players as $stat) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $stat['date'],
                    'game'       => $stat['game'],
                    'provider'   => $stat['provider'],
                    'type'       => 'total_bets',
                    'place'      => $place,
                    'user_id'    => $stat['user_id'],
                    'win_amount' => $stat['max_win'],
                    'bet_amount' => $stat['total_bet'],
                    'multiplier' => $stat['max_multiplier'],
                    'spins'      => $stat['spins'],
                ];
                $place++;
            }
        }

        // 4. total_spins: топ-3 по каждому game/date из user_game_day_stats
        $grouped = [];
        foreach ($agg['user_game_day_stats'] as $stat) {
            $key = $stat['game'] . '|' . $stat['date'];
            $grouped[$key][] = $stat;
        }
        foreach ($grouped as $key => $players) {
            usort($players, fn($a, $b) => $b['spins'] <=> $a['spins']);
            $place = 1;
            foreach ($players as $stat) {
                if ($place > 3) break;
                $champion_rows[] = [
                    'date'       => $stat['date'],
                    'game'       => $stat['game'],
                    'provider'   => $stat['provider'],
                    'type'       => 'total_spins',
                    'place'      => $place,
                    'user_id'    => $stat['user_id'],
                    'win_amount' => $stat['max_win'],
                    'bet_amount' => $stat['total_bet'],
                    'multiplier' => $stat['max_multiplier'],
                    'spins'      => $stat['spins'],
                ];
                $place++;
            }
        }

        // --- ВСТАВКА В ТАБЛИЦЫ ---
        foreach ($agg['user_game_day_stats'] as $stat) {
            $wpdb->replace($user_game_table, $stat);
        }
        foreach ($agg['casino_daily_stats'] as $stat) {
            $wpdb->replace($casino_daily_table, $stat);
        }
        foreach ($agg['top_win_by_game_date'] as $row) {
            $wpdb->replace($top_winners_table, $row);
        }
        foreach ($agg['top_mult_by_game_date'] as $row) {
            $wpdb->replace($top_winners_table, $row);
        }
        foreach ($agg['admin_transactions'] as $row) {
            $wpdb->insert($admin_table, $row);
        }
        foreach ($champion_rows as $row) {
            $wpdb->replace($champions_table, $row);
        }
        foreach ($agg['user_last_balance'] as $key => $end_balance) {
            list($user_id, $date) = explode('|', $key);
            if ($end_balance !== null) {
                $wpdb->replace($user_balance_table, [
                    'user_id'     => $user_id,
                    'date'        => $date,
                    'end_balance' => $end_balance,
                ]);
            }
        }

        // Очищаем временный агрегат, сбрасываем last_tid
        delete_transient('cos_agg_daily_batch_tmp_' . $batch_date);
        update_option('cos_agg_daily_batch_last_tid', 0);
        update_option('cos_agg_daily_batch_completed', true);
        update_option('cos_agg_daily_batch_progress', 100);
        error_log('COS Aggregation: daily-aggregate завершена '.$batch_date );
    }




    /**
     * Массовая батчевая очистка основной таблицы (по 10000 за раз)
     */
    public static function mass_cleanup_batch($batch_size = 10000) {
        global $wpdb;
        $wallet_table = $wpdb->prefix . 'cos_wallet_transactions';
        $keep_days = 60;
        $border_date = date('Y-m-d', strtotime("-$keep_days days", current_time('timestamp')));

        // Если очистка не активна — ничего не делаем
        if ( ! get_option('cos_agg_cleanup_active') ) return;

        // Определим сколько всего надо удалить (только при первом запуске)
        if ( get_option('cos_agg_cleanup_total_to_delete', 0) <= 0 ) {
            $total_to_delete = $wpdb->get_var(
                $wpdb->prepare("SELECT COUNT(*) FROM $wallet_table WHERE date < %s", $border_date)
            );
            update_option('cos_agg_cleanup_total_to_delete', (int)$total_to_delete);
            update_option('cos_agg_cleanup_deleted_count', 0);
            update_option('cos_agg_cleanup_progress', 0);
        }

        // Удаляем batch_size строк
        $deleted = $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM $wallet_table WHERE date < %s LIMIT %d", $border_date, $batch_size
            )
        );
        // Обновляем прогресс
        $deleted_total = get_option('cos_agg_cleanup_deleted_count', 0) + $deleted;
        $to_delete = (int)get_option('cos_agg_cleanup_total_to_delete', 0);

        $progress = $to_delete > 0 ? round($deleted_total / $to_delete * 100, 1) : 100;
        $progress = min($progress, 100);

        update_option('cos_agg_cleanup_deleted_count', $deleted_total);
        update_option('cos_agg_cleanup_progress', $progress);
        update_option('cos_agg_cleanup_last_run', current_time('mysql'));

        // Если больше нечего удалять — завершаем процесс!
        $left = $wpdb->get_var(
            $wpdb->prepare("SELECT COUNT(*) FROM $wallet_table WHERE date < %s", $border_date)
        );
        if ( $left == 0 ) {
            update_option('cos_agg_cleanup_active', false);
            update_option('cos_agg_cleanup_progress', 100);
            self::unschedule_mass_cleanup_cron();
            COS_Aggregation_Helpers::add_notice('Массовая очистка завершена!', 'success');
        }
    }



}