import { Decimal } from '@prisma/client/runtime/library';
import type { AgentsService } from '../../agent/agents.service';
import type { BetsService } from '../../betting/bets.service';
import type { SettlementService } from '../../settlement/settlement.service';
import type { WalletService } from '../../ledger/wallet.service';
import type { PrismaService } from '../../../shared/prisma/prisma.service';
import { expectEqual, expectThrows, expectTrue } from './smoke-test.helpers';
import type { SmokeTestCaseDef } from './smoke-test.cases';
import {
  BetFlowFixtureIds,
  createBetFlowFixture,
  teardownBetFlowFixture,
} from './smoke-test.bet-flow.fixture';

export const BET_FLOW_PROBE_COUNT = 5;

export type BetFlowProbeDeps = {
  prisma: PrismaService;
  wallet: WalletService;
  bets: BetsService;
  settlement: SettlementService;
  agents: AgentsService;
};

async function confirmMatchSettlement(
  deps: BetFlowProbeDeps,
  fx: BetFlowFixtureIds,
  score: { htHome: number; htAway: number; ftHome: number; ftAway: number },
) {
  await deps.settlement.recordScore(
    fx.matchId,
    score.htHome,
    score.htAway,
    score.ftHome,
    score.ftAway,
    fx.operatorId,
  );
  const preview = await deps.settlement.previewSettlement(fx.matchId, fx.operatorId);
  await deps.settlement.confirmSettlement(preview.batch.id, fx.operatorId);
}

