<?php

/**
 * 文件功能：猜谜活动回合服务
 *
 * 统一处理题型兼容、房间范围、自动出题、超时结算与公屏公告，
 * 避免控制器与定时任务各自维护一套猜谜活动逻辑。
 */

namespace App\Services;

use App\Events\MessageSent;
use App\Events\RiddleGameStarted;
use App\Models\GameConfig;
use App\Models\Riddle;
use App\Models\RiddleGameRound;
use App\Models\Room;

/**
 * 类功能：提供猜谜活动的配置读取、出题、过期结算与公告能力。
 */
class RiddleGameService
{
    /**
     * 方法功能：注入聊天室状态服务，复用现有公屏消息推送链路。
     */
    public function __construct(
        private readonly ChatStateService $chatState,
    ) {}

    /**
     * 方法功能：读取指定题型的完整配置，并兼容旧版平铺参数。
     *
     * @return array{reward_gold:int,reward_exp:int,expire_minutes:int,auto_start_interval:int,room_mode:string,room_ids:array<int, int>}
     */
    public function getTypeConfig(?string $quizType = null): array
    {
        $normalizedQuizType = $this->normalizeQuizType($quizType);
        $config = GameConfig::forGame(Riddle::TYPE_IDIOM) ?? GameConfig::forGame($normalizedQuizType);
        $params = $config?->params ?? [];
        $typeConfig = (array) (($params['type_configs'] ?? [])[$normalizedQuizType] ?? []);
        $sharedRoomIds = $this->normalizeRoomIds(
            $params['room_ids']
                ?? (($params['room_id'] ?? null) !== null ? [$params['room_id']] : [])
        );
        $roomMode = (string) ($params['room_scope_mode'] ?? ($typeConfig['room_mode'] ?? 'single'));

        if (! in_array($roomMode, ['all', 'single', 'multiple'], true)) {
            $roomMode = 'single';
        }

        $roomIds = $sharedRoomIds !== []
            ? $sharedRoomIds
            : $this->normalizeRoomIds($typeConfig['room_ids'] ?? [1]);

        return [
            'reward_gold' => max(0, (int) ($params['reward_gold'] ?? ($typeConfig['reward_gold'] ?? 50))),
            'reward_exp' => max(0, (int) ($params['reward_exp'] ?? ($typeConfig['reward_exp'] ?? 30))),
            'expire_minutes' => max(0, (int) ($params['expire_minutes'] ?? ($typeConfig['expire_minutes'] ?? 5))),
            'auto_start_interval' => max(0, (int) ($params['auto_start_interval'] ?? ($typeConfig['auto_start_interval'] ?? 0))),
            'room_mode' => $roomMode,
            'room_ids' => $roomIds,
        ];
    }

    /**
     * 方法功能：读取题目有效时长配置，单位分钟。
     */
    public function getExpireMinutes(?string $quizType = null): int
    {
        return $this->getTypeConfig($quizType)['expire_minutes'];
    }

    /**
     * 方法功能：读取自动出题间隔配置，单位分钟。
     */
    public function getAutoStartInterval(?string $quizType = null): int
    {
        return $this->getTypeConfig($quizType)['auto_start_interval'];
    }

    /**
     * 方法功能：读取答题奖励配置。
     *
     * @return array{reward_gold:int,reward_exp:int}
     */
    public function getRewardConfig(?string $quizType = null): array
    {
        $typeConfig = $this->getTypeConfig($quizType);

        return [
            'reward_gold' => $typeConfig['reward_gold'],
            'reward_exp' => $typeConfig['reward_exp'],
        ];
    }

    /**
     * 方法功能：将外部传入的题型归一化为系统支持值。
     */
    public function normalizeQuizType(?string $quizType): string
    {
        $normalizedType = trim((string) $quizType);

        return Riddle::isSupportedType($normalizedType)
            ? $normalizedType
            : Riddle::TYPE_IDIOM;
    }

    /**
     * 方法功能：返回题型对应的中文名称。
     */
    public function getQuizTypeLabel(string $quizType): string
    {
        return Riddle::labelForType($this->normalizeQuizType($quizType));
    }

