<?php

/**
 * 文件功能：聊天室核心控制器
 * 接管原版 INIT.ASP, NEWSAY.ASP, LEAVE.ASP 的所有职责
 *
 * @author   ChatRoom Laravel
 *
 * @version  1.0.0
 */

namespace App\Http\Controllers;

use App\Events\MessageSent;
use App\Events\UserJoined;
use App\Events\UserLeft;
use App\Http\Requests\SendMessageRequest;
use App\Jobs\SaveMessageJob;
use App\Models\Autoact;
use App\Models\Gift;
use App\Models\Room;
use App\Models\Sysparam;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\MessageFilterService;
use App\Services\VipService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
use Illuminate\View\View;

class ChatController extends Controller
{
    public function __construct(
        private readonly ChatStateService $chatState,
        private readonly MessageFilterService $filter,
        private readonly VipService $vipService,
        private readonly \App\Services\ShopService $shopService,
    ) {}

    /**
     * 进入房间初始化 (等同于原版 INIT.ASP)
     *
     * @param  int  $id  房间ID
     * @return View|JsonResponse
     */
    public function init(int $id)
    {
        $room = Room::findOrFail($id);
        $user = Auth::user();

        // 房间人气 +1（每次访问递增，复刻原版人气计数）
        $room->increment('visit_num');
        
        // 用户进房时间刷新
        $user->update(['in_time' => now()]);

        // 1. 将当前用户加入到 Redis 房间在线列表（包含 VIP 和管理员信息）
        $superLevel = (int) Sysparam::getValue('superlevel', '100');
        $userData = [
            'level' => $user->user_level,
            'sex' => $user->sex,
            'headface' => $user->headface,
            'vip_icon' => $user->vipIcon(),
            'vip_name' => $user->vipName(),
            'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
            'is_admin' => $user->user_level >= $superLevel,
        ];
        $this->chatState->userJoin($id, $user->username, $userData);

        // 2. 广播 UserJoined 事件，通知房间内的其他人
        broadcast(new UserJoined($id, $user->username, $userData))->toOthers();

        // 3. 管理员（superlevel）进入时：触发全房间烟花特效 + 公屏欢迎公告
        if ($user->user_level >= $superLevel) {
            // 广播烟花特效给所有在线用户
            broadcast(new \App\Events\EffectBroadcast($id, 'fireworks', $user->username));

            // 发送欢迎公告消息（使用系统公告样式）
            $welcomeMsg = [
                'id'        => $this->chatState->nextMessageId($id),
                'room_id'   => $id,
                'from_user' => '系统公告',
                'to_user'   => '大家',
                'content'   => "🎉 欢迎管理员 <b>{$user->username}</b> 驾临本聊天室！请各位文明聊天！",
                'is_secret' => false,
                'font_color' => '#b91c1c',
                'action'    => '',
                'sent_at'   => now()->toDateTimeString(),
            ];
            $this->chatState->pushMessage($id, $welcomeMsg);
            broadcast(new MessageSent($id, $welcomeMsg));
        }

        // 4. 获取历史消息用于初次渲染
        // TODO: 可在前端通过请求另外的接口拉取历史记录，或者直接在这里 attach

        // 渲染主聊天框架视图
        return view('chat.frame', [
            'room'       => $room,
            'user'       => $user,
            'weekEffect' => $this->shopService->getActiveWeekEffect($user), // 周卡特效（登录自动播放）
        ]);
    }

