<?php

/**
 * 文件功能：VIP 支付业务服务
 * 负责本地 VIP 订单创建、同步远端支付单、处理回调并在支付成功后开通会员
 */

namespace App\Services;

use App\Events\EffectBroadcast;
use App\Events\MessageSent;
use App\Models\User;
use App\Models\VipLevel;
use App\Models\VipPaymentOrder;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * VIP 支付业务服务
 * 负责支付订单创建、支付回调幂等处理，以及会员开通成功后的聊天室喜报广播。
 */
class VipPaymentService
{
    /**
     * 构造函数
     *
     * @param  VipPaymentCenterClient  $paymentCenterClient  支付中心客户端
     * @param  VipService  $vipService  VIP 开通服务
     */
    public function __construct(
        private readonly VipPaymentCenterClient $paymentCenterClient,
        private readonly VipService $vipService,
        private readonly ChatStateService $chatState,
        private readonly RoomBroadcastService $roomBroadcastService,
    ) {}

    /**
     * 创建本地 VIP 支付订单
     *
     * @param  User  $user  购买用户
     * @param  VipLevel  $vipLevel  目标 VIP 等级
     * @param  string  $provider  用户选择的支付渠道
     */
    public function createLocalOrder(User $user, VipLevel $vipLevel, string $provider): VipPaymentOrder
    {
        $currentVip = $user->isVip() ? $user->vipLevel : null;
        $isUpgrade = $currentVip && $vipLevel->isHigherThan($currentVip);
        $provider = $this->normalizeProvider($provider);

        // 如果已经是该等级或更高级别，且不是永久会员续费（逻辑上续费应该用原价，但此处 user 需求是升级补差价）
        // 这里我们主要处理补差价升级逻辑。
        $price = $isUpgrade
            ? $vipLevel->getUpgradePrice($currentVip)
            : (float) $vipLevel->price;

        if ($price < 0.01) {
            // 如果差价极小或为 0（例如同级或降级），抛出异常或根据业务逻辑处理
            if ($isUpgrade) {
                throw new RuntimeException('当前等级差价不足 0.01 元，无法发起升级。');
            }
            if ($user->vip_level_id === $vipLevel->id) {
                // 续费逻辑保持原价
                $price = (float) $vipLevel->price;
            } else {
                throw new RuntimeException('不支持降级购买会员。');
            }
        }

        return VipPaymentOrder::create([
            'order_no' => $this->generateOrderNo(),
            'merchant_order_no' => $this->generateOrderNo(),
            'user_id' => $user->id,
            'vip_level_id' => $vipLevel->id,
            'status' => 'created',
            'amount' => $price,
            'subject' => ($isUpgrade ? '【升级】' : '购买').' VIP 会员 - '.$vipLevel->name,
            // 下单时必须固化用户选择的支付渠道，避免支付中心拒绝未指定 provider 的请求。
            'provider' => $provider,
            'vip_name' => $vipLevel->name,
            'vip_duration_days' => (int) $vipLevel->duration_days,
            'meta' => [
                'username' => $user->username,
                'is_upgrade' => $isUpgrade,
                'old_vip_level_id' => $currentVip?->id,
            ],
        ]);
    }

    /**
     * 调用支付中心创建远端支付单
     *
     * @param  VipPaymentOrder  $vipPaymentOrder  本地 VIP 支付订单
     * @return array<string, mixed>
     */
    public function createRemoteOrder(VipPaymentOrder $vipPaymentOrder): array
    {
        $remoteOrder = $this->paymentCenterClient->createOrder(
            $vipPaymentOrder,
            route('vip.payment.return'),
            route('vip.payment.notify')
        );

        // 将远端平台支付单号回填到本地订单，后续回调和补单都依赖它。
        $vipPaymentOrder->update([
            'payment_order_no' => (string) ($remoteOrder['payment_order_no'] ?? ''),
            'status' => 'pending',
        ]);

        return $remoteOrder;
    }

    /**
     * 根据远端支付单号查找本地订单
     *
     * @param  string|null  $paymentOrderNo  远端平台支付单号
     */
    public function findByPaymentOrderNo(?string $paymentOrderNo): ?VipPaymentOrder
    {
        if (! $paymentOrderNo) {
            return null;
        }

        return VipPaymentOrder::query()->where('payment_order_no', $paymentOrderNo)->first();
    }

    /**
     * 根据本地业务订单号查找本地订单
     *
     * @param  string|null  $merchantOrderNo  商户业务订单号
     */
    public function findByMerchantOrderNo(?string $merchantOrderNo): ?VipPaymentOrder
    {
        if (! $merchantOrderNo) {
            return null;
        }

        return VipPaymentOrder::query()->where('merchant_order_no', $merchantOrderNo)->first();
    }

    /**
     * 在同步回调时主动补查远端状态
     *
     * @param  VipPaymentOrder  $vipPaymentOrder  本地 VIP 支付订单
     */
    public function syncRemoteStatus(VipPaymentOrder $vipPaymentOrder): VipPaymentOrder
    {
        if (! $vipPaymentOrder->payment_order_no || $vipPaymentOrder->isVipOpened()) {
            return $vipPaymentOrder;
        }

        $remoteOrder = $this->paymentCenterClient->queryOrder($vipPaymentOrder->payment_order_no);

        if (($remoteOrder['status'] ?? null) === 'paid') {
            return $this->markOrderAsPaid($vipPaymentOrder, $remoteOrder, 'sync');
        }

        if (($remoteOrder['status'] ?? null) === 'closed') {
            $vipPaymentOrder->update(['status' => 'closed']);
        }

        return $vipPaymentOrder->fresh();
    }