    /**
     * 方法功能：读取自动出题的房间范围模式。
     */
    public function getRoomScopeMode(?string $quizType = null): string
    {
        return $this->getTypeConfig($quizType)['room_mode'];
    }

    /**
     * 方法功能：读取自动出题允许覆盖的房间列表。
     *
     * @return array<int, int>
     */
    public function getScopedRoomIds(?string $quizType = null): array
    {
        $typeConfig = $this->getTypeConfig($quizType);
        $mode = $typeConfig['room_mode'];
        $configuredRoomIds = $typeConfig['room_ids'];

        if ($mode === 'all') {
            return Room::query()->orderBy('id')->pluck('id')->map(fn (mixed $id): int => (int) $id)->all();
        }

        if ($mode === 'single') {
            return array_slice($configuredRoomIds !== [] ? $configuredRoomIds : [1], 0, 1);
        }

        return $configuredRoomIds !== [] ? $configuredRoomIds : [1];
    }

    /**
     * 方法功能：判断指定回合是否已经超过有效时长。
     */
    public function isRoundExpired(RiddleGameRound $round): bool
    {
        $expireMinutes = $this->getExpireMinutes($round->quiz_type);
        if ($expireMinutes <= 0) {
            return false;
        }

        if (! in_array($round->status, ['pending', 'active'], true)) {
            return false;
        }

        if (! $round->started_at) {
            return false;
        }

        return $round->started_at->copy()->addMinutes($expireMinutes)->lte(now());
    }

    /**
     * 方法功能：结算并结束已过期的回合，必要时发送超时公告。
     */
    public function expireRound(RiddleGameRound $round, bool $announce = true): bool
    {
        if (! $this->isRoundExpired($round)) {
            return false;
        }

        $round->loadMissing('idiom');

        // 已过期回合统一落为 ended，防止继续答题或阻塞新开题。
        $round->update([
            'status' => 'ended',
            'ended_at' => $round->ended_at ?? now(),
        ]);

        if ($announce) {
            $this->pushExpiredRoundMessage($round);
        }

        return true;
    }

    /**
     * 方法功能：批量清理指定房间内已超时但仍处于进行中的回合。
     */
    public function expireActiveRoundsForRoom(int $roomId, bool $announce = true, ?string $quizType = null): int
    {
        $expiredCount = 0;
        $normalizedQuizType = $quizType !== null ? $this->normalizeQuizType($quizType) : null;

        RiddleGameRound::with('idiom')
            ->where('room_id', $roomId)
            ->when(
                $normalizedQuizType !== null,
                fn ($query) => $query->where('quiz_type', $normalizedQuizType),
            )
            ->whereIn('status', ['pending', 'active'])
            ->orderBy('id')
            ->get()
            ->each(function (RiddleGameRound $round) use ($announce, &$expiredCount): void {
                if ($this->expireRound($round, $announce)) {
                    $expiredCount++;
                }
            });

        return $expiredCount;
    }

    /**
     * 方法功能：手动结束指定房间指定题型的所有进行中回合。
     */
    public function endActiveRoundsForRoom(int $roomId, ?string $quizType = null): int
    {
        $endedCount = 0;
        $normalizedQuizType = $quizType !== null ? $this->normalizeQuizType($quizType) : null;

        RiddleGameRound::query()
            ->where('room_id', $roomId)
            ->when(
                $normalizedQuizType !== null,
                fn ($query) => $query->where('quiz_type', $normalizedQuizType),
            )
            ->whereIn('status', ['pending', 'active'])
            ->orderBy('id')
            ->get()
            ->each(function (RiddleGameRound $round) use (&$endedCount): void {
                // 手动出题覆盖旧题时，直接结束旧回合，不再额外发超时公告。
                $round->update([
                    'status' => 'ended',
                    'ended_at' => $round->ended_at ?? now(),
                ]);
                $endedCount++;
            });

        return $endedCount;
    }