    /**
     * 发送消息 (等同于原版 NEWSAY.ASP)
     *
     * @param  int  $id  房间ID
     */
    public function send(SendMessageRequest $request, int $id): JsonResponse
    {
        $data = $request->validated();
        $user = Auth::user();

        // 0. 检查用户是否被禁言（Redis TTL 自动过期）
        $muteKey = "mute:{$id}:{$user->username}";
        if (Redis::exists($muteKey)) {
            $ttl = Redis::ttl($muteKey);
            $minutes = ceil($ttl / 60);

            return response()->json([
                'status' => 'error',
                'message' => "您正在禁言中，还需等待约 {$minutes} 分钟。",
            ], 403);
        }

        // 1. 过滤净化消息体
        $pureContent = $this->filter->filter($data['content'] ?? '');
        if (empty($pureContent)) {
            return response()->json(['status' => 'error', 'message' => '消息内容不能为空或不合法。'], 422);
        }

        // 2. 封装消息对象
        $messageData = [
            'id' => $this->chatState->nextMessageId($id), // 分布式安全自增序号
            'room_id' => $id,
            'from_user' => $user->username,
            'to_user' => $data['to_user'] ?? '大家',
            'content' => $pureContent,
            'is_secret' => $data['is_secret'] ?? false,
            'font_color' => $data['font_color'] ?? '',
            'action' => $data['action'] ?? '',
            'sent_at' => now()->toDateTimeString(),
        ];

        // 3. 压入 Redis 缓存列表 (防炸内存，只保留最近 N 条)
        $this->chatState->pushMessage($id, $messageData);

        // 4. 立刻向 WebSocket 发射广播，前端达到 0 延迟渲染
        broadcast(new MessageSent($id, $messageData));

        // 5. 丢进异步列队，慢慢持久化到 MySQL，保护数据库连接池
        SaveMessageJob::dispatch($messageData);

        // 6. 如果用户更换了字体颜色，顺便保存到 s_color 字段，下次进入时恢复
        $chosenColor = $data['font_color'] ?? '';
        if ($chosenColor && $chosenColor !== ($user->s_color ?? '')) {
            $user->s_color = $chosenColor;
            $user->save();
        }

        // 7. 聊天给魅力值（仅对指定用户的非悄悄话公开发言有效）
        $toUser = $data['to_user'] ?? '大家';
        $isSecret = $data['is_secret'] ?? false;
        if ($toUser !== '大家' && ! $isSecret) {
            $this->grantChatCharm($user, $toUser);
        }

        return response()->json(['status' => 'success']);
    }

