import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Decimal } from '@prisma/client/runtime/library';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { appBadRequest } from '../../shared/common/app-error';

type TxClient = Prisma.TransactionClient;
type PrismaClientLike = PrismaService | TxClient;

export type BettingLimits = {
  minStake: number;
  maxStakeSingle: number;
  maxStakeParlay: number;
  maxPayoutSingle: number;
  maxPayoutParlay: number;
  dailyStakeLimit: number;
};

export const BET_LIMIT_KEYS = {
  minStake: 'bet.min_stake',
  maxStakeSingle: 'bet.max_stake_single',
  maxStakeParlay: 'bet.max_stake_parlay',
  maxPayoutSingle: 'bet.max_payout_single',
  maxPayoutParlay: 'bet.max_payout_parlay',
  dailyStakeLimit: 'bet.daily_stake_limit',
} as const;

const DEFAULTS: BettingLimits = {
  minStake: 1,
  maxStakeSingle: 50000,
  maxStakeParlay: 20000,
  maxPayoutSingle: 500000,
  maxPayoutParlay: 1000000,
  dailyStakeLimit: 200000,
};

@Injectable()
export class BettingLimitsService {
  constructor(private prisma: PrismaService) {}

  private async getNumber(
    key: string,
    fallback: number,
    client: PrismaClientLike = this.prisma,
  ): Promise<number> {
    const row = await client.systemConfig.findUnique({ where: { configKey: key } });
    if (!row) return fallback;
    const n = Number(row.configValue);
    return Number.isFinite(n) && n >= 0 ? n : fallback;
  }

  private async setNumber(key: string, value: number, description: string) {
    await this.prisma.systemConfig.upsert({
      where: { configKey: key },
      create: { configKey: key, configValue: String(value), description },
      update: { configValue: String(value) },
    });
  }

  async getLimits(tx?: TxClient): Promise<BettingLimits> {
    const client = tx ?? this.prisma;
    return {
      minStake: await this.getNumber(BET_LIMIT_KEYS.minStake, DEFAULTS.minStake, client),
      maxStakeSingle: await this.getNumber(BET_LIMIT_KEYS.maxStakeSingle, DEFAULTS.maxStakeSingle, client),
      maxStakeParlay: await this.getNumber(BET_LIMIT_KEYS.maxStakeParlay, DEFAULTS.maxStakeParlay, client),
      maxPayoutSingle: await this.getNumber(BET_LIMIT_KEYS.maxPayoutSingle, DEFAULTS.maxPayoutSingle, client),
      maxPayoutParlay: await this.getNumber(BET_LIMIT_KEYS.maxPayoutParlay, DEFAULTS.maxPayoutParlay, client),
      dailyStakeLimit: await this.getNumber(BET_LIMIT_KEYS.dailyStakeLimit, DEFAULTS.dailyStakeLimit, client),
    };
  }

  async updateLimits(data: Partial<BettingLimits>): Promise<BettingLimits> {
    const desc: Record<keyof BettingLimits, string> = {
      minStake: '最小单注金额',
      maxStakeSingle: '单关最大投注额',
      maxStakeParlay: '串关最大投注额',
      maxPayoutSingle: '单关最高派彩',
      maxPayoutParlay: '串关最高派彩',
      dailyStakeLimit: '玩家每日投注上限',
    };
    for (const [field, key] of Object.entries(BET_LIMIT_KEYS) as Array<
      [keyof BettingLimits, string]
    >) {
      const val = data[field];
      if (val !== undefined) {
        await this.setNumber(key, val, desc[field]);
      }
    }
    return this.getLimits();
  }

  async validateBet(params: {
    userId: bigint;
    betType: 'SINGLE' | 'PARLAY';
    stake: number;
    potentialReturn: Decimal;
    tx?: TxClient;
  }) {
    const client = params.tx ?? this.prisma;
    const limits = await this.getLimits(params.tx);
    const stake = params.stake;

    if (stake < limits.minStake) {
      throw appBadRequest('MIN_STAKE', { minStake: limits.minStake });
    }

    const maxStake = params.betType === 'PARLAY' ? limits.maxStakeParlay : limits.maxStakeSingle;
    if (stake > maxStake) {
      throw appBadRequest('MAX_STAKE', { maxStake });
    }

    const maxPayout =
      params.betType === 'PARLAY' ? limits.maxPayoutParlay : limits.maxPayoutSingle;
    if (params.potentialReturn.gt(maxPayout)) {
      throw appBadRequest('MAX_PAYOUT', { maxPayout });
    }

    if (limits.dailyStakeLimit > 0) {
      const startOfDay = new Date();
      startOfDay.setHours(0, 0, 0, 0);
      const endOfDay = new Date();
      endOfDay.setHours(23, 59, 59, 999);

      const agg = await client.bet.aggregate({
        where: {
          userId: params.userId,
          placedAt: { gte: startOfDay, lte: endOfDay },
          status: { notIn: ['VOID', 'CANCELLED'] },
        },
        _sum: { stake: true },
      });
      const todayStake = new Decimal(agg._sum.stake ?? 0);
      if (todayStake.add(stake).gt(limits.dailyStakeLimit)) {
        throw appBadRequest('DAILY_STAKE_LIMIT', { limit: limits.dailyStakeLimit });
      }
    }
  }
}
