<?php

/**
 * 文件功能：前台每日签到控制器
 *
 * 提供签到状态查询、领取奖励、刷新在线名单载荷和聊天室签到通知。
 */

namespace App\Http\Controllers;

use App\Events\MessageSent;
use App\Events\UserStatusUpdated;
use App\Http\Requests\ClaimDailySignInRequest;
use App\Http\Requests\DailySignInCalendarRequest;
use App\Http\Requests\MakeupDailySignInRequest;
use App\Models\DailySignIn;
use App\Models\User;
use App\Models\UserIdentityBadge;
use App\Services\ChatStateService;
use App\Services\ChatUserPresenceService;
use App\Services\SignInService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;

/**
 * 类功能：处理前台用户每日签到状态与领取奖励流程。
 */
class DailySignInController extends Controller
{
    /**
     * 构造每日签到控制器依赖。
     */
    public function __construct(
        private readonly SignInService $signInService,
        private readonly ChatStateService $chatState,
        private readonly ChatUserPresenceService $presenceService,
    ) {}

    /**
     * 方法功能：查询当前用户今日签到状态和奖励预览。
     */
    public function status(): JsonResponse
    {
        /** @var User $user */
        $user = Auth::user();
        $status = $this->signInService->status($user);

        return response()->json([
            'status' => 'success',
            'data' => $this->formatStatusPayload($user, $status),
        ]);
    }

    /**
     * 方法功能：查询指定月份的签到日历与补签卡状态。
     */
    public function calendar(DailySignInCalendarRequest $request): JsonResponse
    {
        /** @var User $user */
        $user = Auth::user();

        return response()->json([
            'status' => 'success',
            'data' => $this->signInService->calendar($user, $request->validated('month')),
        ]);
    }

    /**
     * 方法功能：领取今日签到奖励并同步聊天室在线名单。
     */
    public function claim(ClaimDailySignInRequest $request): JsonResponse
    {
        /** @var User $user */
        $user = Auth::user();
        $roomId = $request->validated('room_id');
        $roomId = $roomId === null ? null : (int) $roomId;

        $dailySignIn = $this->signInService->claim($user, $roomId);
        if (! $dailySignIn->wasRecentlyCreated) {
            return response()->json([
                'status' => 'error',
                'message' => '今日已签到，请明天再来。',
                'data' => $this->formatClaimPayload($user->fresh(), $dailySignIn),
            ], 422);
        }

        $freshUser = $user->fresh(['vipLevel', 'activePosition.position.department']);
        $presencePayload = $this->presenceService->build($freshUser);
        $this->refreshOnlinePresence($freshUser, $presencePayload);

        if ($roomId !== null && $this->chatState->isUserInRoom($roomId, $freshUser->username)) {
            $this->broadcastSignInNotice($freshUser, $dailySignIn, $roomId);
        }

        return response()->json([
            'status' => 'success',
            'message' => $this->buildSuccessMessage($dailySignIn),
            'data' => $this->formatClaimPayload($freshUser, $dailySignIn, $presencePayload),
        ]);
    }

    /**
     * 方法功能：使用补签卡补签历史漏签日期。
     */
    public function makeup(MakeupDailySignInRequest $request): JsonResponse
    {
        /** @var User $user */
        $user = Auth::user();
        $roomId = $request->validated('room_id');
        $roomId = $roomId === null ? null : (int) $roomId;

        $dailySignIn = $this->signInService->makeup($user, (string) $request->validated('target_date'), $roomId);
        $refreshedSignIn = $dailySignIn->fresh();
        $latestSignIn = DailySignIn::query()
            ->where('user_id', $user->id)
            ->latest('sign_in_date')
            ->first();
        $currentStreakDays = (int) ($latestSignIn?->streak_days ?? $refreshedSignIn?->streak_days ?? 0);

        $freshUser = $user->fresh(['vipLevel', 'activePosition.position.department']);
        $presencePayload = $this->presenceService->build($freshUser);
        $this->refreshOnlinePresence($freshUser, $presencePayload);

        if ($roomId !== null && $this->chatState->isUserInRoom($roomId, $freshUser->username)) {
            $this->broadcastSignInNotice($freshUser, $refreshedSignIn, $roomId, $currentStreakDays);
        }

        return response()->json([
            'status' => 'success',
            'message' => '补签成功，'.$refreshedSignIn?->sign_in_date?->format('Y-m-d').' 已补签，当前连续签到 '.$currentStreakDays.' 天。',
            'data' => $this->formatClaimPayload($freshUser, $refreshedSignIn, $presencePayload, $currentStreakDays),
        ]);
    }

    /**
     * 方法功能：刷新用户当前所在房间的 Redis 在线载荷并广播名单更新。
     *
     * @param  array<string, mixed>  $presencePayload
     */
    private function refreshOnlinePresence(User $user, array $presencePayload): void
    {
        foreach ($this->chatState->getUserRooms($user->username) as $activeRoomId) {
            // 签到身份会展示在在线名单里，必须立即回写 Redis 载荷。
            $this->chatState->userJoin((int) $activeRoomId, $user->username, $presencePayload);
            broadcast(new UserStatusUpdated((int) $activeRoomId, $user->username, $presencePayload));
        }
    }