    /**
     * 自动挂机存点心跳与经验升级 (新增)
     * 替代原版定时 iframe 刷新的 save.asp。
     *
     * @param  int  $id  房间ID
     */
    public function heartbeat(Request $request, int $id): JsonResponse
    {
        $user = Auth::user();
        if (! $user) {
            return response()->json(['status' => 'error'], 401);
        }

        // 1. 心跳奖励：通过 Redis 限制最小间隔（默认30秒），防止频繁点击
        $cooldownKey = "heartbeat_exp:{$user->id}";
        $canGainReward = ! Redis::exists($cooldownKey);
        $actualExpGain = 0;
        $actualJjbGain = 0;

        if ($canGainReward) {
            // 经验奖励（支持固定值 "1" 或范围 "1-10"）
            $expGain = $this->parseRewardValue(Sysparam::getValue('exp_per_heartbeat', '1'));
            $expMultiplier = $this->vipService->getExpMultiplier($user);
            $actualExpGain = (int) round($expGain * $expMultiplier);
            $user->exp_num += $actualExpGain;

            // 金币奖励（支持固定值 "1" 或范围 "1-5"）
            $jjbGain = $this->parseRewardValue(Sysparam::getValue('jjb_per_heartbeat', '0'));
            if ($jjbGain > 0) {
                $jjbMultiplier = $this->vipService->getJjbMultiplier($user);
                $actualJjbGain = (int) round($jjbGain * $jjbMultiplier);
                $user->jjb = ($user->jjb ?? 0) + $actualJjbGain;
            }

            // 设置冷却（30秒内不再给奖励）
            Redis::setex($cooldownKey, 30, 1);
        }

        // 2. 使用 sysparam 表中可配置的等级-经验阈值计算等级
        // 管理员（superlevel 及以上）不参与自动升降级，等级由后台手动设置
        $superLevel = (int) Sysparam::getValue('superlevel', '100');
        $oldLevel = $user->user_level;
        $leveledUp = false;

        if ($oldLevel < $superLevel) {
            $newLevel = Sysparam::calculateLevel($user->exp_num);
            if ($newLevel !== $oldLevel && $newLevel < $superLevel) {
                $user->user_level = $newLevel;
                $leveledUp = ($newLevel > $oldLevel);
            }
        }

        $user->save(); // 存点入库

        // 3. 将新的等级反馈给当前用户的在线名单上
        // 确保刚刚升级后别人查看到的也是最准确等级
        $this->chatState->userJoin($id, $user->username, [
            'level' => $user->user_level,
            'sex' => $user->sex,
            'headface' => $user->headface,
            'vip_icon' => $user->vipIcon(),
            'vip_name' => $user->vipName(),
            'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
            'is_admin' => $user->user_level >= $superLevel,
        ]);

        // 4. 如果突破境界，向全房系统喊话广播！
        if ($leveledUp) {
            // 生成炫酷广播消息发向该频道
            $sysMsg = [
                'id' => $this->chatState->nextMessageId($id),
                'room_id' => $id,
                '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($id, $sysMsg);
            broadcast(new MessageSent($id, $sysMsg));

            // 落库
            SaveMessageJob::dispatch($sysMsg);
        }

        // 5. 随机事件触发（复刻原版 autoact 系统，概率可在后台配置）
        $autoEvent = null;
        $eventChance = (int) Sysparam::getValue('auto_event_chance', '10');
        if ($eventChance > 0 && rand(1, 100) <= $eventChance) {
            $autoEvent = Autoact::randomEvent();
            if ($autoEvent) {
                // 应用经验/金币变化（不低于 0）
                if ($autoEvent->exp_change !== 0) {
                    $user->exp_num = max(0, $user->exp_num + $autoEvent->exp_change);
                }
                if ($autoEvent->jjb_change !== 0) {
                    $user->jjb = max(0, ($user->jjb ?? 0) + $autoEvent->jjb_change);
                }
                $user->save();

                // 重新计算等级（经验可能因事件而变化，但管理员不参与自动升降级）
                if ($user->user_level < $superLevel) {
                    $recalcLevel = Sysparam::calculateLevel($user->exp_num);
                    if ($recalcLevel !== $user->user_level && $recalcLevel < $superLevel) {
                        $user->user_level = $recalcLevel;
                        $user->save();
                    }
                }

                // 广播随机事件消息到聊天室
                $eventMsg = [
                    'id' => $this->chatState->nextMessageId($id),
                    'room_id' => $id,
                    'from_user' => '星海小博士',
                    'to_user' => '大家',
                    'content' => $autoEvent->renderText($user->username),
                    'is_secret' => false,
                    'font_color' => match ($autoEvent->event_type) {
                        'good' => '#16a34a',  // 绿色（好运）
                        'bad' => '#dc2626',   // 红色（坏运）
                        default => '#7c3aed', // 紫色（中性）
                    },
                    'action' => '',
                    'sent_at' => now()->toDateTimeString(),
                ];

                $this->chatState->pushMessage($id, $eventMsg);
                broadcast(new MessageSent($id, $eventMsg));
                SaveMessageJob::dispatch($eventMsg);
            }
        }

        // 确定用户称号：管理员 > VIP 名称 > 普通会员
        $title = '普通会员';
        if ($user->user_level >= $superLevel) {
            $title = '管理员';
        } elseif ($user->isVip()) {
            $title = $user->vipName() ?: '会员';
        }

        return response()->json([
            'status' => 'success',
            'data' => [
                'exp_num' => $user->exp_num,
                'jjb' => $user->jjb ?? 0,
                'exp_gain' => $actualExpGain,
                'jjb_gain' => $actualJjbGain,
                'user_level' => $user->user_level,
                'title' => $title,
                'leveled_up' => $leveledUp,
                'is_max_level' => $user->user_level >= $superLevel,
                'auto_event' => $autoEvent ? $autoEvent->renderText($user->username) : null,
            ],
        ]);
    }

    /**
     * 离开房间 (等同于原版 LEAVE.ASP)
     *
     * @param  int  $id  房间ID
     */
    public function leave(Request $request, int $id): JsonResponse
    {
        $user = Auth::user();
        if (! $user) {
            return response()->json(['status' => 'error'], 401);
        }

        // 1. 从 Redis 删除该用户
        $this->chatState->userLeave($id, $user->username);

        // 记录退出时间和退出信息
        $user->update([
            'out_time' => now(),
            'out_info' => "正常退出了房间",
        ]);

        // 2. 广播通知他人
        broadcast(new UserLeft($id, $user->username))->toOthers();

        return response()->json(['status' => 'success']);
    }

    /**
     * 获取可用头像列表（返回 JSON）
     * 扫描 /public/images/headface/ 目录，返回所有可用头像文件名
     */
    public function headfaceList(): JsonResponse
    {
        $dir = public_path('images/headface');
        $files = [];

        if (is_dir($dir)) {
            $all = scandir($dir);
            foreach ($all as $file) {
                // 只包含图片文件
                if (preg_match('/\.(gif|jpg|jpeg|png|bmp)$/i', $file)) {
                    $files[] = $file;
                }
            }
        }

        // 自然排序（1, 2, 3... 10, 11...）
        natsort($files);

        return response()->json(['headfaces' => array_values($files)]);
    }

    /**
     * 修改头像（原版 fw.asp 功能）
     * 用户选择一个头像文件名，更新到 usersf 字段
     */
    public function changeAvatar(Request $request): JsonResponse
    {
        $user = Auth::user();
        $headface = $request->input('headface', '');

        if (empty($headface)) {
            return response()->json(['status' => 'error', 'message' => '请选择一个头像'], 422);
        }

        // 验证文件确实存在
        if (! file_exists(public_path('images/headface/'.$headface))) {
            return response()->json(['status' => 'error', 'message' => '头像文件不存在'], 422);
        }

        // 更新用户头像
        $user->usersf = $headface;
        $user->save();

        // 将新头像同步到 Redis 在线用户列表中（所有房间）
        // 通过更新 Redis 的用户信息，使得其他用户和自己刷新后都能看到新头像
        $superLevel = (int) Sysparam::getValue('superlevel', '100');
        $rooms = $this->chatState->getUserRooms($user->username);
        foreach ($rooms as $roomId) {
            $this->chatState->userJoin((int) $roomId, $user->username, [
                'level' => $user->user_level,
                'sex' => $user->sex,
                'headface' => $headface,
                'vip_icon' => $user->vipIcon(),
                'vip_name' => $user->vipName(),
                'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
                'is_admin' => $user->user_level >= $superLevel,
            ]);
        }

        return response()->json([
            'status' => 'success',
            'message' => '头像修改成功！',
            'headface' => $headface,
        ]);
    }

    /**
     * 设置房间公告/祝福语（滚动显示在聊天室顶部）
     * 需要房间主人或等级达到 level_announcement 配置值
     *
     * @param  int  $id  房间ID
     */
    public function setAnnouncement(Request $request, int $id): JsonResponse
    {
        $user = Auth::user();
        $room = Room::findOrFail($id);

        // 权限检查：房间主人 或 等级 >= level_announcement
        $requiredLevel = (int) Sysparam::getValue('level_announcement', '10');
        if ($user->username !== $room->master && $user->user_level < $requiredLevel) {
            return response()->json(['status' => 'error', 'message' => '权限不足，无法修改公告'], 403);
        }

        $request->validate([
            'announcement' => 'required|string|max:500',
        ]);

        $room->announcement = $request->input('announcement');
        $room->save();

        // 广播公告更新到所有在线用户
        $sysMsg = [
            'id' => $this->chatState->nextMessageId($id),
            'room_id' => $id,
            'from_user' => '系统公告',
            'to_user' => '大家',
            'content' => "📢 {$user->username} 更新了房间公告：{$room->announcement}",
            'is_secret' => false,
            'font_color' => '#cc0000',
            'action' => '',
            'sent_at' => now()->toDateTimeString(),
        ];

        $this->chatState->pushMessage($id, $sysMsg);
        broadcast(new MessageSent($id, $sysMsg));

        return response()->json([
            'status' => 'success',
            'message' => '公告已更新！',
            'announcement' => $room->announcement,
        ]);
    }

    /**
     * 送花/礼物：消耗金币给目标用户增加魅力值
     *
     * 根据 gift_id 查找 gifts 表中的礼物类型，读取对应的金币消耗和魅力增量。
     * 送花成功后在聊天室广播带图片的消息。
     */
    public function sendFlower(Request $request): JsonResponse
    {
        $request->validate([
            'to_user' => 'required|string',
            'room_id' => 'required|integer',
            'gift_id' => 'required|integer',
            'count' => 'sometimes|integer|min:1|max:99',
        ]);

        $user = Auth::user();
        if (! $user) {
            return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
        }

        $toUsername = $request->input('to_user');
        $roomId = $request->input('room_id');
        $giftId = $request->integer('gift_id');
        $count = $request->integer('count', 1);

        // 不能给自己送花
        if ($toUsername === $user->username) {
            return response()->json(['status' => 'error', 'message' => '不能给自己送花哦～']);
        }

        // 查找礼物类型
        $gift = Gift::where('id', $giftId)->where('is_active', true)->first();
        if (! $gift) {
            return response()->json(['status' => 'error', 'message' => '礼物不存在或已下架']);
        }

        // 查找目标用户
        $toUser = User::where('username', $toUsername)->first();
        if (! $toUser) {
            return response()->json(['status' => 'error', 'message' => '用户不存在']);
        }

        $totalCost = $gift->cost * $count;
        $totalCharm = $gift->charm * $count;

        // 检查金币余额
        if (($user->jjb ?? 0) < $totalCost) {
            return response()->json([
                'status' => 'error',
                'message' => "金币不足！送 {$count} 份【{$gift->name}】需要 {$totalCost} 金币，您当前有 ".($user->jjb ?? 0).' 枚。',
            ]);
        }

        // 扣除金币、增加对方魅力
        $user->jjb = ($user->jjb ?? 0) - $totalCost;
        $user->save();

        $toUser->meili = ($toUser->meili ?? 0) + $totalCharm;
        $toUser->save();

        // 构建礼物图片 URL
        $giftImageUrl = $gift->image ? "/images/gifts/{$gift->image}" : '';

        // 广播送花消息（含图片标记，前端识别后渲染图片）
        $countText = $count > 1 ? " {$count} 份" : '';
        $sysMsg = [
            'id' => $this->chatState->nextMessageId($roomId),
            'room_id' => $roomId,
            'from_user' => '送花播报',
            'to_user' => $toUsername,
            'content' => "{$gift->emoji} {$user->username} 向 {$toUsername} 送出了{$countText}【{$gift->name}】！魅力 +{$totalCharm}！",
            'is_secret' => false,
            'font_color' => '#e91e8f',
            'action' => '',
            'sent_at' => now()->toDateTimeString(),
            'gift_image' => $giftImageUrl,
            'gift_name' => $gift->name,
        ];

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

        return response()->json([
            'status' => 'success',
            'message' => "送花成功！花费 {$totalCost} 金币，{$toUsername} 魅力 +{$totalCharm}",
            'data' => [
                'my_jjb' => $user->jjb,
                'target_charm' => $toUser->meili,
            ],
        ]);
    }

    /**
     * 聊天获取魅力值（方案 B：每条消息触发，Redis 每小时上限控制）
     *
     * 异性聊天给更多魅力，同性少一些。
     * 系统用户（如 AI小班长）不触发魅力奖励。
     * 发送者和接收者都会获得对应魅力值。
     *
     * @param  mixed  $sender  发送消息的用户模型
     * @param  string  $toUsername  接收消息的用户名
     */
    private function grantChatCharm(mixed $sender, string $toUsername): void
    {
        // 系统用户不参与魅力计算
        $systemNames = ['大家', '系统传音', '系统公告', '钓鱼播报', '星海小博士', 'AI小班长', '送花播报'];
        if (in_array($toUsername, $systemNames)) {
            return;
        }

        // 查找接收者
        $receiver = User::where('username', $toUsername)->first();
        if (! $receiver) {
            return;
        }

        // 检查发送者每小时魅力上限（Redis 自动过期）
        $capKey = "charm_cap:{$sender->username}:".date('YmdH');
        $hourlyLimit = (int) Sysparam::getValue('charm_hourly_limit', '20');
        $currentGained = (int) Redis::get($capKey);
        if ($currentGained >= $hourlyLimit) {
            return; // 已达本小时上限
        }

        // 根据性别关系计算魅力增量
        $senderSex = $sender->sex ?? '';
        $receiverSex = $receiver->sex ?? '';
        $isCrossSex = ($senderSex !== $receiverSex) && $senderSex !== '' && $receiverSex !== '';

        $charmSame = (int) Sysparam::getValue('charm_same_sex', '1');
        $charmCross = (int) Sysparam::getValue('charm_cross_sex', '2');
        $charmGain = $isCrossSex ? $charmCross : $charmSame;

        // 不超过本小时剩余额度
        $remaining = $hourlyLimit - $currentGained;
        $charmGain = min($charmGain, $remaining);
        if ($charmGain <= 0) {
            return;
        }

        // 发送者获得魅力
        $sender->meili = ($sender->meili ?? 0) + $charmGain;
        $sender->save();

        // 更新 Redis 计数器（1 小时过期）
        Redis::incrby($capKey, $charmGain);
        Redis::expire($capKey, 3600);
    }

    /**
     * 解析奖励数值配置（支持固定值或范围格式）
     *
     * 支持格式：
     *   "5"    → 固定返回 5
     *   "1-10" → 随机返回 1~10 之间的整数
     *   "0"    → 返回 0（关闭该奖励）
     *
     * @param  string  $value  配置值
     * @return int 解析后的奖励数值
     */
    private function parseRewardValue(string $value): int
    {
        $value = trim($value);

        // 支持范围格式 "min-max"
        if (str_contains($value, '-')) {
            $parts = explode('-', $value, 2);
            $min = max(0, (int) $parts[0]);
            $max = max($min, (int) $parts[1]);

            return rand($min, $max);
        }

        return max(0, (int) $value);
    }
}