    /**
     * 方法功能：为指定房间和题型创建一轮新题。
     */
    public function startRound(int $roomId, ?string $quizType = null): ?RiddleGameRound
    {
        $normalizedQuizType = $this->normalizeQuizType($quizType);

        if (! $this->isGameEnabled($normalizedQuizType)) {
            return null;
        }

        // 先清理同房间同题型的过期回合，避免旧记录卡住新题。
        $this->expireActiveRoundsForRoom($roomId, true, $normalizedQuizType);

        if ($this->findActiveRound($roomId, $normalizedQuizType)) {
            return null;
        }

        $idiom = $this->pickRandomQuestion($normalizedQuizType);
        if (! $idiom) {
            return null;
        }

        $rewardConfig = $this->getRewardConfig($normalizedQuizType);

        // 新回合显式记录 quiz_type，保证房间与题型维度都能独立判定。
        $round = RiddleGameRound::create([
            'room_id' => $roomId,
            'idiom_id' => $idiom->id,
            'quiz_type' => $normalizedQuizType,
            'status' => 'active',
            'reward_gold' => $rewardConfig['reward_gold'],
            'reward_exp' => $rewardConfig['reward_exp'],
            'started_at' => now(),
        ]);

        $round->setRelation('idiom', $idiom);
        $this->broadcastStartedRound($round);

        return $round;
    }

    /**
     * 方法功能：按配置范围自动为各房间各题型尝试开题。
     */
    public function autoStartEligibleRounds(): int
    {
        $startedCount = 0;

        foreach (Riddle::supportedTypes() as $quizType) {
            $interval = $this->getAutoStartInterval($quizType);
            if ($interval <= 0) {
                continue;
            }

            foreach ($this->getScopedRoomIds($quizType) as $roomId) {
                // 房间与题型维度独立结算过期回合，互不干扰。
                $this->expireActiveRoundsForRoom($roomId, true, $quizType);

                if ($this->findActiveRound($roomId, $quizType)) {
                    continue;
                }

                if (! $this->hasReachedAutoStartInterval($roomId, $quizType, $interval)) {
                    continue;
                }

                if (! $this->pickRandomQuestion($quizType)) {
                    continue;
                }

                if ($this->startRound($roomId, $quizType)) {
                    $startedCount++;
                }
            }
        }

        return $startedCount;
    }

    /**
     * 方法功能：查询指定房间指定题型的进行中回合。
     */
    public function findActiveRound(int $roomId, ?string $quizType = null): ?RiddleGameRound
    {
        return RiddleGameRound::query()
            ->with('idiom')
            ->where('room_id', $roomId)
            ->where('quiz_type', $this->normalizeQuizType($quizType))
            ->whereIn('status', ['pending', 'active'])
            ->first();
    }

    /**
     * 方法功能：随机抽取一条启用中的题目。
     */
    public function pickRandomQuestion(?string $quizType = null): ?Riddle
    {
        return Riddle::query()
            ->where('type', $this->normalizeQuizType($quizType))
            ->where('is_active', true)
            ->inRandomOrder()
            ->first();
    }

    /**
     * 方法功能：生成答题奖励日志文案。
     */
    public function buildRewardDescription(RiddleGameRound $round): string
    {
        $quizTypeLabel = $this->getQuizTypeLabel($round->quiz_type);

        return "猜谜活动{$quizTypeLabel}答对「{$round->idiom?->answer}」奖励";
    }

    /**
     * 方法功能：向公屏推送回合超时公告。
     */
    public function pushExpiredRoundMessage(RiddleGameRound $round): void
    {
        $answer = $round->idiom?->answer ?? '未知答案';
        $quizTitle = Riddle::activityLabelForType($round->quiz_type);
        $message = [
            'id' => $this->chatState->nextMessageId($round->room_id),
            'room_id' => $round->room_id,
            'from_user' => '系统传音',
            'to_user' => '大家',
            'content' => "⏳ 【{$quizTitle}】第 #{$round->id} 题已超时结束！正确答案：{$answer}",
            'is_secret' => false,
            'font_color' => '#d97706',
            'action' => '',
            'quiz_type' => $this->normalizeQuizType($round->quiz_type),
            'quiz_type_label' => $this->getQuizTypeLabel($round->quiz_type),
            'quiz_round_id' => $round->id,
            'quiz_round_ended_id' => $round->id,
            'quiz_answer' => $answer,
            'idiom_game_round_ended_id' => $round->id,
            'sent_at' => now()->toDateTimeString(),
        ];

        $this->chatState->pushMessage($round->room_id, $message);
        broadcast(new MessageSent($round->room_id, $message));
    }