    /**
     * 方法功能：向当前聊天室广播签到成功通知。
     */
    private function broadcastSignInNotice(User $user, DailySignIn $dailySignIn, int $roomId, ?int $currentStreakDays = null): void
    {
        $message = [
            'id' => $this->chatState->nextMessageId($roomId),
            'room_id' => $roomId,
            'from_user' => '签到播报',
            'to_user' => '大家',
            'content' => $this->buildNoticeContent($user, $dailySignIn, $currentStreakDays),
            'is_secret' => false,
            'font_color' => '#0f766e',
            'action' => '',
            'sent_at' => now()->toDateTimeString(),
        ];

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

    /**
     * 方法功能：生成聊天室内的签到播报内容。
     */
    private function buildNoticeContent(User $user, DailySignIn $dailySignIn, ?int $currentStreakDays = null): string
    {
        $rewardText = $this->buildRewardText($dailySignIn);
        $identityText = $dailySignIn->identity_badge_name
            ? '，获得身份 '.e($dailySignIn->identity_badge_name)
            : '';

        if ($dailySignIn->is_makeup) {
            $signInDate = $dailySignIn->sign_in_date?->format('Y-m-d') ?? '漏签日期';
            $streakDays = $currentStreakDays ?? (int) $dailySignIn->streak_days;

            return '【'.e($user->username).'】使用补签卡补签 '.$signInDate
                .'，当前连续签到 '.$streakDays.' 天，获得 '.$rewardText.$identityText.'。';
        }

        $quickButton = '<button type="button" onclick="window.quickDailySignIn && window.quickDailySignIn()" '
            .'style="display:inline-block;margin-left:6px;padding:1px 8px;border:none;border-radius:999px;'
            .'background:#ccfbf1;color:#0f766e;font-size:10px;font-weight:bold;cursor:pointer;vertical-align:middle;">'
            .'✅ 快速签到</button>';

        return '【'.e($user->username).'】完成今日签到，连续签到 '
            .$dailySignIn->streak_days.' 天，获得 '.$rewardText.$identityText.'。'.$quickButton;
    }

    /**
     * 方法功能：生成本机签到成功提示文案。
     */
    private function buildSuccessMessage(DailySignIn $dailySignIn): string
    {
        return '签到成功，连续签到 '.$dailySignIn->streak_days.' 天，获得 '.$this->buildRewardText($dailySignIn).'。';
    }

    /**
     * 方法功能：按实际签到奖励快照生成奖励描述。
     */
    private function buildRewardText(DailySignIn $dailySignIn): string
    {
        $items = [];
        if ($dailySignIn->gold_reward > 0) {
            $items[] = $dailySignIn->gold_reward.' 金币';
        }
        if ($dailySignIn->exp_reward > 0) {
            $items[] = $dailySignIn->exp_reward.' 经验';
        }
        if ($dailySignIn->charm_reward > 0) {
            $items[] = $dailySignIn->charm_reward.' 魅力';
        }

        return $items === [] ? '签到记录' : implode(' + ', $items);
    }

    /**
     * 方法功能：格式化状态查询响应载荷。
     *
     * @param  array<string, mixed>  $status
     * @return array<string, mixed>
     */
    private function formatStatusPayload(User $user, array $status): array
    {
        return [
            'signed_today' => $status['signed_today'],
            'can_claim' => $status['can_claim'],
            'current_streak_days' => $status['current_streak_days'],
            'claimable_streak_days' => $status['claimable_streak_days'],
            'preview_rule' => $status['matched_rule']?->only([
                'streak_days',
                'gold_reward',
                'exp_reward',
                'charm_reward',
                'identity_badge_name',
                'identity_badge_icon',
                'identity_badge_color',
                'identity_duration_days',
            ]),
            'identity' => $this->formatIdentityPayload($status['current_identity']),
            'user' => [
                'jjb' => (int) $user->jjb,
            ],
        ];
    }

    /**
     * 方法功能：格式化签到领取响应载荷。
     *
     * @param  array<string, mixed>|null  $presencePayload
     * @return array<string, mixed>
     */
    private function formatClaimPayload(User $user, DailySignIn $dailySignIn, ?array $presencePayload = null, ?int $currentStreakDays = null): array
    {
        $identity = $user->currentSignInIdentity();

        return [
            'sign_in' => [
                'id' => $dailySignIn->id,
                'sign_in_date' => $dailySignIn->sign_in_date?->toDateString(),
                'is_makeup' => (bool) $dailySignIn->is_makeup,
                'streak_days' => (int) $dailySignIn->streak_days,
                'gold_reward' => (int) $dailySignIn->gold_reward,
                'exp_reward' => (int) $dailySignIn->exp_reward,
                'charm_reward' => (int) $dailySignIn->charm_reward,
            ],
            'current_streak_days' => $currentStreakDays ?? (int) $dailySignIn->streak_days,
            'identity' => $this->formatIdentityPayload($identity),
            'presence' => $presencePayload ?? $this->presenceService->build($user),
            'user' => [
                'jjb' => (int) $user->jjb,
                'gold' => (int) $user->jjb,
            ],
        ];
    }

    /**
     * 方法功能：格式化签到身份数据供前端展示。
     *
     * @return array<string, mixed>|null
     */
    private function formatIdentityPayload(?UserIdentityBadge $identity): ?array
    {
        if ($identity === null) {
            return null;
        }

        return [
            'key' => $identity->badge_code,
            'label' => $identity->badge_name,
            'name' => $identity->badge_name,
            'icon' => $identity->badge_icon ?? '✅',
            'color' => $identity->badge_color ?? '#0f766e',
            'expires_at' => $identity->expires_at?->toIso8601String(),
            'streak_days' => (int) data_get($identity->metadata, 'streak_days', 0),
        ];
    }
}
