<?php

namespace Kylin\AntiAddiction\Services;

use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Kylin\AntiAddiction\Enums\AgeGroup;
use Kylin\AntiAddiction\Enums\AntiAddictionErrorCode;
use Kylin\AntiAddiction\Enums\PopupType;
use Kylin\AntiAddiction\Enums\RestrictionType;
use Kylin\AntiAddiction\Exceptions\AntiAddictionException;
use Kylin\AntiAddiction\Models\AntiAddictionRecord;
use Kylin\AntiAddiction\Models\PlaySession;
use Kylin\AntiAddiction\Models\SpendingRecord;
use Kylin\User\Models\User;
use Kylin\User\Models\UserProfile;

/**
 * 防沉迷核心服务
 *
 * 实现 2021 年国家新闻出版署防沉迷新规：
 * 1. 禁止游客模式：未实名用户不可游戏
 * 2. 未成年人时段限制：仅限周五六日+节假日 20:00-21:00
 * 3. 消费限额：按年龄分组严格限制充值金额
 * 4. 宵禁机制：夜间 22:00-08:00 禁止游戏（可配置）
 */
class AntiAddictionService
{
    public function __construct(
        private readonly HolidayService $holidayService,
    ) {}

    /**
     * 防沉迷检查（核心入口）
     *
     * 检查流程：
     * 1. 查询用户实名状态 -> 未实名则禁止游戏
     * 2. 判断年龄分组
     * 3. 成年人：检查宵禁（如启用）
     * 4. 未成年人：检查时段限制 + 时长限制
     *
     * @return array{allowed: bool, age_group: AgeGroup, restrictions: RestrictionType[], popups: PopupType[], remaining_seconds: int}
     */
    public function check(int $userId, int $gameId): array
    {
        $ageGroup = $this->resolveAgeGroup($userId);

        // 未实名：禁止游戏
        if ($ageGroup === AgeGroup::Unknown) {
            return [
                'allowed' => false,
                'age_group' => $ageGroup,
                'restrictions' => [RestrictionType::Unverified],
                'popups' => [PopupType::RealNameRequired],
                'remaining_seconds' => 0,
            ];
        }

        // 成年人：仅检查宵禁
        if ($ageGroup === AgeGroup::Adult) {
            return $this->checkAdultAccess($ageGroup);
        }

        // 未成年人：完整检查链
        return $this->checkMinorAccess($userId, $gameId, $ageGroup);
    }

    /**
     * 心跳上报
     *
     * 客户端定时调用，累计游戏时长。
     * 超时未上报则视为离线，自动结束会话。
     *
     * @return array{success: bool, daily_play_time: int, remaining_seconds: int, should_kick: bool}
     */
    public function heartbeat(int $userId, int $playTime): array
    {
        $record = $this->getOrCreateRecord($userId);

        // 每日重置检查
        if ($record->needsDailyReset()) {
            $record->resetDaily();
        }

        // 累加时长
        $record->addPlayTime($playTime);

        $ageGroup = $record->age_group;
        $remainingSeconds = $this->calculateRemainingSeconds($record->daily_play_time, $ageGroup);
        $shouldKick = $remainingSeconds <= 0 && $ageGroup->isMinor();

        return [
            'success' => true,
            'daily_play_time' => $record->daily_play_time,
            'remaining_seconds' => max(0, $remainingSeconds),
            'should_kick' => $shouldKick,
        ];
    }

