<?php

namespace App\Services\AgentSettlement;

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

final class GameSettlementReversalService
{
    public function __construct(
        private readonly PlayerCreditService $playerCreditService,
    ) {}

    public function reverseTicketItem(TicketItem $item): void
    {
        $ledger = DB::table('share_ledger')->where('ticket_item_id', $item->id)->whereNull('reversal_of_id')->first();
        if ($ledger === null) {
            return;
        }

        $settledAt = now();

        DB::transaction(function () use ($item, $ledger, $settledAt): void {
            // 幂等闸门：share_ledger.reversal_of_id 上有 partial unique 索引，
            // 同一原账不允许插入多条反转记录。重复调用时唯一约束冲突即视为已反转、跳过。
            // 一次只让 reversal_of_id 出现一次写入，确保所有后续副作用（rebate、credit 冲正）
            // 只在唯一反转成功路径上执行。
            try {
                DB::table('share_ledger')->insert([
                    'ticket_item_id' => $item->id,
                    'player_id' => $ledger->player_id,
                    'agent_node_id' => $ledger->agent_node_id,
                    'agent_path' => $ledger->agent_path,
                    'share_snapshot' => $ledger->share_snapshot,
                    'game_win_loss' => -1 * (int) $ledger->game_win_loss,
                    'basic_rebate' => -1 * (int) $ledger->basic_rebate,
                    'shared_net_win_loss' => -1 * (int) $ledger->shared_net_win_loss,
                    'allocations_json' => $ledger->allocations_json,
                    'reversal_of_id' => $ledger->id,
                    'settled_at' => $settledAt,
                    'created_at' => $settledAt,
                    'updated_at' => $settledAt,
                ]);
            } catch (QueryException $e) {
                if ($this->isUniqueViolation($e)) {
                    return;
                }
                throw $e;
            }

            $rebates = DB::table('rebate_records')
                ->where('ticket_item_id', $item->id)
                ->where('status', 'accrued')
                ->get();

            foreach ($rebates as $rebate) {
                DB::table('rebate_records')->insert([
                    'player_id' => $rebate->player_id,
                    'ticket_item_id' => $item->id,
                    'game_type' => $rebate->game_type,
                    'valid_bet_amount' => $rebate->valid_bet_amount,
                    'rebate_rate' => $rebate->rebate_rate,
                    'rebate_amount' => -1 * (int) $rebate->rebate_amount,
                    'rebate_type' => $rebate->rebate_type,
                    'owner_agent_id' => $rebate->owner_agent_id,
                    'status' => 'reversed',
                    'reversal_of_id' => $rebate->id,
                    'created_at' => $settledAt,
                    'updated_at' => $settledAt,
                ]);
                DB::table('rebate_records')->where('id', $rebate->id)->update(['status' => 'reversed']);
            }

            $player = Player::query()->find((int) $ledger->player_id);
            if ($player !== null && PlayerFundingMode::usesCredit($player)) {
                $gameWinLoss = (int) $ledger->game_win_loss;
                if ($gameWinLoss !== 0) {
                    $this->playerCreditService->reverseGameSettlement($player, $gameWinLoss, $item->id);
                }
            }
        });
    }

    /**
     * 识别数据库唯一约束冲突（PostgreSQL SQLSTATE 23505；SQLite SQLSTATE 23000/error code 19）。
     * 兜底扫描消息中 unique 字样，兼容驱动返回的 SQLSTATE 缺失场景。
     */
    private function isUniqueViolation(QueryException $e): bool
    {
        $sqlState = (string) $e->getCode();
        $errorInfo = $e->errorInfo ?? null;
        $driverCode = is_array($errorInfo) ? (int) ($errorInfo[1] ?? 0) : 0;

        if ($sqlState === '23505') {
            return true;
        }
        if ($sqlState === '23000' || $sqlState === '19') {
            return true;
        }
        if ($driverCode === 19 || $driverCode === 1062) {
            return true;
        }
        $message = $e->getMessage();
        if (stripos($message, 'unique') !== false || stripos($message, 'UNIQUE constraint') !== false) {
            return true;
        }

        return false;
    }
}