    /**
     * 记录同步回调原始参数
     *
     * @param  VipPaymentOrder  $vipPaymentOrder  本地 VIP 支付订单
     * @param  array<string, mixed>  $payload  同步回调参数
     */
    public function recordSyncReturn(VipPaymentOrder $vipPaymentOrder, array $payload): void
    {
        $vipPaymentOrder->update([
            'sync_return_payload' => $payload,
        ]);
    }

    /**
     * 根据异步通知将订单标记为已支付，并完成会员开通
     *
     * @param  VipPaymentOrder  $vipPaymentOrder  本地 VIP 支付订单
     * @param  array<string, mixed>  $payload  支付中心回调数据
     * @param  string  $source  触发来源：sync|async
     */
    public function markOrderAsPaid(VipPaymentOrder $vipPaymentOrder, array $payload, string $source = 'async'): VipPaymentOrder
    {
        $shouldBroadcastVipCelebration = false;

        $paidOrder = DB::transaction(function () use ($vipPaymentOrder, $payload, $source, &$shouldBroadcastVipCelebration) {
            $lockedOrder = VipPaymentOrder::query()
                ->with(['user', 'vipLevel'])
                ->lockForUpdate()
                ->findOrFail($vipPaymentOrder->id);

            $amount = number_format((float) $lockedOrder->amount, 2, '.', '');
            $callbackAmount = number_format((float) ($payload['amount'] ?? 0), 2, '.', '');

            if ($amount !== $callbackAmount) {
                throw new RuntimeException('支付金额校验失败，已拒绝开通会员。');
            }

            // 无论是同步还是异步，都保留原始回调数据，方便后台排查问题。
            $lockedOrder->fill([
                'payment_order_no' => (string) ($payload['payment_order_no'] ?? $lockedOrder->payment_order_no),
                'provider' => (string) ($payload['provider'] ?? $lockedOrder->provider ?? 'alipay'),
                'provider_trade_no' => (string) ($payload['provider_trade_no'] ?? $lockedOrder->provider_trade_no),
                'status' => 'paid',
                'paid_at' => isset($payload['paid_at']) ? Carbon::parse((string) $payload['paid_at']) : ($lockedOrder->paid_at ?? now()),
            ]);

            if ($source === 'async') {
                $lockedOrder->async_notify_payload = $payload;
            }

            if ($source === 'sync') {
                $lockedOrder->sync_return_payload = array_merge($lockedOrder->sync_return_payload ?? [], $payload);
            }

            $lockedOrder->save();

            if (! $lockedOrder->isVipOpened()) {
                // 只在首次成功支付时开通会员，防止重复回调导致会员时长重复叠加。
                $user = User::query()->lockForUpdate()->findOrFail($lockedOrder->user_id);

                // 从订单扩展信息中识别是否为升级购买，保证会员时长与等级处理一致。
                $isUpgrade = (bool) ($lockedOrder->meta['is_upgrade'] ?? false);

                $this->vipService->grantVip($user, $lockedOrder->vip_level_id, (int) $lockedOrder->vip_duration_days, $isUpgrade);

                // 仅首次开通时触发聊天室喜报，重复回调只更新订单状态不重复刷屏。
                $lockedOrder->update([
                    'opened_vip_at' => now(),
                ]);

                $shouldBroadcastVipCelebration = true;
            }

            return $lockedOrder->fresh(['user', 'vipLevel']);
        });

        if ($shouldBroadcastVipCelebration) {
            $this->broadcastVipPurchaseCelebration($paidOrder);
        }

        return $paidOrder;
    }

    /**
     * 生成本地 VIP 订单号
     */
    private function generateOrderNo(): string
    {
        return 'VPO'.date('YmdHis').random_int(1000, 9999);
    }

    /**
     * 规范化并校验支付渠道
     *
     * @param  string  $provider  前端提交的支付渠道
     */
    private function normalizeProvider(string $provider): string
    {
        $provider = trim(strtolower($provider));

        if (! in_array($provider, ['alipay', 'wechat'], true)) {
            throw new RuntimeException('当前支付方式不受支持。');
        }

        return $provider;
    }

    /**
     * 向用户当前在线的聊天室广播 VIP 购买成功喜报与烟花特效。
     *
     * @param  VipPaymentOrder  $vipPaymentOrder  已完成开通的支付订单
     */
    private function broadcastVipPurchaseCelebration(VipPaymentOrder $vipPaymentOrder): void
    {
        $user = User::query()->with('vipLevel')->find($vipPaymentOrder->user_id);

        if (! $user) {
            return;
        }

        $purchasePayload = $this->roomBroadcastService->buildVipPurchasePayload($user);
        if (empty($purchasePayload)) {
            return;
        }

        $roomIds = $this->chatState->getUserRooms($user->username);
        foreach ($roomIds as $roomId) {
            // 先把喜报写入房间历史，确保当前在线用户和后续短时间内进房的人都能看到。
            $celebrationMessage = [
                'id' => $this->chatState->nextMessageId($roomId),
                'room_id' => $roomId,
                'from_user' => '会员播报',
                'to_user' => '大家',
                'content' => sprintf(
                    '<span style="color: %s; font-weight: bold;">%s</span>',
                    $purchasePayload['presence_color'] ?: '#f59e0b',
                    $purchasePayload['presence_text']
                ),
                'is_secret' => false,
                'font_color' => $purchasePayload['presence_color'] ?: '#f59e0b',
                'action' => 'vip_presence',
                'sent_at' => now()->toDateTimeString(),
            ];

            $celebrationMessage = array_merge($celebrationMessage, $purchasePayload);

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

            // 购买成功固定播放烟花，和会员登录时的豪华表现保持一致。
            broadcast(new EffectBroadcast($roomId, 'fireworks', $user->username));
        }
    }
}
