<?php

/**
 * 文件功能：婚礼仪式与红包业务服务
 *
 * 处理婚礼设置、金币预扣（冻结）、随机红包分配、触发广播及领取逻辑。
 * 所有金币变更通过 UserCurrencyService 统一记账。
 *
 * @author  ChatRoom Laravel
 *
 * @version 1.0.0
 */

namespace App\Services;

use App\Enums\CurrencySource;
use App\Models\Marriage;
use App\Models\User;
use App\Models\WeddingCeremony;
use App\Models\WeddingEnvelopeClaim;
use App\Models\WeddingTier;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class WeddingService
{
    public function __construct(
        private readonly UserCurrencyService $currency,
        private readonly MarriageConfigService $config,
    ) {}

    // ──────────────────────────── 婚礼设置 ────────────────────────────

    /**
     * 设置并创建婚礼（接受求婚后调用）。
     *
     * @param  Marriage  $marriage  已婚记录
     * @param  int|null  $tierId  婚礼档位 ID（null=不举办普天同庆）
     * @param  string  $payerType  'groom'=男方全付 | 'joint'=各半
     * @param  string  $ceremonyType  'immediate'=立即 | 'scheduled'=定时
     * @param  Carbon|null  $ceremonyAt  定时婚礼时间
     * @return array{ok: bool, message: string, ceremony_id: int|null}
     */
    public function setup(
        Marriage $marriage,
        ?int $tierId,
        string $payerType = 'groom',
        string $ceremonyType = 'immediate',
        ?Carbon $ceremonyAt = null,
    ): array {
        // 不举办（仅公告，不发红包）
        if ($tierId === null) {
            return $this->createCeremony($marriage, null, 0, $payerType, $ceremonyType, $ceremonyAt);
        }

        $tier = WeddingTier::find($tierId);
        if (! $tier || ! $tier->is_active) {
            return ['ok' => false, 'message' => '所选婚礼档位不存在或已关闭。', 'ceremony_id' => null];
        }

        // 验证余额
        $groom = $marriage->user;
        $partner = $marriage->partner;
        $total = $tier->amount;

        if ($payerType === 'groom') {
            if (($groom->jjb ?? 0) < $total) {
                return ['ok' => false, 'message' => "金币不足，{$tier->name}需要 {$total} 金币，您当前只有 {$groom->jjb} 金币。", 'ceremony_id' => null];
            }
        } else {
            $half = (int) ceil($total / 2);
            if (($groom->jjb ?? 0) < $half) {
                return ['ok' => false, 'message' => "金币不足，男方需要 {$half} 金币，当前只有 {$groom->jjb} 金币。", 'ceremony_id' => null];
            }
            if (($partner->jjb ?? 0) < $half) {
                return ['ok' => false, 'message' => "金币不足，女方需要 {$half} 金币，当前只有 {$partner->jjb} 金币。", 'ceremony_id' => null];
            }
        }

        return $this->createCeremony($marriage, $tier, $total, $payerType, $ceremonyType, $ceremonyAt);
    }

    /**
     * 创建婚礼记录，并预扣（或冻结）金币。
     */
    private function createCeremony(
        Marriage $marriage,
        ?WeddingTier $tier,
        int $total,
        string $payerType,
        string $ceremonyType,
        ?Carbon $ceremonyAt,
    ): array {
        return DB::transaction(function () use ($marriage, $tier, $total, $payerType, $ceremonyType, $ceremonyAt) {
            $groom = $marriage->user;
            $partner = $marriage->partner;
            $groomAmount = 0;
            $partnerAmount = 0;

            if ($total > 0) {
                if ($payerType === 'groom') {
                    $groomAmount = $total;
                } else {
                    $groomAmount = (int) floor($total / 2);
                    $partnerAmount = $total - $groomAmount;
                }

                if ($ceremonyType === 'immediate') {
                    // 立即扣款
                    $this->currency->change($groom, 'gold', -$groomAmount, CurrencySource::WEDDING_ENV_SEND, "婚礼红包发送（{$tier->name}）");
                    if ($partnerAmount > 0) {
                        $this->currency->change($partner, 'gold', -$partnerAmount, CurrencySource::WEDDING_ENV_SEND, "婚礼红包发送（{$tier->name}）");
                    }
                } else {
                    // 定时婚礼：冻结金币
                    $groom->increment('frozen_jjb', $groomAmount);
                    $groom->decrement('jjb', $groomAmount);
                    if ($partnerAmount > 0) {
                        $partner->increment('frozen_jjb', $partnerAmount);
                        $partner->decrement('jjb', $partnerAmount);
                    }
                }
            }

            $expireHours = $this->config->get('envelope_expire_hours', 24);
            $at = $ceremonyType === 'immediate' ? now() : ($ceremonyAt ?? now());

            $ceremony = WeddingCeremony::create([
                'marriage_id' => $marriage->id,
                'tier_id' => $tier?->id,
                'total_amount' => $total,
                'payer_type' => $payerType,
                'groom_amount' => $groomAmount,
                'partner_amount' => $partnerAmount,
                'ceremony_type' => $ceremonyType,
                'ceremony_at' => $at,
                'status' => $ceremonyType === 'immediate' ? 'active' : 'pending',
                'expires_at' => $at->copy()->addHours($expireHours),
            ]);

            return ['ok' => true, 'message' => '婚礼设置成功！', 'ceremony_id' => $ceremony->id];
        });
    }

    // ──────────────────────────── 婚礼生命周期钩子 ───────────────────

    /**
     * 将预先设置好的定时婚礼（因求婚冻结）转为即刻开始（解冻并记录消费）。
     */
    public function confirmCeremony(WeddingCeremony $ceremony): void
    {
        DB::transaction(function () use ($ceremony) {
            $marriage = $ceremony->marriage;
            $tierName = $ceremony->tier?->name ?? '婚礼';

            if ($ceremony->ceremony_type === 'scheduled') {
                // 解除冻结，正式扣款记账
                if ($ceremony->groom_amount > 0) {
                    $groom = clone $marriage->user;
                    $marriage->user->decrement('frozen_jjb', $ceremony->groom_amount);
                    $this->currency->change($groom, 'gold', 0, CurrencySource::WEDDING_ENV_SEND, "求婚成功，正式发红包（{$tierName}）");
                }
                if ($ceremony->partner_amount > 0) {
                    $partner = clone $marriage->partner;
                    $marriage->partner->decrement('frozen_jjb', $ceremony->partner_amount);
                    $this->currency->change($partner, 'gold', 0, CurrencySource::WEDDING_ENV_SEND, "求婚成功，正式发红包（{$tierName}）");
                }

                // 将类型转为即时开始
                $ceremony->update([
                    'ceremony_type' => 'immediate',
                    'ceremony_at' => now(),
                    'expires_at' => now()->addHours($this->config->get('envelope_expire_hours', 24)),
                ]);
            }
        });
    }

    /**
     * 撤销由于求婚设置的婚礼，并且解冻/退还因为该婚礼冻结的金币。
     */
    public function cancelAndRefund(WeddingCeremony $ceremony): void
    {
        DB::transaction(function () use ($ceremony) {
            $ceremony->update(['status' => 'cancelled']);

            if ($ceremony->ceremony_type === 'scheduled') {
                $marriage = $ceremony->marriage;
                if ($ceremony->groom_amount > 0) {
                    $marriage->user?->decrement('frozen_jjb', $ceremony->groom_amount);
                    $marriage->user?->increment('jjb', $ceremony->groom_amount);
                }
                if ($ceremony->partner_amount > 0) {
                    $marriage->partner?->decrement('frozen_jjb', $ceremony->partner_amount);
                    $marriage->partner?->increment('jjb', $ceremony->partner_amount);
                }
            }
        });
    }

    // ──────────────────────────── 触发婚礼 ────────────────────────────

    /**
     * 触发婚礼：获取在线用户 → 分配红包 → 写入 claims 表。
     * 由立即婚礼（setup 后直接调用）或 TriggerScheduledWeddings Job 调用。
     *
     * @param  WeddingCeremony  $ceremony  婚礼记录
     * @return array{ok: bool, message: string, online_count: int}
     */
    public function trigger(WeddingCeremony $ceremony): array
    {
        if (! in_array($ceremony->status, ['pending', 'active'])) {
            return ['ok' => false, 'message' => '婚礼状态异常。', 'online_count' => 0];
        }

        // 获取所有在线用户（含新郎新娘本人，让所有人都能领红包）
        $onlineIds = $this->getOnlineUserIds(roomId: $ceremony->marriage->room_id ?? 1);

        // 在线人数为0时金币退还
        if (count($onlineIds) === 0 && $ceremony->total_amount > 0) {
            $this->refundCeremony($ceremony);

            return ['ok' => false, 'message' => '当前没有在线用户，红包已退还。', 'online_count' => 0];
        }

        // 随机分配红包金额
        $amounts = $ceremony->total_amount > 0
            ? $this->distributeRedPacket($ceremony->total_amount, count($onlineIds))
            : array_fill(0, count($onlineIds), 0);

        DB::transaction(function () use ($ceremony, $onlineIds, $amounts) {
            $now = now();
            $claims = [];
            foreach ($onlineIds as $i => $userId) {
                $claims[] = [
                    'ceremony_id' => $ceremony->id,
                    'user_id' => $userId,
                    'amount' => $amounts[$i] ?? 0,
                    'claimed' => false,
                    'created_at' => $now,
                ];
            }

            WeddingEnvelopeClaim::insert($claims);

            $ceremony->update([
                'status' => 'active',
                'online_count' => count($onlineIds),
                'ceremony_at' => now(),
            ]);
        });

        return ['ok' => true, 'message' => '婚礼触发成功！', 'online_count' => count($onlineIds)];
    }

    // ──────────────────────────── 领取红包 ────────────────────────────

    /**
     * 用户领取婚礼红包。
     *
     * @param  WeddingCeremony  $ceremony  婚礼记录
     * @param  User  $claimer  领取用户
     * @return array{ok: bool, message: string, amount: int}
     */
    public function claim(WeddingCeremony $ceremony, User $claimer): array
    {
        $claim = WeddingEnvelopeClaim::query()
            ->where('ceremony_id', $ceremony->id)
            ->where('user_id', $claimer->id)
            ->where('claimed', false)
            ->lockForUpdate()
            ->first();

        if (! $claim) {
            return ['ok' => false, 'message' => '没有待领取的红包，或红包已被领取。', 'amount' => 0];
        }

        if ($ceremony->expires_at && $ceremony->expires_at->isPast()) {
            return ['ok' => false, 'message' => '红包已过期。', 'amount' => 0];
        }

        DB::transaction(function () use ($claim, $ceremony, $claimer) {
            $claim->update(['claimed' => true, 'claimed_at' => now()]);
            $ceremony->increment('claimed_count');
            $ceremony->increment('claimed_amount', $claim->amount);

            // 金币入账
            if ($claim->amount > 0) {
                $marriage = $ceremony->marriage;
                $remark = "婚礼红包：{$marriage->user->username} × {$marriage->partner->username}";
                $this->currency->change($claimer, 'gold', $claim->amount, CurrencySource::WEDDING_ENV_RECV, $remark);
            }
        });

        return ['ok' => true, 'message' => "已领取 {$claim->amount} 金币！", 'amount' => $claim->amount];
    }

    // ──────────────────────────── 随机红包算法 ─────────────────────────

    /**
     * 随机红包分配（二倍均值算法）。
     * 保证每人至少 1 金币，总和精确等于 totalAmount。
     *
     * @param  int  $totalAmount  总金额
     * @param  int  $count  人数
     * @return array<int> 每人分配金额
     */
    private function distributeRedPacket(int $totalAmount, int $count): array
    {
        if ($count <= 0 || $totalAmount <= 0) {
            return [];
        }

        // 人数多于金额时，部分人分到0
        if ($totalAmount < $count) {
            $amounts = array_fill(0, $count, 0);
            for ($i = 0; $i < $totalAmount; $i++) {
                $amounts[$i] = 1;
            }
            shuffle($amounts);

            return $amounts;
        }

        $amounts = [];
        $remaining = $totalAmount;

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

        $amounts[] = $remaining; // 最后一人拿剩余
        shuffle($amounts);       // 随机打乱

        return $amounts;
    }

    /**
     * 退还定时婚礼金币（在线人数为0时）。
     */
    private function refundCeremony(WeddingCeremony $ceremony): void
    {
        $ceremony->update(['status' => 'cancelled']);

        // 解冻退还（定时婚礼金币已被冻结在 frozen_jjb）
        if ($ceremony->ceremony_type === 'scheduled') {
            $marriage = $ceremony->marriage;
            if ($ceremony->groom_amount > 0) {
                $marriage->user?->decrement('frozen_jjb', $ceremony->groom_amount);
                $marriage->user?->increment('jjb', $ceremony->groom_amount);
            }
            if ($ceremony->partner_amount > 0) {
                $marriage->partner?->decrement('frozen_jjb', $ceremony->partner_amount);
                $marriage->partner?->increment('jjb', $ceremony->partner_amount);
            }
        }
    }

    /**
     * 从 Redis room:{roomId}:users Hash 获取当前在线用户 ID 列表。
     * 若 Hash value 中无 user_id（旧版登录用户），则用 username 批量查库补齐。
     *
     * @param  int  $roomId  房间 ID
     * @return array<int>
     */
    private function getOnlineUserIds(int $roomId = 1): array
    {
        try {
            $key = "room:{$roomId}:users";
            $users = Redis::hgetall($key);
            if (empty($users)) {
                return [];
            }

            $ids = [];
            $fallbacks = []; // 需要 fallback 查库的用户名

            foreach ($users as $username => $jsonInfo) {
                $info = json_decode($jsonInfo, true);
                if (isset($info['user_id'])) {
                    // 新版登录：user_id 直接存在 Redis
                    $ids[] = (int) $info['user_id'];
                } else {
                    // 旧版登录（修复前）：user_id 缺失，记录 username 待批量查库
                    $fallbacks[] = $username;
                }
            }

            // 对旧用户批量查库补齐 user_id
            if (! empty($fallbacks)) {
                $dbIds = User::whereIn('username', $fallbacks)
                    ->pluck('id')
                    ->map(fn ($id) => (int) $id)
                    ->all();
                $ids = array_merge($ids, $dbIds);
            }

            return array_values(array_unique($ids));
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * 获取用户在指定婚礼中的待领取红包信息。
     */
    public function getUnclaimedEnvelope(WeddingCeremony $ceremony, int $userId): ?WeddingEnvelopeClaim
    {
        return WeddingEnvelopeClaim::query()
            ->where('ceremony_id', $ceremony->id)
            ->where('user_id', $userId)
            ->where('claimed', false)
            ->first();
    }

    /**
     * 返回所有激活档位（前台选择用）。
     *
     * @return Collection<int, WeddingTier>
     */
    public function activeTiers(): Collection
    {
        return WeddingTier::query()->where('is_active', true)->orderBy('tier')->get();
    }
}
