<?php

/**
 * 文件功能：节日福利触发队列任务
 *
 * 由 Laravel 调度器每分钟检查并触发到期的节日活动：
 * 计算在线用户 → 分配金额 → 写入领取记录 → 广播 WebSocket 事件。
 * 通过 Horizon 队列异步执行，不阻塞 HTTP 请求。
 *
 * @author  ChatRoom Laravel
 *
 * @version 1.0.0
 */

namespace App\Jobs;

use App\Events\HolidayEventStarted;
use App\Events\MessageSent;
use App\Models\HolidayClaim;
use App\Models\HolidayEvent;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class TriggerHolidayEventJob implements ShouldQueue
{
    use Queueable;

    /**
     * 最大重试次数（避免因为临时故障无限重试）。
     */
    public int $tries = 3;

    /**
     * @param  HolidayEvent  $event  节日活动记录
     */
    public function __construct(
        public readonly HolidayEvent $event,
    ) {}

    /**
     * 执行任务：触发节日福利发放。
     */
    public function handle(
        ChatStateService $chatState,
        UserCurrencyService $currency,
    ): void {
        $event = $this->event->fresh();

        // 防止重复触发
        if (! $event || $event->status !== 'pending') {
            return;
        }

        $now = now();
        $expiresAt = $now->copy()->addMinutes($event->expire_minutes);

        // 先标记为 active，防止并发重复触发
        $updated = HolidayEvent::query()
            ->where('id', $event->id)
            ->where('status', 'pending')
            ->update([
                'status' => 'active',
                'triggered_at' => $now,
                'expires_at' => $expiresAt,
            ]);

        if (! $updated) {
            return; // 已被其他进程触发
        }

        $event->refresh();

        // 获取在线用户（满足 target_type 条件）
        $onlineIds = $this->getEligibleOnlineUsers($event, $chatState);

        if (empty($onlineIds)) {
            // 无合格在线用户，直接标记完成
            $event->update(['status' => 'completed']);

            return;
        }

        // 按 max_claimants 限制人数
        if ($event->max_claimants > 0 && count($onlineIds) > $event->max_claimants) {
            shuffle($onlineIds);
            $onlineIds = array_slice($onlineIds, 0, $event->max_claimants);
        }

        // 计算每人金额
        $amounts = $this->distributeAmounts($event, count($onlineIds));

        DB::transaction(function () use ($event, $onlineIds, $amounts, $now) {
            $claims = [];

            foreach ($onlineIds as $i => $userId) {
                $claims[] = [
                    'event_id' => $event->id,
                    'user_id' => $userId,
                    'amount' => $amounts[$i] ?? 0,
                    'claimed_at' => $now,
                ];
            }

            // 批量插入领取记录
            HolidayClaim::insert($claims);
        });

        // 广播全房间 WebSocket 事件
        broadcast(new HolidayEventStarted($event->refresh()));

        // 向聊天室追加系统消息（写入 Redis + 落库）
        $this->pushSystemMessage($event, count($onlineIds), $chatState);

        // 处理重复活动（计算下次触发时间）
        $this->scheduleNextRepeat($event);
    }

    /**
     * 获取满足条件的在线用户 ID 列表。
     *
     * @return array<int>
     */
    private function getEligibleOnlineUsers(HolidayEvent $event, ChatStateService $chatState): array
    {
        try {
            $key = 'room:1:users';
            $users = Redis::hgetall($key);
            if (empty($users)) {
                return [];
            }

            $usernames = array_keys($users);

            // 根据 user_id 从 Redis value 或数据库查出 ID
            $ids = [];
            $fallbacks = [];

            foreach ($users as $username => $jsonInfo) {
                $info = json_decode($jsonInfo, true);
                if (isset($info['user_id'])) {
                    $ids[] = (int) $info['user_id'];
                } else {
                    $fallbacks[] = $username;
                }
            }

            if (! empty($fallbacks)) {
                $dbIds = User::whereIn('username', $fallbacks)->pluck('id')->map(fn ($id) => (int) $id)->all();
                $ids = array_merge($ids, $dbIds);
            }

            $ids = array_values(array_unique($ids));

            // 根据 target_type 过滤
            return match ($event->target_type) {
                'vip' => User::whereIn('id', $ids)->whereNotNull('vip_level_id')->pluck('id')->map(fn ($id) => (int) $id)->all(),
                'level' => User::whereIn('id', $ids)->where('user_level', '>=', (int) ($event->target_value ?? 1))->pluck('id')->map(fn ($id) => (int) $id)->all(),
                default => $ids,
            };
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * 按分配方式计算每人金额数组。
     *
     * @return array<int>
     */
    private function distributeAmounts(HolidayEvent $event, int $count): array
    {
        if ($count <= 0) {
            return [];
        }

        if ($event->distribute_type === 'fixed') {
            // 定额模式：每人相同金额
            $amount = $event->fixed_amount ?? (int) floor($event->total_amount / $count);

            return array_fill(0, $count, $amount);
        }

        // 随机模式：二倍均值算法
        $total = $event->total_amount;
        $min = max(1, $event->min_amount ?? 1);
        $max = $event->max_amount ?? (int) ceil($total * 2 / $count);

        $amounts = [];
        $remaining = $total;

        for ($i = 0; $i < $count - 1; $i++) {
            $remainingPeople = $count - $i;
            $avgDouble = (int) floor($remaining * 2 / $remainingPeople);
            $cap = max($min, min($max, $avgDouble - 1, $remaining - ($remainingPeople - 1) * $min));
            $amounts[] = random_int($min, max($min, $cap));
            $remaining -= end($amounts);
        }

        $amounts[] = max($min, $remaining);
        shuffle($amounts);

        return $amounts;
    }

    /**
     * 向聊天室推送系统公告消息并写入 Redis + 落库。
     */
    private function pushSystemMessage(HolidayEvent $event, int $claimCount, ChatStateService $chatState): void
    {
        $typeLabel = $event->distribute_type === 'fixed' ? "每人固定 {$event->fixed_amount} 金币" : '随机分配';
        $content = "🎊 【{$event->name}】节日福利开始啦！总奖池 💰".number_format($event->total_amount)
            ." 金币，{$typeLabel}，共 {$claimCount} 名在线用户可领取！点击弹窗按钮立即领取！";

        $msg = [
            'id' => $chatState->nextMessageId(1),
            'room_id' => 1,
            'from_user' => '系统传音',
            'to_user' => '大家',
            'content' => $content,
            'is_secret' => false,
            'font_color' => '#f59e0b',
            'action' => '大声宣告',
            'sent_at' => now()->toDateTimeString(),
        ];

        $chatState->pushMessage(1, $msg);
        broadcast(new MessageSent(1, $msg));
        SaveMessageJob::dispatch($msg);
    }

    /**
     * 处理重复活动：计算下次触发时间并重置状态。
     */
    private function scheduleNextRepeat(HolidayEvent $event): void
    {
        $nextSendAt = match ($event->repeat_type) {
            'daily' => $event->send_at->copy()->addDay(),
            'weekly' => $event->send_at->copy()->addWeek(),
            'monthly' => $event->send_at->copy()->addMonth(),
            default => null, // 'once' 或 'cron' 不自动重复
        };

        if ($nextSendAt) {
            $event->update([
                'status' => 'pending',
                'send_at' => $nextSendAt,
                'triggered_at' => null,
                'expires_at' => null,
                'claimed_count' => 0,
                'claimed_amount' => 0,
            ]);
        } else {
            $event->update(['status' => 'completed']);
        }
    }
}
