<?php

/**
 * 文件功能：用户成就扫描与授予服务。
 *
 * 基于聊天室已有日志表聚合用户进度，并写入固定成就目录的解锁状态。
 */

namespace App\Services;

use App\Enums\CurrencySource;
use App\Events\MessageSent;
use App\Jobs\SaveMessageJob;
use App\Models\BaccaratBet;
use App\Models\DailySignIn;
use App\Models\GomokuGame;
use App\Models\HorseBet;
use App\Models\LotteryTicket;
use App\Models\Marriage;
use App\Models\Message;
use App\Models\PositionAuthorityLog;
use App\Models\PositionDutyLog;
use App\Models\RedPacketClaim;
use App\Models\RedPacketEnvelope;
use App\Models\SlotMachineLog;
use App\Models\User;
use App\Models\UserAchievement;
use App\Models\UserAchievementProgress;
use App\Models\UserCurrencyLog;
use App\Models\UserPosition;
use App\Support\AchievementCatalog;
use Illuminate\Support\Collection;

/**
 * 类功能：计算成就进度、创建解锁记录并推送本人通知。
 */
class AchievementService
{
    /**
     * 创建成就服务依赖。
     */
    public function __construct(
        private readonly ChatStateService $chatState,
    ) {}

    /**
     * 扫描单个用户的所有固定成就。
     *
     * @return array{checked: int, unlocked: int, updated: int, dry_run: bool}
     */
    public function scanUser(User $user, bool $notify = false, bool $dryRun = false): array
    {
        $progress = $this->progressForUser($user);
        $checked = 0;
        $unlocked = 0;
        $updated = 0;

        foreach (AchievementCatalog::definitions() as $definition) {
            $checked++;
            $value = (int) ($progress[$definition['metric']] ?? 0);
            $achievement = UserAchievement::query()
                ->where('user_id', $user->id)
                ->where('achievement_key', $definition['key'])
                ->first();

            if ($dryRun) {
                if ($value >= $definition['threshold'] && ! $achievement?->achieved_at) {
                    $unlocked++;
                }

                continue;
            }

            $this->storeProgress($user, $definition, $value);

            if (! $achievement) {
                $achievement = UserAchievement::query()->create([
                    'user_id' => $user->id,
                    'achievement_key' => $definition['key'],
                    'progress_value' => $value,
                    'metadata' => ['threshold' => $definition['threshold']],
                ]);
                $updated++;
            } elseif ($achievement->progress_value !== $value) {
                $achievement->forceFill(['progress_value' => $value])->save();
                $updated++;
            }

            if ($value < $definition['threshold'] || $achievement->achieved_at) {
                continue;
            }

            $achievement->forceFill([
                'progress_value' => $value,
                'achieved_at' => now(),
                'metadata' => ['threshold' => $definition['threshold']],
            ])->save();
            $unlocked++;

            if ($notify) {
                $this->notifyUnlocked($user, $achievement, $definition);
            }
        }

        return [
            'checked' => $checked,
            'unlocked' => $unlocked,
            'updated' => $updated,
            'dry_run' => $dryRun,
        ];
    }

    /**
     * 批量扫描用户成就。
     *
     * @return array{users: int, checked: int, unlocked: int, updated: int, dry_run: bool}
     */
    public function scanUsers(iterable $users, bool $notify = false, bool $dryRun = false): array
    {
        $summary = ['users' => 0, 'checked' => 0, 'unlocked' => 0, 'updated' => 0, 'dry_run' => $dryRun];

        foreach ($users as $user) {
            $result = $this->scanUser($user, $notify, $dryRun);
            $summary['users']++;
            $summary['checked'] += $result['checked'];
            $summary['unlocked'] += $result['unlocked'];
            $summary['updated'] += $result['updated'];
        }

        return $summary;
    }

    /**
     * 组装用户成就展示数据。
     *
     * @return array{categories: array<string, string>, achievements: Collection<int, array<string, mixed>>, unlocked_count: int, total_count: int}
     */
    public function displayForUser(User $user): array
    {
        $progress = $this->progressForUser($user);
        $records = UserAchievement::query()
            ->where('user_id', $user->id)
            ->get()
            ->keyBy('achievement_key');

        $achievements = collect(AchievementCatalog::definitions())
            ->sortBy('sort')
            ->map(function (array $definition) use ($progress, $records): array {
                $record = $records->get($definition['key']);
                $value = max((int) ($record?->progress_value ?? 0), (int) ($progress[$definition['metric']] ?? 0));
                $threshold = (int) $definition['threshold'];

                return [
                    ...$definition,
                    'progress_value' => $value,
                    'progress_percent' => $threshold > 0 ? min(100, (int) floor($value / $threshold * 100)) : 100,
                    'achieved_at' => $record?->achieved_at,
                    'unlocked' => (bool) $record?->achieved_at,
                ];
            })
            ->values();

        return [
            'categories' => AchievementCatalog::categories(),
            'achievements' => $achievements,
            'unlocked_count' => $achievements->where('unlocked', true)->count(),
            'total_count' => $achievements->count(),
        ];
    }

