<?php

declare(strict_types=1);

namespace app\common\service;

use app\common\library\game\StreakWinReward;
use support\think\Db;

/**
 * 移动端 WebSocket：仅推送当前玩家本局适用赔率（非 streak_win_reward 全表）。
 */
final class GameWebSocketPayloadHelper
{
    public const TOPIC_USER_STREAK = 'user.streak';

    /** @var list<string> */
    public const ODDS_PUSH_TOPICS = [
        'user.streak',
        'wallet.changed',
        'bet.accepted',
    ];

    /**
     * @return array{user_id: int, current_streak: int, streak_level: int, odds_factor: int, is_jackpot: bool}
     */
    public static function userStreakData(int $userId, ?int $currentStreak = null): array
    {
        if ($userId <= 0) {
            return [
                'user_id' => 0,
                'current_streak' => 0,
                'streak_level' => 1,
                'odds_factor' => 1,
                'is_jackpot' => false,
            ];
        }
        if ($currentStreak === null) {
            $row = GameHotDataRedis::userRow($userId);
            $raw = $row['current_streak'] ?? 0;
            $parsed = filter_var($raw, FILTER_VALIDATE_INT);
            $currentStreak = $parsed === false ? 0 : $parsed;
        }

        $odds = StreakWinReward::playerBetOddsForCurrentStreak($currentStreak);

        return [
            'user_id' => $userId,
            'current_streak' => $odds['current_streak'],
            'streak_level' => $odds['streak_level'],
            'odds_factor' => $odds['odds_factor'],
            'is_jackpot' => $odds['is_jackpot'],
        ];
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    public static function mergeUserStreakInto(array $payload, int $userId, ?int $currentStreak = null): array
    {
        if ($userId <= 0) {
            return $payload;
        }

        return array_merge($payload, self::userStreakData($userId, $currentStreak));
    }

    public static function publishUserStreak(int $userId, ?int $currentStreak = null): void
    {
        if ($userId <= 0) {
            return;
        }
        GameWebSocketEventBus::publish(self::TOPIC_USER_STREAK, self::userStreakData($userId, $currentStreak));
    }

    /**
     * 后台 WebSocket 联调：从库内选取样例玩家（优先 current_streak 最高）。
     *
     * @return array<string, mixed>|null
     */
    public static function pickAdminTestUserRow(): ?array
    {
        $fields = ['id', 'username', 'uuid', 'phone', 'current_streak', 'coin'];
        $row = Db::name('user')
            ->where('status', 1)
            ->order('current_streak', 'desc')
            ->order('id', 'asc')
            ->field($fields)
            ->find();
        if (is_array($row) && !empty($row['id'])) {
            return $row;
        }
        $fallback = Db::name('user')
            ->order('id', 'asc')
            ->field($fields)
            ->find();

        return is_array($fallback) && !empty($fallback['id']) ? $fallback : null;
    }

    /**
     * wsConfig 与订阅后演示推送共用的玩家赔率快照。
     *
     * @return array<string, mixed>
     */
    public static function adminTestPlayerOddsSnapshot(): array
    {
        $row = self::pickAdminTestUserRow();
        if ($row === null) {
            $demoStreak = 3;
            $odds = StreakWinReward::playerBetOddsForCurrentStreak($demoStreak);

            return array_merge([
                'is_test' => true,
                'preview' => true,
                'user_id' => 0,
                'username' => '演示玩家（库内无用户）',
                'uuid' => '',
                'phone' => '',
                'coin' => '0.00',
                'source' => 'synthetic',
            ], $odds);
        }

        $userIdRaw = $row['id'] ?? 0;
        $userIdParsed = filter_var($userIdRaw, FILTER_VALIDATE_INT);
        $userId = $userIdParsed === false ? 0 : $userIdParsed;
        $streakRaw = $row['current_streak'] ?? 0;
        $streakParsed = filter_var($streakRaw, FILTER_VALIDATE_INT);
        $currentStreak = $streakParsed === false ? 0 : $streakParsed;
        $odds = StreakWinReward::playerBetOddsForCurrentStreak($currentStreak);

        return array_merge([
            'is_test' => true,
            'preview' => true,
            'user_id' => $userId,
            'username' => (string) ($row['username'] ?? ''),
            'uuid' => (string) ($row['uuid'] ?? ''),
            'phone' => (string) ($row['phone'] ?? ''),
            'coin' => (string) ($row['coin'] ?? '0.00'),
            'source' => 'db_user',
        ], $odds);
    }

    /**
     * 后台测试连接订阅赔率主题后，推送演示帧（非真实业务事件）。
     *
     * @param list<string> $subscribedTopics
     * @return list<array{topic: string, event: string, data: array<string, mixed>}>
     */
    public static function adminTestPushFrames(array $subscribedTopics): array
    {
        $snapshot = self::adminTestPlayerOddsSnapshot();
        $userIdRaw = $snapshot['user_id'] ?? 0;
        $userIdParsed = filter_var($userIdRaw, FILTER_VALIDATE_INT);
        $userId = $userIdParsed === false ? 0 : $userIdParsed;
        $streakRaw = $snapshot['current_streak'] ?? 0;
        $streakParsed = filter_var($streakRaw, FILTER_VALIDATE_INT);
        $currentStreak = $streakParsed === false ? 0 : $streakParsed;

        $topicSet = [];
        foreach ($subscribedTopics as $topic) {
            if (!is_string($topic)) {
                continue;
            }
            $value = trim($topic);
            if ($value !== '') {
                $topicSet[$value] = true;
            }
        }

        $frames = [];
        if (isset($topicSet[self::TOPIC_USER_STREAK])) {
            $frames[] = [
                'topic' => self::TOPIC_USER_STREAK,
                'event' => self::TOPIC_USER_STREAK,
                'data' => $snapshot,
            ];
        }
        if (isset($topicSet['wallet.changed'])) {
            $frames[] = [
                'topic' => 'wallet.changed',
                'event' => 'wallet.changed',
                'data' => self::mergeOddsFieldsFromSnapshot([
                    'is_test' => true,
                    'preview' => true,
                    'user_id' => $userId,
                    'balance_after' => (string) ($snapshot['coin'] ?? '0.00'),
                    'biz_type' => 'admin_test_preview',
                    'changed_at' => time(),
                ], $snapshot),
            ];
        }
        if (isset($topicSet['bet.accepted'])) {
            $frames[] = [
                'topic' => 'bet.accepted',
                'event' => 'bet.accepted',
                'data' => self::mergeOddsFieldsFromSnapshot([
                    'is_test' => true,
                    'preview' => true,
                    'user_id' => $userId,
                    'period_no' => 'ADMIN-TEST-PREVIEW',
                    'numbers' => [1, 2, 3],
                    'bet_id' => 1,
                    'single_bet_amount' => '1.00',
                    'numbers_count' => 3,
                    'total_amount' => '3.00',
                    'balance_after' => (string) ($snapshot['coin'] ?? '0.00'),
                    'accepted_at' => time(),
                ], $snapshot),
            ];
        }

        return $frames;
    }

    /**
     * @param array<string, mixed> $payload
     * @param array<string, mixed> $snapshot
     * @return array<string, mixed>
     */
    private static function mergeOddsFieldsFromSnapshot(array $payload, array $snapshot): array
    {
        return array_merge($payload, [
            'current_streak' => $snapshot['current_streak'] ?? 0,
            'streak_level' => $snapshot['streak_level'] ?? 1,
            'odds_factor' => $snapshot['odds_factor'] ?? 1,
            'is_jackpot' => ($snapshot['is_jackpot'] ?? false) === true,
        ]);
    }
}
