<?php

namespace App\Services\Settlement;

use App\Models\Draw;
use App\Models\TicketItem;
use App\Lottery\DrawStatus;
use App\Models\JackpotPool;
use App\Models\DrawResultItem;
use App\Models\DrawResultBatch;
use App\Models\SettlementBatch;
use Illuminate\Support\Facades\DB;
use App\Lottery\DrawResultBatchStatus;
use App\Lottery\SettlementBatchStatus;
use App\Models\TicketSettlementDetail;
use App\Services\Draw\DrawHallSnapshotBuilder;
use App\Services\Draw\LotteryHallRealtimeBroadcaster;
use App\Services\Ticket\RiskPoolService;
use App\Services\Jackpot\JackpotBurstAllocator;
use App\Services\AgentSettlement\AgentGameSettlementRecorder;

/**
 * 阶段 6：对已发布开奖、处于 `settling` 的期号执行结算（匹配 → 回水派彩调整 → Jackpot 爆池分配 → 明细 → 风险池释放 → 待审核）。
 *
 * 派彩入账由审核通过后的独立 payout 动作执行，避免未确认结果直接入账。
 */
final class SettlementOrchestrator
{
    public function __construct(
        private readonly SettlementMatcherRegistry $matchers,
        private readonly SettlementPayoutAdjuster $payoutAdjuster,
        private readonly JackpotBurstAllocator $jackpotBurst,
        private readonly RiskPoolService $riskPool,
        private readonly LotteryHallRealtimeBroadcaster $hallRealtime,
        private readonly DrawHallSnapshotBuilder $hallSnapshot,
        private readonly AgentGameSettlementRecorder $agentGameSettlement,
    ) {}

