<?php
declare(strict_types=1);

/**
 * /functional/games/_lib.php
 *
 * Відповідає за:
 * - серверний баланс фішок рулетки
 * - журнал операцій з фішками
 * - обмін VM Coins <-> фішки
 * - серверний spin рулетки без localStorage
 *
 * Функції файла:
 * - перевірка схеми рулетки
 * - читання балансу фішок
 * - серверне списання/зарахування фішок
 * - читання історії рулетки
 * - обмін coins -> chips
 * - обмін chips -> coins
 * - адмін-поповнення фішок
 * - серверне визначення результату обертання
 */

if (!function_exists('games_ident_ok')) {
    function games_ident_ok(string $s): bool
    {
        return (bool)preg_match('/^[a-zA-Z0-9_]+$/', $s);
    }
}

if (!function_exists('games_table_exists')) {
    function games_table_exists(PDO $pdo, string $table): bool
    {
        if (!games_ident_ok($table)) {
            return false;
        }

        try {
            $st = $pdo->prepare("
                SELECT COUNT(*)
                FROM information_schema.tables
                WHERE table_schema = DATABASE()
                  AND table_name = ?
            ");
            $st->execute([$table]);
            return ((int)$st->fetchColumn()) > 0;
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('games_json_decode_assoc')) {
    function games_json_decode_assoc(?string $json): array
    {
        $json = trim((string)$json);
        if ($json === '') {
            return [];
        }

        try {
            $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
            return is_array($data) ? $data : [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('games_random_suffix')) {
    function games_random_suffix(int $bytes = 8): string
    {
        try {
            return bin2hex(random_bytes(max(4, $bytes)));
        } catch (Throwable $e) {
            return sha1((string)microtime(true) . ':' . mt_rand());
        }
    }
}

if (!function_exists('games_roulette_schema_ready')) {
    function games_roulette_schema_ready(PDO $pdo): bool
    {
        return games_table_exists($pdo, 'game_roulette_wallets')
            && games_table_exists($pdo, 'game_roulette_transactions');
    }
}

if (!function_exists('games_roulette_find_tx_by_op_key')) {
    function games_roulette_find_tx_by_op_key(PDO $pdo, string $opKey): ?array
    {
        $opKey = trim($opKey);
        if ($opKey === '' || !games_roulette_schema_ready($pdo)) {
            return null;
        }

        try {
            $st = $pdo->prepare("
                SELECT id, user_id, delta_chips, chips_after, event_key, op_key, note, meta_json, created_at
                FROM game_roulette_transactions
                WHERE op_key = ?
                LIMIT 1
            ");
            $st->execute([$opKey]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('games_roulette_lock_wallet_row')) {
    function games_roulette_lock_wallet_row(PDO $pdo, int $userId): ?array
    {
        if ($userId <= 0 || !games_roulette_schema_ready($pdo)) {
            return null;
        }

        $ins = $pdo->prepare("
            INSERT INTO game_roulette_wallets (user_id, chips_balance, created_at, updated_at)
            VALUES (?, 0, NOW(), NOW())
            ON DUPLICATE KEY UPDATE user_id = user_id
        ");
        $ins->execute([$userId]);

        $st = $pdo->prepare("
            SELECT user_id, chips_balance, created_at, updated_at
            FROM game_roulette_wallets
            WHERE user_id = ?
            LIMIT 1
            FOR UPDATE
        ");
        $st->execute([$userId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);

        return $row ?: null;
    }
}

if (!function_exists('games_roulette_get_balance')) {
    function games_roulette_get_balance(PDO $pdo, int $userId): int
    {
        if ($userId <= 0 || !games_roulette_schema_ready($pdo)) {
            return 0;
        }

        try {
            $ins = $pdo->prepare("
                INSERT INTO game_roulette_wallets (user_id, chips_balance, created_at, updated_at)
                VALUES (?, 0, NOW(), NOW())
                ON DUPLICATE KEY UPDATE user_id = user_id
            ");
            $ins->execute([$userId]);

            $st = $pdo->prepare("
                SELECT chips_balance
                FROM game_roulette_wallets
                WHERE user_id = ?
                LIMIT 1
            ");
            $st->execute([$userId]);
            $val = $st->fetchColumn();

            return ($val === false) ? 0 : (int)$val;
        } catch (Throwable $e) {
            return 0;
        }
    }
}

if (!function_exists('games_roulette_state')) {
    function games_roulette_state(PDO $pdo, int $userId): array
    {
        $coinsBalance = 0;
        if (function_exists('vmc_get_balance')) {
            $coinsBalance = vmc_get_balance($pdo, $userId);
        }

        return [
            'ok' => games_roulette_schema_ready($pdo),
            'schema_ready' => games_roulette_schema_ready($pdo),
            'coins_balance' => $coinsBalance,
            'chips_balance' => games_roulette_get_balance($pdo, $userId),
        ];
    }
}

if (!function_exists('games_roulette_recent_transactions')) {
    function games_roulette_recent_transactions(PDO $pdo, int $userId, int $limit = 20): array
    {
        if ($userId <= 0 || !games_roulette_schema_ready($pdo)) {
            return [];
        }

        $limit = max(1, min(100, $limit));

        try {
            $sql = "
                SELECT
                    id,
                    user_id,
                    delta_chips,
                    chips_after,
                    event_key,
                    op_key,
                    note,
                    meta_json,
                    created_at
                FROM game_roulette_transactions
                WHERE user_id = ?
                ORDER BY id DESC
                LIMIT {$limit}
            ";
            $st = $pdo->prepare($sql);
            $st->execute([$userId]);

            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('games_roulette_add_transaction')) {
    function games_roulette_add_transaction(
        PDO $pdo,
        int $userId,
        int $deltaChips,
        string $eventKey,
        ?string $opKey = null,
        ?string $note = null,
        array $meta = []
    ): array {
        if ($userId <= 0) {
            return ['ok' => false, 'reason' => 'bad_user'];
        }

        if (!games_roulette_schema_ready($pdo)) {
            return ['ok' => false, 'reason' => 'schema_missing'];
        }

        if ($deltaChips === 0) {
            return ['ok' => false, 'reason' => 'zero_delta'];
        }

        $eventKey = trim($eventKey);
        $opKey    = $opKey !== null ? trim($opKey) : null;
        $note     = $note !== null ? trim($note) : null;

        if ($opKey !== null && $opKey !== '') {
            $existing = games_roulette_find_tx_by_op_key($pdo, $opKey);
            if ($existing) {
                return [
                    'ok' => true,
                    'duplicate' => true,
                    'tx_id' => (int)($existing['id'] ?? 0),
                    'chips_balance' => (int)($existing['chips_after'] ?? 0),
                ];
            }
        } else {
            $opKey = null;
        }

        $ownTx = !$pdo->inTransaction();

        try {
            if ($ownTx) {
                $pdo->beginTransaction();
            }

            $wallet = games_roulette_lock_wallet_row($pdo, $userId);
            if (!$wallet) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'wallet_missing'];
            }

            $current = (int)($wallet['chips_balance'] ?? 0);
            $new     = $current + $deltaChips;

            if ($new < 0) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return [
                    'ok' => false,
                    'reason' => 'insufficient_chips',
                    'chips_balance' => $current,
                ];
            }

            $metaJson = !empty($meta)
                ? json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
                : null;

            $ins = $pdo->prepare("
                INSERT INTO game_roulette_transactions
                    (user_id, delta_chips, chips_after, event_key, op_key, note, meta_json, created_at)
                VALUES
                    (?, ?, ?, ?, ?, ?, ?, NOW())
            ");
            $ins->execute([
                $userId,
                $deltaChips,
                $new,
                $eventKey,
                $opKey,
                $note,
                $metaJson,
            ]);

            $txId = (int)$pdo->lastInsertId();

            $up = $pdo->prepare("
                UPDATE game_roulette_wallets
                SET chips_balance = ?,
                    updated_at = NOW()
                WHERE user_id = ?
                LIMIT 1
            ");
            $up->execute([$new, $userId]);

            if ($ownTx && $pdo->inTransaction()) {
                $pdo->commit();
            }

            return [
                'ok' => true,
                'duplicate' => false,
                'tx_id' => $txId,
                'chips_balance' => $new,
            ];
        } catch (Throwable $e) {
            if ($ownTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            return [
                'ok' => false,
                'reason' => 'exception',
                'error' => $e->getMessage(),
            ];
        }
    }
}

if (!function_exists('games_roulette_order')) {
    function games_roulette_order(): array
    {
        return [0, 14, 3, 19, 7, 18, 9, 17, 6, 16, 8, 15, 2, 13, 1, 12, 4, 11, 5, 10, 20];
    }
}

if (!function_exists('games_roulette_num_color')) {
    function games_roulette_num_color(int $num): string
    {
        static $map = null;

        if ($map === null) {
            $map = [];
            $order = games_roulette_order();

            foreach ($order as $i => $n) {
                if ($n === 0) {
                    $map[$n] = 'green';
                } else {
                    $map[$n] = ($i % 2 === 0) ? 'red' : 'black';
                }
            }
        }

        return $map[$num] ?? 'black';
    }
}

if (!function_exists('games_roulette_spin')) {
    function games_roulette_spin(PDO $pdo, int $userId, array $bet): array
    {
        if ($userId <= 0) {
            return ['ok' => false, 'reason' => 'bad_user'];
        }

        if (!games_roulette_schema_ready($pdo)) {
            return ['ok' => false, 'reason' => 'schema_missing'];
        }

        $betType = strtolower(trim((string)($bet['type'] ?? 'color')));
        $amount  = (int)($bet['amount'] ?? 0);

        if ($amount < 1) {
            return ['ok' => false, 'reason' => 'bad_amount'];
        }

        $betColor  = null;
        $betNumber = null;

        if ($betType === 'color') {
            $betColor = strtolower(trim((string)($bet['color'] ?? '')));
            if (!in_array($betColor, ['red', 'black', 'green'], true)) {
                return ['ok' => false, 'reason' => 'invalid_bet'];
            }
        } elseif ($betType === 'number') {
            $betNumber = (int)($bet['number'] ?? -1);
            if ($betNumber < 0 || $betNumber > 20) {
                return ['ok' => false, 'reason' => 'invalid_bet'];
            }
        } else {
            return ['ok' => false, 'reason' => 'invalid_bet'];
        }

        $ownTx = !$pdo->inTransaction();

        try {
            if ($ownTx) {
                $pdo->beginTransaction();
            }

            $wallet = games_roulette_lock_wallet_row($pdo, $userId);
            if (!$wallet) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'wallet_missing'];
            }

            $currentChips = (int)($wallet['chips_balance'] ?? 0);
            if ($currentChips <= 0 || $amount > $currentChips) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return [
                    'ok' => false,
                    'reason' => 'insufficient_chips',
                    'chips_balance' => $currentChips,
                ];
            }

            $order = games_roulette_order();
            $count = count($order);
            $payoutSingle = $count - 1;

            $winIndex = random_int(0, $count - 1);
            $winNum   = (int)$order[$winIndex];
            $winColor = games_roulette_num_color($winNum);

            $delta = 0;

            if ($betType === 'color') {
                if ($betColor === $winColor) {
                    $delta = ($winColor === 'green') ? ($amount * $payoutSingle) : $amount;
                } else {
                    $delta = -$amount;
                }
            } else {
                $delta = ($betNumber === $winNum) ? ($amount * $payoutSingle) : (-$amount);
            }

            $opBase = 'roulette:spin:' . $userId . ':' . games_random_suffix(8);

            $tx = games_roulette_add_transaction(
                $pdo,
                $userId,
                $delta,
                $delta >= 0 ? 'spin_win' : 'spin_lose',
                $opBase,
                $delta >= 0 ? 'Roulette spin win' : 'Roulette spin lose',
                [
                    'bet_type' => $betType,
                    'bet_amount' => $amount,
                    'bet_color' => $betColor,
                    'bet_number' => $betNumber,
                    'win_index' => $winIndex,
                    'win_num' => $winNum,
                    'win_color' => $winColor,
                    'delta' => $delta,
                ]
            );

            if (!($tx['ok'] ?? false)) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return $tx;
            }

            $coinsBalance = function_exists('vmc_get_balance')
                ? vmc_get_balance($pdo, $userId)
                : 0;

            if ($ownTx && $pdo->inTransaction()) {
                $pdo->commit();
            }

            return [
                'ok' => true,
                'reason' => 'spun',
                'win_index' => $winIndex,
                'win_num' => $winNum,
                'win_color' => $winColor,
                'delta' => $delta,
                'chips_balance' => (int)($tx['chips_balance'] ?? 0),
                'coins_balance' => $coinsBalance,
            ];
        } catch (Throwable $e) {
            if ($ownTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            return [
                'ok' => false,
                'reason' => 'exception',
                'error' => $e->getMessage(),
            ];
        }
    }
}

if (!function_exists('games_roulette_exchange_coins_to_chips')) {
    function games_roulette_exchange_coins_to_chips(PDO $pdo, int $userId, int $coinAmount): array
    {
        if ($userId <= 0) {
            return ['ok' => false, 'reason' => 'bad_user'];
        }

        if ($coinAmount < 1) {
            return ['ok' => false, 'reason' => 'bad_amount'];
        }

        if (!games_roulette_schema_ready($pdo)) {
            return ['ok' => false, 'reason' => 'schema_missing'];
        }

        if (!function_exists('vmc_enabled') || !function_exists('vmc_add_transaction') || !function_exists('vmc_user_columns_exist')) {
            return ['ok' => false, 'reason' => 'vmcoins_lib_missing'];
        }

        if (!vmc_enabled($pdo)) {
            return ['ok' => false, 'reason' => 'disabled'];
        }

        if (!vmc_user_columns_exist($pdo)) {
            return ['ok' => false, 'reason' => 'vmcoins_schema_missing'];
        }

        $chipsAdd = $coinAmount * 10;
        $ownTx = !$pdo->inTransaction();

        try {
            if ($ownTx) {
                $pdo->beginTransaction();
            }

            $st = $pdo->prepare("
                SELECT id, vm_coins_balance
                FROM users
                WHERE id = ?
                LIMIT 1
                FOR UPDATE
            ");
            $st->execute([$userId]);
            $user = $st->fetch(PDO::FETCH_ASSOC);

            if (!$user) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'user_not_found'];
            }

            $coinsBalance = (int)($user['vm_coins_balance'] ?? 0);
            if ($coinsBalance < $coinAmount) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return [
                    'ok' => false,
                    'reason' => 'insufficient_coins',
                    'coins_balance' => $coinsBalance,
                ];
            }

            $opBase = 'roulette:ex:c2h:' . $userId . ':' . games_random_suffix(8);

            $coinTx = vmc_add_transaction(
                $pdo,
                $userId,
                -$coinAmount,
                'roulette_exchange_to_chips',
                'roulette',
                null,
                null,
                $opBase . ':coins',
                'Exchange VM Coins to roulette chips',
                [
                    'coins_spent' => $coinAmount,
                    'chips_added' => $chipsAdd,
                ]
            );

            if (!($coinTx['ok'] ?? false)) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'coins_tx_failed', 'detail' => $coinTx];
            }

            $chipTx = games_roulette_add_transaction(
                $pdo,
                $userId,
                $chipsAdd,
                'exchange_from_coins',
                $opBase . ':chips',
                'Exchange VM Coins to roulette chips',
                [
                    'coins_spent' => $coinAmount,
                    'chips_added' => $chipsAdd,
                ]
            );

            if (!($chipTx['ok'] ?? false)) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'chips_tx_failed', 'detail' => $chipTx];
            }

            if ($ownTx && $pdo->inTransaction()) {
                $pdo->commit();
            }

            return [
                'ok' => true,
                'reason' => 'exchanged',
                'coins_spent' => $coinAmount,
                'chips_added' => $chipsAdd,
                'coins_balance' => (int)($coinTx['balance'] ?? 0),
                'chips_balance' => (int)($chipTx['chips_balance'] ?? 0),
            ];
        } catch (Throwable $e) {
            if ($ownTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            return [
                'ok' => false,
                'reason' => 'exception',
                'error' => $e->getMessage(),
            ];
        }
    }
}

if (!function_exists('games_roulette_exchange_chips_to_coins')) {
    function games_roulette_exchange_chips_to_coins(PDO $pdo, int $userId, int $chipsAmount): array
    {
        if ($userId <= 0) {
            return ['ok' => false, 'reason' => 'bad_user'];
        }

        if ($chipsAmount < 15) {
            return ['ok' => false, 'reason' => 'bad_amount'];
        }

        if (($chipsAmount % 15) !== 0) {
            return ['ok' => false, 'reason' => 'bad_multiple'];
        }

        if (!games_roulette_schema_ready($pdo)) {
            return ['ok' => false, 'reason' => 'schema_missing'];
        }

        if (!function_exists('vmc_enabled') || !function_exists('vmc_add_transaction') || !function_exists('vmc_user_columns_exist')) {
            return ['ok' => false, 'reason' => 'vmcoins_lib_missing'];
        }

        if (!vmc_enabled($pdo)) {
            return ['ok' => false, 'reason' => 'disabled'];
        }

        if (!vmc_user_columns_exist($pdo)) {
            return ['ok' => false, 'reason' => 'vmcoins_schema_missing'];
        }

        $coinsAdd = intdiv($chipsAmount, 15);
        if ($coinsAdd < 1) {
            return ['ok' => false, 'reason' => 'bad_amount'];
        }

        $ownTx = !$pdo->inTransaction();

        try {
            if ($ownTx) {
                $pdo->beginTransaction();
            }

            $wallet = games_roulette_lock_wallet_row($pdo, $userId);
            if (!$wallet) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'wallet_missing'];
            }

            $chipsBalance = (int)($wallet['chips_balance'] ?? 0);
            if ($chipsBalance < $chipsAmount) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return [
                    'ok' => false,
                    'reason' => 'insufficient_chips',
                    'chips_balance' => $chipsBalance,
                ];
            }

            $opBase = 'roulette:ex:h2c:' . $userId . ':' . games_random_suffix(8);

            $chipTx = games_roulette_add_transaction(
                $pdo,
                $userId,
                -$chipsAmount,
                'exchange_to_coins',
                $opBase . ':chips',
                'Exchange roulette chips to VM Coins',
                [
                    'chips_spent' => $chipsAmount,
                    'coins_added' => $coinsAdd,
                ]
            );

            if (!($chipTx['ok'] ?? false)) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'chips_tx_failed', 'detail' => $chipTx];
            }

            $coinTx = vmc_add_transaction(
                $pdo,
                $userId,
                $coinsAdd,
                'roulette_exchange_from_chips',
                'roulette',
                null,
                null,
                $opBase . ':coins',
                'Exchange roulette chips to VM Coins',
                [
                    'chips_spent' => $chipsAmount,
                    'coins_added' => $coinsAdd,
                ]
            );

            if (!($coinTx['ok'] ?? false)) {
                if ($ownTx && $pdo->inTransaction()) {
                    $pdo->rollBack();
                }
                return ['ok' => false, 'reason' => 'coins_tx_failed', 'detail' => $coinTx];
            }

            if ($ownTx && $pdo->inTransaction()) {
                $pdo->commit();
            }

            return [
                'ok' => true,
                'reason' => 'exchanged',
                'chips_spent' => $chipsAmount,
                'coins_added' => $coinsAdd,
                'coins_balance' => (int)($coinTx['balance'] ?? 0),
                'chips_balance' => (int)($chipTx['chips_balance'] ?? 0),
            ];
        } catch (Throwable $e) {
            if ($ownTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            return [
                'ok' => false,
                'reason' => 'exception',
                'error' => $e->getMessage(),
            ];
        }
    }
}

if (!function_exists('games_roulette_admin_topup')) {
    function games_roulette_admin_topup(PDO $pdo, int $userId, int $chips = 1000): array
    {
        if ($userId <= 0) {
            return ['ok' => false, 'reason' => 'bad_user'];
        }

        if ($chips < 1) {
            return ['ok' => false, 'reason' => 'bad_amount'];
        }

        if (!games_roulette_schema_ready($pdo)) {
            return ['ok' => false, 'reason' => 'schema_missing'];
        }

        $tx = games_roulette_add_transaction(
            $pdo,
            $userId,
            $chips,
            'admin_topup',
            'roulette:topup:' . $userId . ':' . games_random_suffix(8),
            'Admin top up roulette chips',
            [
                'chips_added' => $chips,
            ]
        );

        if (!($tx['ok'] ?? false)) {
            return $tx;
        }

        $coinsBalance = function_exists('vmc_get_balance')
            ? vmc_get_balance($pdo, $userId)
            : 0;

        return [
            'ok' => true,
            'reason' => 'topup_done',
            'chips_added' => $chips,
            'chips_balance' => (int)($tx['chips_balance'] ?? 0),
            'coins_balance' => $coinsBalance,
        ];
    }
}