    /**
     * 消费限额检查
     *
     * 严格遵循 2021 年法规：
     * - child (<8): 禁止任何充值
     * - junior_minor (8-15): 单次 <= 50 元, 月累计 <= 200 元
     * - senior_minor (16-17): 单次 <= 100 元, 月累计 <= 400 元
     * - adult (>=18): 无限制
     *
     * @param int $userId      用户 ID
     * @param int $amountCents 充值金额（分）
     * @return array{allowed: bool, age_group: AgeGroup, single_limit: int, monthly_limit: int, monthly_used: int}
     *
     * @throws AntiAddictionException
     */
    public function checkSpending(int $userId, int $amountCents): array
    {
        $ageGroup = $this->resolveAgeGroup($userId);

        // 未实名禁止充值
        if ($ageGroup === AgeGroup::Unknown) {
            throw new AntiAddictionException(AntiAddictionErrorCode::UnverifiedUser);
        }

        $limits = config("kylin-anti-addiction.spending_limits.{$ageGroup->value}");
        $singleLimit = $limits['single'] ?? 0;
        $monthlyLimit = $limits['monthly'] ?? 0;
        $monthlyUsed = SpendingRecord::monthlyTotal($userId);

        // 成年人无限制
        if ($ageGroup === AgeGroup::Adult) {
            return [
                'allowed' => true,
                'age_group' => $ageGroup,
                'single_limit' => -1,
                'monthly_limit' => -1,
                'monthly_used' => $monthlyUsed,
            ];
        }

        // child 禁止充值
        if ($ageGroup === AgeGroup::Child) {
            throw new AntiAddictionException(AntiAddictionErrorCode::SpendingForbidden);
        }

        // 单次限额检查
        if ($singleLimit >= 0 && $amountCents > $singleLimit) {
            throw new AntiAddictionException(AntiAddictionErrorCode::SpendingSingleExceeded);
        }

        // 月累计限额检查
        if ($monthlyLimit >= 0 && ($monthlyUsed + $amountCents) > $monthlyLimit) {
            throw new AntiAddictionException(AntiAddictionErrorCode::SpendingMonthlyExceeded);
        }

        return [
            'allowed' => true,
            'age_group' => $ageGroup,
            'single_limit' => $singleLimit,
            'monthly_limit' => $monthlyLimit,
            'monthly_used' => $monthlyUsed,
        ];
    }

    /**
     * 检查当前是否为未成年人允许游戏时段
     *
     * 允许时段：周五、周六、周日及法定节假日 20:00-21:00
     */
    public function isMinorAllowedTime(): bool
    {
        $now = now();

        // 检查是否为法定节假日
        $isHoliday = $this->holidayService->isHoliday($now->toDateString());

        // 检查是否为允许的星期（周五=5, 周六=6, 周日=7）
        $allowedDays = config('kylin-anti-addiction.time_slot.allowed_days', [5, 6, 7]);
        $isAllowedDay = in_array((int) $now->isoFormat('E'), $allowedDays, true);

        if (! $isAllowedDay && ! $isHoliday) {
            return false;
        }

        // 检查是否在允许时段内（20:00-21:00）
        $startHour = config('kylin-anti-addiction.time_slot.start_hour', 20);
        $endHour = config('kylin-anti-addiction.time_slot.end_hour', 21);
        $currentHour = (int) $now->format('H');
        $currentMinute = (int) $now->format('i');

        // 精确到分钟：20:00:00 ~ 20:59:59
        return $currentHour === $startHour;
    }

    /**
     * 检查当前是否为宵禁时段
     *
     * 宵禁时段：22:00 ~ 次日 08:00
     */
    public function isCurfewTime(): bool
    {
        if (! config('kylin-anti-addiction.curfew.enabled', false)) {
            return false;
        }

        $now = now();
        $startHour = config('kylin-anti-addiction.curfew.start_hour', 22);
        $endHour = config('kylin-anti-addiction.curfew.end_hour', 8);
        $currentHour = (int) $now->format('H');

        // 22:00 ~ 23:59 或 00:00 ~ 07:59
        return $currentHour >= $startHour || $currentHour < $endHour;
    }

    /**
     * 解析用户年龄分组
     */
    public function resolveAgeGroup(int $userId): AgeGroup
    {
        return Cache::remember(
            "kylin:anti_addiction:age_group:{$userId}",
            config('kylin-anti-addiction.real_name_verify.cache_ttl', 86400),
            function () use ($userId): AgeGroup {
                $userProfile = UserProfile::where('user_id', $userId)->first();

                // 未实名
                if (! $userProfile || ! $userProfile->id_card || ! $userProfile->birthday) {
                    return AgeGroup::Unknown;
                }

                $age = $userProfile->birthday->age;

                return AgeGroup::fromAge($age);
            },
        );
    }

