<?php

/**
 * 文件功能：钓鱼小游戏控制器
 *
 * 新增随机浮漂点击防挂机机制：
 * - 抛竿时生成一次性 token 和随机浮漂坐标（百分比），返回给前端
 * - 前端显示漂浮动画，等待下沉后用户点击，将 token 随收竿请求提交
 * - 若持有有效「自动钓鱼卡」，前端直接自动点击，无需手动操作
 * - 服务端验证 token 有效性，防止脚本直接调用收竿接口
 *
 * @author   ChatRoom Laravel
 *
 * @version  2.0.0
 */

namespace App\Http\Controllers;

use App\Enums\CurrencySource;
use App\Models\GameConfig;
use App\Models\Sysparam;
use App\Services\ChatStateService;
use App\Services\FishingService;
use App\Services\ShopService;
use App\Services\UserCurrencyService;
use App\Services\VipService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;

class FishingController extends Controller
{
    public function __construct(
        private readonly ChatStateService $chatState,
        private readonly VipService $vipService,
        private readonly UserCurrencyService $currencyService,
        private readonly ShopService $shopService,
        private readonly FishingService $fishingService,
    ) {}

    /**
     * 抛竿 — 检查冷却和金币，扣除金币，生成浮漂 token 和随机坐标。
     *
     * 返回：
     *   wait_time     — 等待秒数（前端倒数后触发下沉动画）
     *   bobber_x/y    — 浮漂随机位置（0-100 百分比）
     *   token         — 本次钓鱼唯一令牌（收竿时必须携带）
     *   auto_fishing  — 是否持有有效自动钓鱼卡（前端据此自动点击）
     *
     * @param  int  $id  房间ID
     */
    public function cast(Request $request, int $id): JsonResponse
    {
        $user = Auth::user();
        if (! $user) {
            return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
        }

        // 检查钓鱼全局开关
        if (! GameConfig::isEnabled('fishing')) {
            return response()->json(['status' => 'error', 'message' => '钓鱼功能暂未开放。'], 403);
        }

        // 1. 检查冷却时间（Redis TTL）
        $cooldownKey = "fishing:cd:{$user->id}";
        if (Redis::exists($cooldownKey)) {
            $ttl = Redis::ttl($cooldownKey);

            return response()->json([
                'status' => 'error',
                'message' => "钓鱼冷却中，还需等待 {$ttl} 秒。",
                'cooldown' => $ttl,
            ], 429);
        }

        // 2. 检查金币是否足够
        $cost = (int) (GameConfig::param('fishing', 'fishing_cost') ?? Sysparam::getValue('fishing_cost', '5'));
        if (($user->jjb ?? 0) < $cost) {
            return response()->json([
                'status' => 'error',
                'message' => "金币不足！钓鱼需要 {$cost} 金币，您当前只有 {$user->jjb} 金币。",
            ], 422);
        }

        // 3. 扣除金币
        $this->currencyService->change(
            $user, 'gold', -$cost,
            CurrencySource::FISHING_COST,
            "钓鱼抛竿消耗 {$cost} 金币",
            $id,
        );
        $user->refresh();

        // 4. 生成一次性 token，存入 Redis（TTL = 等待时间 + 收竿窗口 + 缓冲）
        $waitMin = (int) (GameConfig::param('fishing', 'fishing_wait_min') ?? Sysparam::getValue('fishing_wait_min', '8'));
        $waitMax = (int) (GameConfig::param('fishing', 'fishing_wait_max') ?? Sysparam::getValue('fishing_wait_max', '15'));
        $waitTime = rand($waitMin, $waitMax);
        $token = Str::random(32);
        $tokenKey = "fishing:token:{$user->id}";
        // token 有效期 = 等待时间 + 8秒点击窗口 + 5秒缓冲
        // 同时把 cast 时间戳和 wait_time 一起存入，供 reel 做服务端时间校验
        Redis::setex($tokenKey, $waitTime + 13, json_encode([
            'token' => $token,
            'cast_at' => time(),
            'wait_time' => $waitTime,
        ]));

        // 5. 生成随机浮漂坐标（百分比，避开边缘）
        $bobberX = rand(15, 85); // 左右 15%~85%
        $bobberY = rand(20, 65); // 上下 20%~65%

        // 6. 检查是否持有有效自动钓鱼卡
        $autoFishingMinutes = $this->shopService->getActiveAutoFishingMinutesLeft($user);

        return response()->json([
            'status' => 'success',
            'message' => "已花费 {$cost} 金币，鱼竿已抛出！等待鱼儿上钩...",
            'wait_time' => $waitTime,
            'bobber_x' => $bobberX,
            'bobber_y' => $bobberY,
            'token' => $token,
            'auto_fishing' => $autoFishingMinutes > 0,
            'auto_fishing_minutes_left' => $autoFishingMinutes,
            'cost' => $cost,
            'jjb' => $user->jjb,
        ]);
    }

    /**
     * 收竿 — 验证浮漂 token，随机计算钓鱼结果，更新经验/金币，广播到聊天室。
     *
     * 必须携带 token（从抛竿接口获取），否则判定为非法收竿。
     *
     * @param  int  $id  房间ID
     */
    public function reel(Request $request, int $id): JsonResponse
    {
        $user = Auth::user();
        if (! $user) {
            return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
        }

        // 1. 验证 token + 服务端时间校验（防止前端篡改 wait_time 跳过等待）
        $tokenKey = "fishing:token:{$user->id}";
        $storedJson = Redis::get($tokenKey);
        $clientToken = $request->input('token', '');

        if (! $storedJson) {
            return response()->json([
                'status' => 'error',
                'message' => '鱼儿跑了！浮漂已超时，请重新抛竿。',
            ], 422);
        }

        $stored = json_decode($storedJson, true);
        // 校验 token 一致性
        if (($stored['token'] ?? '') !== $clientToken) {
            return response()->json([
                'status' => 'error',
                'message' => '令牌无效，请重新抛竿。',
            ], 422);
        }
        // 校验服务端时间：距抛竿必须已过 wait_time 秒（允许 ±1s 误差）
        $elapsed = time() - (int) ($stored['cast_at'] ?? 0);
        $required = (int) ($stored['wait_time'] ?? 0);
        if ($elapsed < $required - 1) {
            return response()->json([
                'status' => 'error',
                'message' => '鱼还没上钩，别急！',
            ], 422);
        }

        // 清除 token（一次性）
        Redis::del($tokenKey);

        // 2. 设置冷却时间
        $cooldown = (int) (GameConfig::param('fishing', 'fishing_cooldown') ?? Sysparam::getValue('fishing_cooldown', '300'));
        Redis::setex("fishing:cd:{$user->id}", $cooldown, time());

        // 3. 随机决定钓鱼结果并广播（直接调用服务）
        $result = $this->fishingService->processCatch($user, $id, false);

        return response()->json([
            'status' => 'success',
            'result' => $result,
            'exp_num' => $user->exp_num,
            'jjb' => $user->jjb,
            'cooldown_seconds' => $cooldown, // 前端自动钓鱼卡循环等待用
        ]);
    }
}
