<?php

namespace App\Services\AgentSettlement;

use App\Models\Player;
use App\Models\TicketItem;
use App\Services\Player\PlayerCreditService;
use App\Support\PlayerFundingMode;
use Illuminate\Support\Facades\DB;

/**
 * 彩票注单游戏结算侧入账：快照、占成流水、回水计提、玩家已用额度。
 */
final class AgentGameSettlementRecorder
{
    public function __construct(
        private readonly BetSettlementSnapshotBuilder $snapshotBuilder,
        private readonly ShareSettlementCalculator $calculator,
        private readonly PlayerCreditService $playerCreditService,
    ) {}

    public function shouldRecord(TicketItem $item): bool
    {
        $player = $item->player;
        if ($player === null) {
            return false;
        }

        return PlayerFundingMode::usesCredit($player)
            && (int) ($player->agent_node_id ?? 0) > 0;
    }

    public function recordForTicketItem(TicketItem $item, int $netWin, string $terminalStatus): void
    {
        if (! $this->shouldRecord($item)) {
            return;
        }

        if ($this->hasActiveShareLedger($item->id)) {
            return;
        }

        $player = $item->player;
        if ($player === null) {
            return;
        }

        $gameType = trim((string) ($item->play_code ?? '')) ?: '*';
        $snapshot = $this->snapshotBuilder->buildForPlayer($player, $gameType);
        $baseRebateRate = $this->baseRebateRateForTicketItem($item);
        $basicRebateRate = $this->normalizeRate($baseRebateRate + (float) $snapshot['rebate_rate']);
        $shareSnapshot = [
            ...$snapshot,
            'base_rebate_rate' => $baseRebateRate,
            'basic_rebate_rate' => $basicRebateRate,
        ];

        $gameWinLoss = $this->platformWinLoss($item, $netWin, $terminalStatus);
        $validBet = (int) $item->total_bet_amount;
        $basicRebate = (int) round($validBet * $basicRebateRate);
        $extraRebate = (int) round($validBet * $snapshot['extra_rebate_rate']);

        $extraByCode = [];
        if ($extraRebate > 0 && $snapshot['chain_codes'] !== []) {
            $leaf = $snapshot['chain_codes'][0];
            $extraByCode[$leaf] = $extraRebate;
        }

        $result = $this->calculator->calculate(
            sharedNetWinLoss: 0,
            totalSharesByCode: $snapshot['total_shares'],
            extraRebateByCode: $extraByCode,
            gameWinLoss: $gameWinLoss,
            basicRebate: $basicRebate,
            chainFromPlayer: $snapshot['chain_codes'],
        );

        $settledAt = now();

        DB::transaction(function () use ($item, $player, $snapshot, $shareSnapshot, $basicRebateRate, $gameWinLoss, $basicRebate, $result, $settledAt, $validBet, $extraRebate, $gameType): void {
            $item->forceFill([
                'agent_node_id' => $snapshot['agent_node_id'],
                'share_snapshot' => $shareSnapshot,
                'agent_rebate_rate_snapshot' => $basicRebateRate,
                'agent_settled_at' => $settledAt,
            ])->save();

            DB::table('share_ledger')->insert([
                'ticket_item_id' => $item->id,
                'player_id' => $player->id,
                'agent_node_id' => $snapshot['agent_node_id'],
                'agent_path' => json_encode($snapshot['agent_path']),
                'share_snapshot' => json_encode($shareSnapshot),
                'game_win_loss' => (int) round($gameWinLoss),
                'basic_rebate' => $basicRebate,
                'shared_net_win_loss' => (int) round($result->sharedNetWinLoss),
                'allocations_json' => json_encode($result->finalProfits),
                'settled_at' => $settledAt,
                'created_at' => $settledAt,
                'updated_at' => $settledAt,
            ]);

            if ($basicRebate > 0) {
                DB::table('rebate_records')->insert([
                    'player_id' => $player->id,
                    'ticket_item_id' => $item->id,
                    'game_type' => $gameType,
                    'valid_bet_amount' => $validBet,
                    'rebate_rate' => $basicRebateRate,
                    'rebate_amount' => $basicRebate,
                    'rebate_type' => 'basic',
                    'owner_agent_id' => $snapshot['agent_node_id'],
                    'status' => 'accrued',
                    'created_at' => $settledAt,
                    'updated_at' => $settledAt,
                ]);
            }

            if ($extraRebate > 0) {
                DB::table('rebate_records')->insert([
                    'player_id' => $player->id,
                    'ticket_item_id' => $item->id,
                    'game_type' => $gameType,
                    'valid_bet_amount' => $validBet,
                    'rebate_rate' => $snapshot['extra_rebate_rate'],
                    'rebate_amount' => $extraRebate,
                    'rebate_type' => 'extra',
                    'owner_agent_id' => $snapshot['agent_node_id'],
                    'status' => 'accrued',
                    'created_at' => $settledAt,
                    'updated_at' => $settledAt,
                ]);
            }

            $holdAmount = (int) $item->actual_deduct_amount;
            if ($holdAmount > 0) {
                $this->playerCreditService->releaseBetHold($player, $holdAmount, $item->id);
            }

            if ($gameWinLoss > 0) {
                $this->playerCreditService->applySettledLoss($player, (int) round($gameWinLoss), $item->id);
            } elseif ($gameWinLoss < 0) {
                $this->playerCreditService->applySettledWin($player, (int) round(abs($gameWinLoss)), $item->id);
            }
        });
    }

    private function baseRebateRateForTicketItem(TicketItem $item): float
    {
        $ruleSnapshot = is_array($item->rule_snapshot_json) ? $item->rule_snapshot_json : [];
        $baseFromRule = isset($ruleSnapshot['base_rebate_rate'])
            ? (float) $ruleSnapshot['base_rebate_rate']
            : null;

        if ($baseFromRule !== null && $baseFromRule > 0) {
            return $this->normalizeRate($baseFromRule);
        }

        $oddsSnapshot = is_array($item->odds_snapshot_json) ? $item->odds_snapshot_json : [];
        foreach ($oddsSnapshot as $row) {
            if (! is_array($row)) {
                continue;
            }

            if (! array_key_exists('rebate_rate', $row)) {
                continue;
            }

            return $this->normalizeRate((float) $row['rebate_rate']);
        }

        return 0.0;
    }

    /** 原分账行存在且尚未被冲正时视为活跃，禁止重复入账。 */
    private function hasActiveShareLedger(int $ticketItemId): bool
    {
        return DB::table('share_ledger as sl')
            ->where('sl.ticket_item_id', $ticketItemId)
            ->whereNull('sl.reversal_of_id')
            ->whereNotExists(function ($query): void {
                $query->selectRaw('1')
                    ->from('share_ledger as rev')
                    ->whereColumn('rev.reversal_of_id', 'sl.id');
            })
            ->exists();
    }

    private function normalizeRate(float $rate): float
    {
        return max(0.0, min(1.0, $rate));
    }

    private function platformWinLoss(TicketItem $item, int $netWin, string $terminalStatus): float
    {
        if ($terminalStatus === 'settled_lose') {
            return (float) max(0, (int) $item->actual_deduct_amount);
        }

        if ($netWin > 0) {
            return -1 * (float) $netWin;
        }

        return 0.0;
    }
}