    /**
     * 判断当前是否在允许游戏时段（兼容控制器调用）
     */
    public function isInAllowedWindow(): bool
    {
        return $this->isMinorAllowedTime();
    }

    /**
     * 获取消费限额配置
     *
     * @return array{single: int, monthly: int}
     */
    public function getPaymentLimits(int $ageGroup): array
    {
        $limits = [
            0 => ['single' => 0, 'monthly' => 0],       // 未满8周岁：禁止充值
            1 => ['single' => 5000, 'monthly' => 20000], // 8-16周岁：单次50元，月200元（单位：分）
            2 => ['single' => 10000, 'monthly' => 40000],// 16-18周岁：单次100元，月400元
            3 => ['single' => -1, 'monthly' => -1],      // 已满18周岁：无限制
        ];

        return $limits[$ageGroup] ?? $limits[3];
    }

    /**
     * 获取用户月累计消费金额（分）
     */
    public function getMonthlySpending(int $gameId, string $userId): int
    {
        return (int) SpendingRecord::where('user_id', $userId)
            ->where('game_id', $gameId)
            ->where('status', 'success')
            ->whereYear('created_at', now()->year)
            ->whereMonth('created_at', now()->month)
            ->sum('amount_cents');
    }

    /**
     * 记录游戏会话
     */
    public function recordSession(int $gameId, string $userId, string $action, int $durationMinutes): void
    {
        if ($action === 'start') {
            PlaySession::create([
                'game_id' => $gameId,
                'user_id' => $userId,
                'session_start' => now(),
                'ip_address' => request()->ip(),
            ]);

            $this->audit('session_start', $userId, [
                'game_id' => $gameId,
                'action' => $action,
            ]);
        } elseif ($action === 'heartbeat') {
            $session = PlaySession::where('user_id', $userId)
                ->where('game_id', $gameId)
                ->whereNull('session_end')
                ->latest()
                ->first();
            if ($session) {
                $session->duration = $durationMinutes * 60;
                $session->save();
            }
        } elseif ($action === 'end') {
            $session = PlaySession::where('user_id', $userId)
                ->where('game_id', $gameId)
                ->whereNull('session_end')
                ->latest()
                ->first();
            if ($session) {
                $session->end();
            }

            $this->audit('session_end', $userId, [
                'game_id' => $gameId,
                'action' => $action,
                'duration_minutes' => $durationMinutes,
            ]);
        }
    }

    /**
     * 记录消费记录
     */
    public function recordPayment(int $gameId, string $userId, string $orderId, float $amount, string $itemName): void
    {
        SpendingRecord::create([
            'game_id' => $gameId,
            'user_id' => $userId,
            'order_no' => $orderId,
            'amount_cents' => (int) ($amount * 100), // 元转分
            'status' => 'success',
            'age_group_at_time' => $this->resolveAgeGroup((int) $userId),
        ]);

        $this->audit('payment_recorded', $userId, [
            'game_id' => $gameId,
            'order_no' => $orderId,
            'amount_cents' => (int) ($amount * 100),
        ]);
    }

    /**
     * 清除用户年龄分组缓存（实名认证后调用）
     */
    public function clearAgeGroupCache(int $userId): void
    {
        Cache::forget("kylin:anti_addiction:age_group:{$userId}");
    }

    // ─── 内部方法 ───────────────────────────────────────────────

