<?php

/**
 * 文件功能：定时自动存点指令
 *
 * 每5分钟由 Laravel Scheduler 调用，为所有在线用户：
 * 1. 发放经验和金币奖励
 * 2. 自动计算并更新等级
 * 3. 在聊天室内推送"系统为你自动存点"提示
 * 4. 若用户等级提升，向全频道广播恭喜消息
 *
 * @author   ChatRoom Laravel
 *
 * @version  1.0.0
 */

namespace App\Console\Commands;

use App\Enums\CurrencySource;
use App\Events\MessageSent;
use App\Jobs\SaveMessageJob;
use App\Models\PositionDutyLog;
use App\Models\Sysparam;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\ChatUserPresenceService;
use App\Services\UserCurrencyService;
use App\Services\VipService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class AutoSaveExp extends Command
{
    /**
     * Artisan 指令名称
     */
    protected $signature = 'chatroom:auto-save-exp';

    /**
     * 指令描述（在 artisan list 中显示）
     */
    protected $description = '为所有聊天室在线用户执行自动存点（经验/金币/等级），每5分钟运行一次';

    /**
     * 注入依赖服务
     */
    public function __construct(
        private readonly ChatStateService $chatState,
        private readonly ChatUserPresenceService $chatUserPresenceService,
        private readonly VipService $vipService,
        private readonly UserCurrencyService $currencyService,
    ) {
        parent::__construct();
    }

    /**
     * 指令入口：扫描所有房间，为在线用户发放经验和金币奖励。
     *
     * 处理流程：
     * 1. 从 Redis 扫描所有 room:*:users 的哈希表
     * 2. 对每个在线用户应用经验/金币奖励
     * 3. 重新计算等级，若升级则推送全频道广播
     * 4. 向用户所在房间推送"系统为你自动存点"私信
     */
    public function handle(): int
    {
        // 读取奖励配置
        $expGainRaw = Sysparam::getValue('exp_per_heartbeat', '1');
        $jjbGainRaw = Sysparam::getValue('jjb_per_heartbeat', '0');
        $superLevel = (int) Sysparam::getValue('superlevel', '100');

        // 从 Redis 扫描所有在线房间
        $roomMap = $this->scanOnlineRooms();

        if (empty($roomMap)) {
            $this->info('当前没有在线用户，跳过存点。');

            return Command::SUCCESS;
        }

        // 统计本次处理总人次（一个用户在多个房间会被计算多次）
        $totalProcessed = 0;

        foreach ($roomMap as $roomId => $usernames) {
            foreach ($usernames as $username) {
                $this->processUser($username, $roomId, $expGainRaw, $jjbGainRaw, $superLevel);
                $totalProcessed++;
            }
        }

        $this->info("自动存点完成，共处理 {$totalProcessed} 个在线用户。");

        return Command::SUCCESS;
    }

    /**
     * 查询所有活跃房间及其在线用户列表。
     *
     * 改为从数据库获取所有房间，再用 Redis::hkeys() 查询在线用户。
     * 这样可以避免 Redis SCAN + 前缀匹配不一致的问题，
     * 且 Redis::hkeys() 会自动正确地加上前缀，与 ChatStateService::userJoin() 一致。
     *
     * @return array<int, array<string>> 格式：[房间ID => [用户名, ...]]
     */
    private function scanOnlineRooms(): array
    {
        $roomMap = [];

        // 从数据库取出所有房间 ID
        $roomIds = \App\Models\Room::pluck('id');

        foreach ($roomIds as $roomId) {
            // Laravel 的 Redis facade 会自动加配置的前缀，与 ChatStateService 存入时完全一致
            $usernames = Redis::hkeys("room:{$roomId}:users");
            if (! empty($usernames)) {
                $roomMap[(int) $roomId] = $usernames;
            }
        }

        return $roomMap;
    }

    /**
     * 为单个在线用户执行存点逻辑，并在其所在房间推送存点通知。
     *
     * @param  string  $username  用户名
     * @param  int  $roomId  所在房间ID
     * @param  string  $expGainRaw  经验奖励原始配置（支持 "1" 或 "1-10" 范围）
     * @param  string  $jjbGainRaw  金币奖励原始配置
     * @param  int  $superLevel  管理员等级阈值
     */
    private function processUser(
        string $username,
        int $roomId,
        string $expGainRaw,
        string $jjbGainRaw,
        int $superLevel
    ): void {
        $user = User::where('username', $username)->first();
        if (! $user) {
            return;
        }

        // 1. 计算奖励量（经验/金币 均支持 VIP 倍率）
        $expGain = $this->parseRewardValue($expGainRaw);
        $expMultiplier = $this->vipService->getExpMultiplier($user);
        $actualExpGain = (int) round($expGain * $expMultiplier);

        $jjbGain = $this->parseRewardValue($jjbGainRaw);
        $actualJjbGain = 0;
        if ($jjbGain > 0) {
            $jjbMultiplier = $this->vipService->getJjbMultiplier($user);
            $actualJjbGain = (int) round($jjbGain * $jjbMultiplier);
        }

        // 2. 通过统一积分服务发放奖励（原子写入 + 流水记录）
        if ($actualExpGain > 0) {
            $this->currencyService->change(
                $user, 'exp', $actualExpGain, CurrencySource::AUTO_SAVE, '自动存点', $roomId,
            );
        }
        if ($actualJjbGain > 0) {
            $this->currencyService->change(
                $user, 'gold', $actualJjbGain, CurrencySource::AUTO_SAVE, '自动存点', $roomId,
            );
        }
        $user->refresh(); // 刷新获取最新属性（service 已原子更新）
        $user->load(['activePosition.position.department', 'vipLevel']); // 存点通知需要展示部门、职务与会员身份。

        // 3. 自动升降级逻辑
        //    - 有在职职务的用户：等级固定为职务对应等级，不随经验变化
        //    - 管理员（>= superLevel）：不变动
        //    - 普通用户：按经验计算等级，支持升降级
        $oldLevel = $user->user_level;
        $leveledUp = false;

        $activeUP = $user->activePosition;  // 已在 refresh 后加载

        if ($activeUP?->position) {
            // 有在职职务：等级锁定为职务设定值，确保不被经验系统覆盖
            $requiredLevel = (int) $activeUP->position->level;
            if ($requiredLevel > 0 && $user->user_level !== $requiredLevel) {
                $user->user_level = $requiredLevel;
            }
        } elseif ($oldLevel < $superLevel) {
            // 普通用户：按经验计算并更新等级
            $newLevel = Sysparam::calculateLevel($user->exp_num);
            if ($newLevel !== $oldLevel && $newLevel < $superLevel) {
                $user->user_level = $newLevel;
                $leveledUp = ($newLevel > $oldLevel);
            }
        }

        $user->save();

        // 4. 若升级，向全频道广播升级消息
        if ($leveledUp) {
            $sysMsg = [
                'id' => $this->chatState->nextMessageId($roomId),
                'room_id' => $roomId,
                'from_user' => '系统传音',
                'to_user' => '大家',
                'content' => "天道酬勤！恭喜侠客【{$user->username}】挂机苦修，境界突破至 LV.{$user->user_level}！",
                'is_secret' => false,
                'font_color' => '#d97706',
                'action' => '大声宣告',
                'sent_at' => now()->toDateTimeString(),
            ];
            $this->chatState->pushMessage($roomId, $sysMsg);
            broadcast(new MessageSent($roomId, $sysMsg));
            SaveMessageJob::dispatch($sysMsg);

            // 触发微信机器人私聊通知 (等级提升)
            try {
                $wechatService = app(\App\Services\WechatBot\WechatNotificationService::class);
                $wechatService->notifyLevelChange($user, $oldLevel, $newLevel);
            } catch (\Exception $e) {
                \Illuminate\Support\Facades\Log::error('WechatBot level change notification failed', ['error' => $e->getMessage()]);
            }
        }

        // 5. 向用户私人推送"系统为你自动存点"信息，在其聊天框显示
        $gainParts = [];
        if ($actualExpGain > 0) {
            $gainParts[] = "经验+{$actualExpGain}";
        }
        if ($actualJjbGain > 0) {
            $gainParts[] = "金币+{$actualJjbGain}";
        }
        $jjbDisplay = $user->jjb ?? 0;
        $gainStr = ! empty($gainParts) ? ' 本次获得：'.implode('，', $gainParts) : '';

        $identitySummary = $this->chatUserPresenceService->buildIdentitySummary($user);

        // 格式：⏰ 自动存点 · 部门 X · 职务 Y · 会员 Z · LV.100 · 经验 10,468 · 金币 8,345 · 已满级 · 本次获得：经验+1，金币+3
        $statusTag = $user->user_level >= $superLevel ? ' · 已满级 ✓' : '';
        $content = "⏰ 自动存点 · {$identitySummary['inline']} · LV.{$user->user_level} · 经验 {$user->exp_num} · 金币 {$jjbDisplay}{$statusTag}{$gainStr}";

        $noticeMsg = [
            'id' => $this->chatState->nextMessageId($roomId),
            'room_id' => $roomId,
            'from_user' => '系统',
            'to_user' => $username,     // 定向推送给本人
            'content' => $content,
            'is_secret' => true,          // 私信模式：前端过滤，只有收件人才能看到
            'font_color' => '#16a34a',     // 草绿色
            'action' => '',
            'sent_at' => now()->toDateTimeString(),
        ];

        $this->chatState->pushMessage($roomId, $noticeMsg);
        broadcast(new MessageSent($roomId, $noticeMsg));

        // 6. 同步更新在职用户的勤务时长
        $this->tickDutyLog($user, $roomId);
    }

    /**
     * 解析奖励值配置字符串，支持固定值和随机范围两种格式。
     *
     * 格式说明：
     * - 固定值：直接写数字，如 "5"  → 返回 5
     * - 随机范围：如 "3-10" → 返回 [3, 10] 之间的随机整数
     *
     * @param  string  $raw  原始配置字符串
     */
    private function parseRewardValue(string $raw): int
    {
        $raw = trim($raw);
        if (str_contains($raw, '-')) {
            [$min, $max] = explode('-', $raw, 2);

            return rand((int) $min, (int) $max);
        }

        return (int) $raw;
    }

    /**
     * 自动存点时同步更新或创建在职用户的勤务日志。
     *
     * 逻辑同 ChatController::tickDutyLog：
     * 1. 无在职职务 → 跳过
     * 2. 今日已有开放日志 → 刷新 duration_seconds
     * 3. 今日无日志 → 新建，login_at 取 user->in_time（进房时间）
     *
     * @param  \App\Models\User  $user  已 refresh 的用户实例
     * @param  int  $roomId  所在房间 ID
     */
    private function tickDutyLog(User $user, int $roomId): void
    {
        // 无论有无职务，均记录在线流水
        $activeUP = $user->activePosition;

        // ① 今日未关闭的开放日志 → 刷新时长
        $openLog = PositionDutyLog::query()
            ->where('user_id', $user->id)
            ->whereNull('logout_at')
            ->whereDate('login_at', today())
            ->first();

        if ($openLog) {
            DB::table('position_duty_logs')
                ->where('id', $openLog->id)
                ->update([
                    'duration_seconds' => DB::raw('GREATEST(0, TIMESTAMPDIFF(SECOND, login_at, NOW()))'),
                    'updated_at' => now(),
                ]);

            return;
        }

        // ② 今日无开放日志 → 新建
        // 若今日已有已关闭的日志（是重建场景），必须用 now()，防止重用旧 in_time 累积膨胀
        $hasClosedToday = PositionDutyLog::query()
            ->where('user_id', $user->id)
            ->whereDate('login_at', today())
            ->whereNotNull('logout_at')
            ->exists();

        $loginAt = (! $hasClosedToday && $user->in_time && $user->in_time->isToday())
            ? $user->in_time
            : now();

        PositionDutyLog::create([
            'user_id' => $user->id,
            'user_position_id' => $activeUP?->id,
            'login_at' => $loginAt,
            'ip_address' => '0.0.0.0',
            'room_id' => $roomId,
        ]);
    }
}