    /**
     * 方法功能：广播新回合开始事件并同步写入公屏消息。
     */
    public function broadcastStartedRound(RiddleGameRound $round): void
    {
        $round->loadMissing('idiom');

        broadcast(new RiddleGameStarted(
            roomId: $round->room_id,
            quizType: $round->quiz_type,
            hint: $round->idiom?->hint ?? '',
            roundId: $round->id,
            rewardGold: $round->reward_gold,
            rewardExp: $round->reward_exp,
        ));

        $message = [
            'id' => $this->chatState->nextMessageId($round->room_id),
            'room_id' => $round->room_id,
            'from_user' => '系统传音',
            'to_user' => '大家',
            'content' => $this->buildStartMessage($round->quiz_type, $round->id, $round->idiom?->hint ?? ''),
            'is_secret' => false,
            'font_color' => '#b91c1c',
            'action' => '',
            'quiz_type' => $this->normalizeQuizType($round->quiz_type),
            'quiz_type_label' => $this->getQuizTypeLabel($round->quiz_type),
            'quiz_round_id' => $round->id,
            'quiz_hint' => $round->idiom?->hint ?? '',
            'quiz_reward_gold' => $round->reward_gold,
            'quiz_reward_exp' => $round->reward_exp,
            'idiom_game_round_id' => $round->id,
            'idiom_reward_gold' => $round->reward_gold,
            'idiom_reward_exp' => $round->reward_exp,
            'sent_at' => now()->toDateTimeString(),
        ];

        $this->chatState->pushMessage($round->room_id, $message);
        broadcast(new MessageSent($round->room_id, $message));
    }

    /**
     * 方法功能：判断指定房间指定题型是否已到自动开题间隔。
     */
    private function hasReachedAutoStartInterval(int $roomId, string $quizType, int $interval): bool
    {
        $lastRound = RiddleGameRound::query()
            ->where('room_id', $roomId)
            ->where('quiz_type', $this->normalizeQuizType($quizType))
            ->latest()
            ->first();

        if (! $lastRound) {
            return true;
        }

        $lastTime = $lastRound->ended_at ?? $lastRound->started_at ?? $lastRound->created_at;

        return ! $lastTime || $lastTime->diffInMinutes(now()) >= $interval;
    }

    /**
     * 方法功能：把 room_ids 配置归一化为整型数组。
     *
     * @return array<int, int>
     */
    private function normalizeRoomIds(mixed $roomIds): array
    {
        $items = is_array($roomIds)
            ? $roomIds
            : preg_split('/[\s,，]+/u', (string) $roomIds, -1, PREG_SPLIT_NO_EMPTY);

        return collect($items)
            ->map(fn (mixed $roomId): int => (int) $roomId)
            ->filter(fn (int $roomId): bool => $roomId > 0)
            ->unique()
            ->values()
            ->all();
    }

    /**
     * 方法功能：返回题型对应的活动标题。
     */
    private function buildStartMessage(string $quizType, int $roundId, string $hint): string
    {
        $normalizedQuizType = $this->normalizeQuizType($quizType);
        $quizLabel = $this->getQuizTypeLabel($normalizedQuizType);
        $icon = $normalizedQuizType === Riddle::TYPE_BRAIN_TEASER ? '🧠' : '🧩';

        return "{$icon} 【猜谜活动·{$quizLabel}】第 #{$roundId} 题开始！题面：{$hint}";
    }

    /**
     * 方法功能：判断猜谜活动总开关是否处于启用状态。
     */
    private function isGameEnabled(?string $quizType = null): bool
    {
        $normalizedQuizType = $this->normalizeQuizType($quizType);
        $config = GameConfig::forGame($normalizedQuizType) ?? GameConfig::forGame(Riddle::TYPE_IDIOM);

        return (bool) $config?->enabled;
    }
}