    /**
     * 读取用户最近解锁成就。
     *
     * @return Collection<int, array<string, mixed>>
     */
    public function recentUnlockedForUser(User $user, int $limit = 5): Collection
    {
        return UserAchievement::query()
            ->where('user_id', $user->id)
            ->whereNotNull('achieved_at')
            ->latest('achieved_at')
            ->limit($limit)
            ->get()
            ->map(function (UserAchievement $achievement): array {
                $definition = AchievementCatalog::find($achievement->achievement_key);

                return [
                    'key' => $achievement->achievement_key,
                    'name' => $definition['name'] ?? $achievement->achievement_key,
                    'icon' => $definition['icon'] ?? '🏅',
                    'description' => $definition['description'] ?? '',
                    'achieved_at' => $achievement->achieved_at?->toDateTimeString(),
                ];
            });
    }

    /**
     * 读取用户资料卡使用的成就摘要。
     *
     * @return array{unlocked_count: int, total_count: int, recent: array<int, array<string, mixed>>}
     */
    public function profileSummaryForUser(User $user): array
    {
        return [
            'unlocked_count' => (int) UserAchievement::query()
                ->where('user_id', $user->id)
                ->whereNotNull('achieved_at')
                ->count(),
            'total_count' => count(AchievementCatalog::definitions()),
            'recent' => $this->recentUnlockedForUser($user, 5)->values()->all(),
        ];
    }

    /**
     * 聚合单个用户所有成就进度。
     *
     * @return array<string, int>
     */
    public function progressForUser(User $user): array
    {
        $username = (string) $user->username;

        return [
            'chat_messages' => $this->chatMessageCount($username),
            'welcome_messages' => $this->welcomeMessageCount($username),
            'total_sign_ins' => (int) DailySignIn::query()->where('user_id', $user->id)->count(),
            'sign_in_streak' => (int) DailySignIn::query()->where('user_id', $user->id)->max('streak_days'),
            'makeup_sign_ins' => (int) DailySignIn::query()->where('user_id', $user->id)->where('is_makeup', true)->count(),
            'exp_gain' => $this->currencyGain($user->id, 'exp'),
            'gold_gain' => $this->currencyGain($user->id, 'gold'),
            'charm_gain' => $this->currencyGain($user->id, 'charm'),
            'gold_assets' => max(0, (int) $user->jjb + (int) $user->bank_jjb),
            'bank_balance' => max(0, (int) $user->bank_jjb),
            'game_gold_won' => $this->gameGoldWon($user->id),
            'game_gold_lost' => $this->gameGoldLost($user->id),
            'baccarat_bets' => (int) BaccaratBet::query()->where('user_id', $user->id)->count(),
            'horse_bets' => (int) HorseBet::query()->where('user_id', $user->id)->count(),
            'lottery_tickets' => (int) LotteryTicket::query()->where('user_id', $user->id)->count(),
            'slot_spins' => (int) SlotMachineLog::query()->where('user_id', $user->id)->count(),
            'gomoku_wins' => $this->gomokuWinCount($user->id),
            'fishing_times' => $this->currencySourceCount($user->id, CurrencySource::FISHING_COST->value),
            'riddle_wins' => $this->currencySourceCount($user->id, CurrencySource::GAME_REWARD->value),
            'red_packets_sent' => (int) RedPacketEnvelope::query()->where('sender_id', $user->id)->count(),
            'red_packets_claimed' => (int) RedPacketClaim::query()->where('user_id', $user->id)->count(),
            'marriages' => (int) Marriage::query()->where('status', 'married')->where(fn ($query) => $query->where('user_id', $user->id)->orWhere('partner_id', $user->id))->count(),
            'marriage_intimacy' => (int) Marriage::query()->where(fn ($query) => $query->where('user_id', $user->id)->orWhere('partner_id', $user->id))->max('intimacy'),
            'gifts_sent' => $this->currencySourceCount($user->id, CurrencySource::SEND_GIFT->value),
            'gifts_received' => $this->currencySourceCount($user->id, CurrencySource::RECV_GIFT->value),
            'positions' => (int) UserPosition::query()->where('user_id', $user->id)->count(),
            'duty_minutes' => (int) floor((int) PositionDutyLog::query()->where('user_id', $user->id)->sum('duration_seconds') / 60),
            'authority_actions' => (int) PositionAuthorityLog::query()->where('user_id', $user->id)->count(),
        ];
    }

    /**
     * 统计普通用户聊天消息数量。
     */
    private function chatMessageCount(string $username): int
    {
        return (int) Message::query()
            ->where('from_user', $username)
            ->whereIn('message_type', ['text', 'image', 'expired_image'])
            ->where(function ($query) {
                $query->where('retention_type', Message::RETENTION_USER_CHAT)
                    ->orWhereNull('retention_type');
            })
            ->count();
    }

    /**
     * 统计用户发出的欢迎动作次数。
     */
    private function welcomeMessageCount(string $username): int
    {
        return (int) Message::query()
            ->where('from_user', $username)
            ->where('action', '欢迎')
            ->count();
    }