export function createBetFlowProbes(deps: BetFlowProbeDeps): SmokeTestCaseDef[] {
  return [
    {
      id: 'BF001',
      suite: 'bet-flow',
      name: '单关赢：冻结后结算派彩',
      uatRef: '8/14',
      description: '下注 100@2.0 主胜，比分 2-1，余额 1000→1100',
      run: async () => {
        const fx = await createBetFlowFixture(deps.prisma, deps.wallet, { initialBalance: 1000 });
        try {
          const bet = await deps.bets.placeSingleBet(
            fx.playerId,
            null,
            fx.homeSelectionId,
            fx.homeOddsVersion,
            100,
            `smoke-win-${fx.runId}`,
          );

          expectEqual('bet.status', bet.status, 'PENDING');
          expectEqual('bet.stake', bet.stake.toString(), '100');

          let w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('available after freeze', w.availableBalance.toString(), '900');
          expectEqual('frozen after freeze', w.frozenBalance.toString(), '100');

          await confirmMatchSettlement(deps, fx, { htHome: 1, htAway: 0, ftHome: 2, ftAway: 1 });

          const settled = await deps.prisma.bet.findUnique({ where: { id: bet.id } });
          expectEqual('settled status', settled?.status, 'WON');
          expectEqual('actualReturn', settled?.actualReturn.toString(), '200');

          w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('available after settle', w.availableBalance.toString(), '1100');
          expectEqual('frozen after settle', w.frozenBalance.toString(), '0');

          const txs = await deps.prisma.walletTransaction.findMany({
            where: { userId: fx.playerId },
            orderBy: { createdAt: 'asc' },
          });
          expectEqual(
            'wallet tx types',
            txs.map((t) => t.transactionType).join(','),
            'MANUAL_DEPOSIT,BET_FREEZE,BET_SETTLE_WIN',
          );
        } finally {
          await teardownBetFlowFixture(deps.prisma, fx);
        }
      },
    },
    {
      id: 'BF002',
      suite: 'bet-flow',
      name: '单关输：冻结本金被消耗',
      uatRef: '14',
      description: '和局选项 + 2-1 比分，余额 1000→900',
      run: async () => {
        const fx = await createBetFlowFixture(deps.prisma, deps.wallet, { initialBalance: 1000 });
        try {
          const bet = await deps.bets.placeSingleBet(
            fx.playerId,
            null,
            fx.drawSelectionId,
            fx.drawOddsVersion,
            100,
            `smoke-lose-${fx.runId}`,
          );

          let w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('available after freeze', w.availableBalance.toString(), '900');
          expectEqual('frozen after freeze', w.frozenBalance.toString(), '100');

          await confirmMatchSettlement(deps, fx, { htHome: 1, htAway: 0, ftHome: 2, ftAway: 1 });

          const settled = await deps.prisma.bet.findUnique({ where: { id: bet.id } });
          expectEqual('settled status', settled?.status, 'LOST');
          expectEqual('actualReturn', settled?.actualReturn.toString(), '0');

          w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('available after settle', w.availableBalance.toString(), '900');
          expectEqual('frozen after settle', w.frozenBalance.toString(), '0');
        } finally {
          await teardownBetFlowFixture(deps.prisma, fx);
        }
      },
    },
    {
      id: 'BF003',
      suite: 'bet-flow',
      name: '下注幂等：相同 requestId 不重复扣款',
      uatRef: '8',
      description: 'userId + requestId 唯一，重复提交返回同一注单',
      run: async () => {
        const fx = await createBetFlowFixture(deps.prisma, deps.wallet, { initialBalance: 500 });
        try {
          const requestId = `smoke-idem-${fx.runId}`;
          const first = await deps.bets.placeSingleBet(
            fx.playerId,
            null,
            fx.homeSelectionId,
            fx.homeOddsVersion,
            50,
            requestId,
          );
          const second = await deps.bets.placeSingleBet(
            fx.playerId,
            null,
            fx.homeSelectionId,
            fx.homeOddsVersion,
            50,
            requestId,
          );

          expectEqual('same bet id', second.id.toString(), first.id.toString());
          const count = await deps.prisma.bet.count({ where: { userId: fx.playerId } });
          expectEqual('bet count', count, 1);

          const w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('available', w.availableBalance.toString(), '450');
          expectEqual('frozen', w.frozenBalance.toString(), '50');
        } finally {
          await teardownBetFlowFixture(deps.prisma, fx);
        }
      },
    },
    {
      id: 'BF004',
      suite: 'bet-flow',
      name: '余额不足：拒绝下注',
      uatRef: '8',
      description: '可用 50 时下 100 注单应失败且无冻结',
      run: async () => {
        const fx = await createBetFlowFixture(deps.prisma, deps.wallet, { initialBalance: 50 });
        try {
          await expectThrows(
            'placeSingleBet',
            async () => {
              await deps.bets.placeSingleBet(
                fx.playerId,
                null,
                fx.homeSelectionId,
                fx.homeOddsVersion,
                100,
                `smoke-insuf-${fx.runId}`,
              );
            },
            'Insufficient balance',
          );

          const count = await deps.prisma.bet.count({ where: { userId: fx.playerId } });
          expectEqual('bet count', count, 0);

          const w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('available', w.availableBalance.toString(), '50');
          expectEqual('frozen', w.frozenBalance.toString(), '0');
        } finally {
          await teardownBetFlowFixture(deps.prisma, fx);
        }
      },
    },
    {
      id: 'BF005',
      suite: 'bet-flow',
      name: '代理额度：结算后 usedCredit 同步',
      uatRef: '4/14',
      description: '玩家输 100 后代理 usedCredit 1000→900',
      run: async () => {
        const fx = await createBetFlowFixture(deps.prisma, deps.wallet, {
          initialBalance: 1000,
          withAgent: true,
        });
        try {
          expectTrue('agent exists', !!fx.agentId);

          await deps.agents.recalculateUsedCredit(fx.agentId!);
          let profile = await deps.prisma.agentProfile.findUnique({
            where: { userId: fx.agentId! },
          });
          expectEqual('usedCredit before bet', profile?.usedCredit.toString(), '1000');

          await deps.bets.placeSingleBet(
            fx.playerId,
            fx.agentId!,
            fx.drawSelectionId,
            fx.drawOddsVersion,
            100,
            `smoke-agent-${fx.runId}`,
          );

          await confirmMatchSettlement(deps, fx, { htHome: 0, htAway: 0, ftHome: 2, ftAway: 1 });

          await deps.agents.recalculateUsedCredit(fx.agentId!);
          profile = await deps.prisma.agentProfile.findUnique({ where: { userId: fx.agentId! } });
          expectEqual('usedCredit after settle', profile?.usedCredit.toString(), '900');

          const w = await deps.wallet.getWallet(fx.playerId);
          expectEqual('player available', w.availableBalance.toString(), '900');
          expectTrue(
            'directPlayerLiability matches wallet',
            new Decimal(profile!.directPlayerLiability).eq(w.availableBalance.add(w.frozenBalance)),
            {
              liability: profile?.directPlayerLiability.toString(),
              wallet: w.availableBalance.add(w.frozenBalance).toString(),
            },
          );
        } finally {
          await teardownBetFlowFixture(deps.prisma, fx);
        }
      },
    },
  ];
}