    /**
     * 成年人访问检查
     */
    private function checkAdultAccess(AgeGroup $ageGroup): array
    {
        // 宵禁检查
        if ($this->isCurfewTime()) {
            return [
                'allowed' => false,
                'age_group' => $ageGroup,
                'restrictions' => [RestrictionType::Curfew],
                'popups' => [PopupType::CurfewWarning],
                'remaining_seconds' => 0,
            ];
        }

        return [
            'allowed' => true,
            'age_group' => $ageGroup,
            'restrictions' => [],
            'popups' => [],
            'remaining_seconds' => -1, // 无限制
        ];
    }

    /**
     * 未成年人访问检查
     */
    private function checkMinorAccess(int $userId, int $gameId, AgeGroup $ageGroup): array
    {
        $restrictions = [];
        $popups = [];

        // 宵禁检查
        if ($this->isCurfewTime()) {
            return [
                'allowed' => false,
                'age_group' => $ageGroup,
                'restrictions' => [RestrictionType::Curfew],
                'popups' => [PopupType::CurfewWarning],
                'remaining_seconds' => 0,
            ];
        }

        // 时段检查
        if (! $this->isMinorAllowedTime()) {
            $restrictions[] = RestrictionType::TimeSlot;
            $popups[] = PopupType::MinorTimeLimit;
        }

        // 时长检查
        $record = $this->getOrCreateRecord($userId);
        if ($record->needsDailyReset()) {
            $record->resetDaily();
        }

        $remainingSeconds = $this->calculateRemainingSeconds($record->daily_play_time, $ageGroup);

        if ($remainingSeconds <= 0) {
            $restrictions[] = RestrictionType::Duration;
            $popups[] = PopupType::MinorDurationLimit;
        }

        $allowed = empty($restrictions);

        // 时段内但即将结束，弹出下线警告
        if ($allowed && $remainingSeconds > 0 && $remainingSeconds <= 600) {
            $popups[] = PopupType::KickOffWarning;
        }

        return [
            'allowed' => $allowed,
            'age_group' => $ageGroup,
            'restrictions' => $restrictions,
            'popups' => $popups,
            'remaining_seconds' => max(0, $remainingSeconds),
        ];
    }

    /**
     * 计算剩余可游戏秒数
     *
     * 未成年人每日限 1 小时（3600 秒），成年人无限制
     */
    private function calculateRemainingSeconds(int $dailyPlayTime, AgeGroup $ageGroup): int
    {
        if (! $ageGroup->isMinor()) {
            return -1; // 无限制
        }

        // 未成年人每日最多 1 小时
        $maxDailySeconds = 3600;

        return max(0, $maxDailySeconds - $dailyPlayTime);
    }

    /**
     * 获取或创建防沉迷记录，并同步更新年龄分组
     */
    private function getOrCreateRecord(int $userId): AntiAddictionRecord
    {
        $record = AntiAddictionRecord::firstOrCreate(
            ['user_id' => $userId],
            [
                'age_group' => $this->resolveAgeGroup($userId),
                'last_play_date' => now()->format('Y-m-d'),
            ],
        );

        // 每次访问时同步最新的年龄分组（用户生日变化或首次实名后）
        $currentAgeGroup = $this->resolveAgeGroup($userId);
        if ($record->age_group !== $currentAgeGroup) {
            $record->age_group = $currentAgeGroup;
            $record->save();
        }

        return $record;
    }

    /**
     * 记录防沉迷审计日志
     *
     * 依据《未成年人保护法》第七十五条和《关于防止未成年人沉迷网络游戏的通知》，
     * 防沉迷系统关键操作（会话开始/结束、消费记录）需审计留痕。
     */
    private function audit(string $event, string $userId, array $extra = []): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => self::class,
                'model_id' => (int) $userId,
                'new_values' => $extra,
                'old_values' => null,
                'user_id' => auth()->id() ?? (int) $userId,
                'ip' => request()?->ip(),
                'url' => request()?->fullUrl(),
                'tag' => 'anti-addiction',
            ]);
        } catch (\Throwable $e) {
            logger()->warning('防沉迷审计日志写入失败', [
                'error' => $e->getMessage(),
                'user_id' => $userId,
                'event' => $event,
            ]);
        }
    }
}