    /**
     * @return bool true 表示已处理（新结算或补全期号状态）
     */
    public function trySettleDraw(Draw $draw): bool
    {
        $afterCommit = DB::transaction(function () use ($draw): array {
            /** @var Draw $locked */
            $locked = Draw::query()->whereKey($draw->id)->lockForUpdate()->firstOrFail();

            if ($locked->status === DrawStatus::Settled->value) {
                return ['handled' => false, 'jackpot_bursts' => [], 'should_notify_status' => false];
            }

            if ($locked->status !== DrawStatus::Settling->value) {
                return ['handled' => false, 'jackpot_bursts' => [], 'should_notify_status' => false];
            }

            $publishedBatch = DrawResultBatch::query()
                ->where('draw_id', $locked->id)
                ->where('status', DrawResultBatchStatus::Published->value)
                ->where('result_version', (int) $locked->current_result_version)
                ->orderByDesc('id')
                ->first();

            if ($publishedBatch === null) {
                return ['handled' => false, 'jackpot_bursts' => [], 'should_notify_status' => false];
            }

            $existingDone = SettlementBatch::query()
                ->where('draw_id', $locked->id)
                ->where('result_batch_id', $publishedBatch->id)
                ->whereIn('status', [
                    SettlementBatchStatus::Running->value,
                    SettlementBatchStatus::PendingReview->value,
                    SettlementBatchStatus::Approved->value,
                    SettlementBatchStatus::Paid->value,
                    SettlementBatchStatus::Completed->value,
                ])
                ->first();

            if ($existingDone !== null) {
                $locked->forceFill([
                    'settle_version' => (int) $existingDone->settle_version,
                ])->save();

                return [
                    'handled' => true,
                    'jackpot_bursts' => [],
                    'should_notify_status' => true,
                ];
            }

            $items = DrawResultItem::query()
                ->where('result_batch_id', $publishedBatch->id)
                ->orderBy('id')
                ->get();

            $board = new PublishedDrawResultBoard($items);

            $nextSettleVersion = (int) $locked->settle_version + 1;

            $batchRow = SettlementBatch::query()->create([
                'draw_id' => $locked->id,
                'result_batch_id' => $publishedBatch->id,
                'settle_version' => $nextSettleVersion,
                'status' => SettlementBatchStatus::Running->value,
                'review_status' => 'pending',
                'started_at' => now(),
            ]);

            $ticketItems = TicketItem::query()
                ->where('draw_id', $locked->id)
                ->where('status', 'pending_draw')
                ->with(['combinations', 'order'])
                ->orderBy('id')
                ->get();

            /** @var list<array{item: TicketItem, gross_win: int, matched_tier: ?string, net_win: int, match_detail: mixed}> $prepared */
            $prepared = [];
            foreach ($ticketItems as $item) {
                $matcher = $this->matchers->for((string) $item->play_code);
                $result = $matcher->match($item, $board, $item->combinations);
                $gross = max(0, (int) $result['win_amount']);
                $tier = $result['matched_prize_tier'] ?? null;
                $tier = is_string($tier) ? $tier : null;
                $net = $this->payoutAdjuster->adjustGrossWin($gross, $item);
                $prepared[] = [
                    'item' => $item,
                    'gross_win' => $gross,
                    'matched_tier' => $tier,
                    'net_win' => $net,
                    'match_detail' => $result['match_detail'],
                ];
            }

            $allocations = [];
            $totalJackpotPayout = 0;
            $jackpotBursts = [];
            $preparedByCurrency = collect($prepared)->groupBy(
                fn (array $p): string => strtoupper((string) ($p['item']->order?->currency_code ?? 'NPR')),
            );
            foreach ($preparedByCurrency as $currency => $currencyPrepared) {
                $pool = JackpotPool::query()
                    ->where('currency_code', $currency)
                    ->where('status', 1)
                    ->lockForUpdate()
                    ->first();
                if ($pool === null) {
                    continue;
                }

                $burstInput = collect($currencyPrepared)->map(fn (array $p): array => [
                    'item' => $p['item'],
                    'matched_tier' => $p['matched_tier'],
                    'gross_win' => $p['gross_win'],
                ]);
                $burstOut = $this->jackpotBurst->allocate($locked, $pool, $burstInput);
                $allocations = array_replace($allocations, $burstOut['allocations']);
                $currencyPayout = (int) $burstOut['pool_payout'];
                $totalJackpotPayout += $currencyPayout;

                if ($currencyPayout > 0 && is_string($burstOut['trigger'])) {
                    $jackpotBursts[] = [
                        'currency' => $currency,
                        'payout' => $currencyPayout,
                        'trigger' => $burstOut['trigger'],
                        'pool_after' => (int) $pool->fresh()->current_amount,
                        'winner_count' => count($burstOut['allocations']),
                    ];
                }
            }

            $ticketCount = 0;
            $winCount = 0;
            $totalPayout = 0;

            foreach ($prepared as $p) {
                /** @var TicketItem $item */
                $item = $p['item'];
                $ticketCount++;
                $net = (int) $p['net_win'];
                $jackpotShare = (int) ($allocations[(int) $item->id] ?? 0);
                $finalCredit = $net + $jackpotShare;

                TicketSettlementDetail::query()->create([
                    'settlement_batch_id' => $batchRow->id,
                    'ticket_item_id' => $item->id,
                    'matched_prize_tier' => $p['matched_tier'],
                    'win_amount' => $net,
                    'jackpot_allocation_amount' => $jackpotShare,
                    'match_detail_json' => $p['match_detail'],
                ]);

                $terminalStatus = $finalCredit > 0 ? 'pending_payout' : 'settled_lose';
                $item->forceFill([
                    'win_amount' => $net,
                    'jackpot_win_amount' => $jackpotShare,
                    'settled_at' => null,
                    'status' => $terminalStatus,
                ])->save();

                $this->agentGameSettlement->recordForTicketItem($item, $net, $terminalStatus);

                if ($finalCredit > 0) {
                    $winCount++;
                }
                $totalPayout += $finalCredit;

                $locks = [];
                foreach ($item->combinations as $c) {
                    $locks[] = [
                        'number_4d' => (string) $c->number_4d,
                        'amount' => (int) $c->estimated_payout,
                    ];
                }
                $this->riskPool->release((int) $locked->id, $item, $locks);
            }

            $batchRow->forceFill([
                'status' => SettlementBatchStatus::PendingReview->value,
                'total_ticket_count' => $ticketCount,
                'total_win_count' => $winCount,
                'total_payout_amount' => $totalPayout,
                'total_jackpot_payout_amount' => $totalJackpotPayout,
                'finished_at' => now(),
            ])->save();

            $locked->forceFill([
                'status' => DrawStatus::Settling->value,
                'settle_version' => $nextSettleVersion,
            ])->save();

            return [
                'handled' => true,
                'jackpot_bursts' => array_map(fn (array $burst): array => [
                    'draw_id' => (int) $locked->id,
                    'draw_no' => (string) $locked->draw_no,
                    'first_prize_number' => $board->firstPrizeNumber4d(),
                    'currency' => (string) $burst['currency'],
                    'payout' => (int) $burst['payout'],
                    'winner_count' => (int) $burst['winner_count'],
                    'trigger' => (string) $burst['trigger'],
                    'pool_after' => (int) $burst['pool_after'],
                ], $jackpotBursts),
                'should_notify_status' => true,
            ];
        });

        foreach ($afterCommit['jackpot_bursts'] as $burst) {
            $this->hallRealtime->notifyJackpotBurst(
                $burst['draw_id'],
                $burst['draw_no'],
                $burst['first_prize_number'],
                $burst['currency'],
                $burst['payout'],
                $burst['winner_count'],
                $burst['trigger'],
                $burst['pool_after'],
            );
        }

        if (($afterCommit['should_notify_status'] ?? false) === true) {
            $this->hallRealtime->notifyStatusChange($this->hallSnapshot->build());
        }

        return (bool) ($afterCommit['handled'] ?? false);
    }
}