    /**
     * 统计指定货币的累计正向获得量。
     */
    private function currencyGain(int $userId, string $currency): int
    {
        return (int) UserCurrencyLog::query()
            ->where('user_id', $userId)
            ->where('currency', $currency)
            ->where('amount', '>', 0)
            ->sum('amount');
    }

    /**
     * 统计指定流水来源次数。
     */
    private function currencySourceCount(int $userId, string $source): int
    {
        return (int) UserCurrencyLog::query()
            ->where('user_id', $userId)
            ->where('source', $source)
            ->count();
    }

    /**
     * 统计用户通过游戏相关流水累计赢取的金币。
     */
    private function gameGoldWon(int $userId): int
    {
        return (int) UserCurrencyLog::query()
            ->where('user_id', $userId)
            ->where('currency', 'gold')
            ->where('amount', '>', 0)
            ->whereIn('source', $this->gameWinSources())
            ->sum('amount');
    }

    /**
     * 统计用户在游戏相关流水中累计输掉或消耗的金币。
     */
    private function gameGoldLost(int $userId): int
    {
        return abs((int) UserCurrencyLog::query()
            ->where('user_id', $userId)
            ->where('currency', 'gold')
            ->where('amount', '<', 0)
            ->whereIn('source', $this->gameLossSources())
            ->sum('amount'));
    }

    /**
     * 返回游戏赢钱来源，用于游戏赢取类成就聚合。
     *
     * @return array<int, string>
     */
    private function gameWinSources(): array
    {
        return [
            CurrencySource::BACCARAT_WIN->value,
            CurrencySource::BACCARAT_LOSS_COVER_CLAIM->value,
            CurrencySource::HORSE_WIN->value,
            CurrencySource::LOTTERY_WIN->value,
            CurrencySource::SLOT_WIN->value,
            CurrencySource::FISHING_GAIN->value,
            CurrencySource::MYSTERY_BOX->value,
            CurrencySource::GOMOKU_WIN->value,
            CurrencySource::GAME_REWARD->value,
        ];
    }

    /**
     * 返回游戏输钱来源，用于游戏输钱类成就聚合。
     *
     * @return array<int, string>
     */
    private function gameLossSources(): array
    {
        return [
            CurrencySource::BACCARAT_BET->value,
            CurrencySource::HORSE_BET->value,
            CurrencySource::LOTTERY_BUY->value,
            CurrencySource::SLOT_SPIN->value,
            CurrencySource::SLOT_CURSE->value,
            CurrencySource::FISHING_COST->value,
            CurrencySource::FORTUNE_COST->value,
            CurrencySource::GOMOKU_ENTRY_FEE->value,
            CurrencySource::MYSTERY_BOX_TRAP->value,
        ];
    }

    /**
     * 统计五子棋胜利次数。
     */
    private function gomokuWinCount(int $userId): int
    {
        return (int) GomokuGame::query()
            ->where('status', 'finished')
            ->where(function ($query) use ($userId) {
                $query->where(fn ($inner) => $inner->where('player_black_id', $userId)->where('winner', 1))
                    ->orWhere(fn ($inner) => $inner->where('player_white_id', $userId)->where('winner', 2));
            })
            ->count();
    }

    /**
     * 写入用户成就进度快照。
     *
     * @param  array<string, mixed>  $definition  成就定义
     */
    private function storeProgress(User $user, array $definition, int $value): void
    {
        UserAchievementProgress::query()->updateOrCreate(
            [
                'user_id' => $user->id,
                'achievement_key' => $definition['key'],
            ],
            [
                'progress_value' => $value,
                'threshold_value' => (int) $definition['threshold'],
                'last_scanned_at' => now(),
            ],
        );
    }

    /**
     * 给用户推送成就解锁通知。
     *
     * @param  array<string, mixed>  $definition  成就定义
     */
    private function notifyUnlocked(User $user, UserAchievement $achievement, array $definition): void
    {
        if ($achievement->notified_at) {
            return;
        }

        $roomId = (int) ($user->room_id ?: 1);
        $message = [
            'id' => $this->chatState->nextMessageId($roomId),
            'room_id' => $roomId,
            'from_user' => '系统公告',
            'to_user' => $user->username,
            'content' => "🏅 恭喜解锁成就：{$definition['icon']} {$definition['name']} <span style=\"color:#64748b;\">{$definition['description']}</span>",
            'is_secret' => true,
            'font_color' => '#ca8a04',
            'action' => 'achievement_unlocked',
            'retention_type' => Message::RETENTION_SYSTEM_NOTICE,
            'toast_notification' => [
                'title' => '🏅 成就解锁',
                'message' => "{$definition['icon']} {$definition['name']}",
                'icon' => '🏅',
                'color' => '#ca8a04',
                'duration' => 8000,
            ],
            'sent_at' => now()->toDateTimeString(),
        ];

        $this->chatState->pushMessage($roomId, $message);
        broadcast(new MessageSent($roomId, $message));
        SaveMessageJob::dispatch($message);

        $achievement->forceFill(['notified_at' => now()])->save();
    }
}
