import Decimal from 'decimal.js';
import {
  calculatePayout,
  settleSelection,
  type ScoreInput,
} from './domain/settlement-calculator';
import {
  FT_CORRECT_SCORE_TEMPLATE,
  HT_CORRECT_SCORE_TEMPLATE,
} from '@thebet365/shared';

export type SmartScoreStrategy =
  | 'MIN_PAYOUT'
  | 'MAX_PAYOUT'
  | 'BALANCED'
  | 'TARGET_HOLD';

export type SmartScoreSimBet = {
  stake: Decimal;
  odds: Decimal;
  marketType: string;
  selectionCode: string;
  handicapLine: number | null;
  totalLine: number | null;
};

export type ScoreEvaluation = {
  htHome: number;
  htAway: number;
  ftHome: number;
  ftAway: number;
  totalStake: string;
  totalPayout: string;
  totalRefund: string;
  houseProfit: string;
  houseHoldPct: number;
  playerWinStakePct: number;
  winBets: number;
  loseBets: number;
  pushBets: number;
};

function templateForMarket(marketType: string): string[] {
  if (marketType === 'FT_CORRECT_SCORE') return [...FT_CORRECT_SCORE_TEMPLATE];
  if (marketType.includes('CORRECT_SCORE')) return [...HT_CORRECT_SCORE_TEMPLATE];
  return [];
}

export function evaluateScoreForSingleBets(
  bets: SmartScoreSimBet[],
  score: ScoreInput,
): Omit<ScoreEvaluation, 'htHome' | 'htAway' | 'ftHome' | 'ftAway'> & {
  score: ScoreInput;
} {
  let totalStake = new Decimal(0);
  let totalPayout = new Decimal(0);
  let totalRefund = new Decimal(0);
  let winStake = new Decimal(0);
  let loseStake = new Decimal(0);
  let winBets = 0;
  let loseBets = 0;
  let pushBets = 0;

  for (const bet of bets) {
    totalStake = totalStake.add(bet.stake);
    const result = settleSelection({
      marketType: bet.marketType,
      selectionCode: bet.selectionCode,
      handicapLine: bet.handicapLine,
      totalLine: bet.totalLine,
      score,
      templateScores: templateForMarket(bet.marketType),
    });
    const payout = calculatePayout(bet.stake, bet.odds, result);

    if (result === 'LOSE') {
      loseBets += 1;
      loseStake = loseStake.add(bet.stake);
    } else if (result === 'PUSH' || result === 'VOID') {
      pushBets += 1;
      totalRefund = totalRefund.add(bet.stake);
    } else {
      winBets += 1;
      winStake = winStake.add(bet.stake);
      totalPayout = totalPayout.add(payout);
    }
  }

  const houseProfit = totalStake.sub(totalPayout).sub(totalRefund);
  const houseHoldPct = totalStake.gt(0)
    ? houseProfit.div(totalStake).mul(100).toNumber()
    : 0;
  const playerWinStakePct = totalStake.gt(0)
    ? winStake.div(totalStake).mul(100).toNumber()
    : 0;

  return {
    score,
    totalStake: totalStake.toString(),
    totalPayout: totalPayout.toString(),
    totalRefund: totalRefund.toString(),
    houseProfit: houseProfit.toString(),
    houseHoldPct: Math.round(houseHoldPct * 100) / 100,
    playerWinStakePct: Math.round(playerWinStakePct * 100) / 100,
    winBets,
    loseBets,
    pushBets,
  };
}

function strategyScore(
  evalResult: ReturnType<typeof evaluateScoreForSingleBets>,
  strategy: SmartScoreStrategy,
  targetHoldPct: number,
): number {
  const payout = new Decimal(evalResult.totalPayout).add(evalResult.totalRefund);
  const hold = evalResult.houseHoldPct;
  const winPct = evalResult.playerWinStakePct;

  switch (strategy) {
    case 'MIN_PAYOUT':
      return payout.toNumber();
    case 'MAX_PAYOUT':
      return -payout.toNumber();
    case 'BALANCED':
      return Math.abs(winPct - 50);
    case 'TARGET_HOLD':
      return Math.abs(hold - targetHoldPct);
    default:
      return payout.toNumber();
  }
}

function scoreKey(s: ScoreInput): string {
  return `${s.htHome}-${s.htAway}-${s.ftHome}-${s.ftAway}`;
}

export function suggestScoresForBets(
  bets: SmartScoreSimBet[],
  options: {
    strategies?: SmartScoreStrategy[];
    targetHoldPct?: number;
    maxGoals?: number;
    alternativesPerStrategy?: number;
  } = {},
): {
  suggestions: Array<{
    strategy: SmartScoreStrategy;
    rank: number;
    evaluation: ScoreEvaluation;
  }>;
  searchedCandidates: number;
} {
  const strategies = options.strategies ?? [
    'MIN_PAYOUT',
    'MAX_PAYOUT',
    'BALANCED',
    'TARGET_HOLD',
  ];
  const targetHoldPct = Math.min(100, Math.max(0, options.targetHoldPct ?? 30));
  const maxGoals = Math.min(8, Math.max(2, options.maxGoals ?? 5));

  if (!bets.length) {
    const zero: ScoreInput = { htHome: 0, htAway: 0, ftHome: 0, ftAway: 0 };
    const ev = evaluateScoreForSingleBets(bets, zero);
    return {
      suggestions: strategies.map((strategy, i) => ({
        strategy,
        rank: 1,
        evaluation: {
          htHome: 0,
          htAway: 0,
          ftHome: 0,
          ftAway: 0,
          ...ev,
        },
      })),
      searchedCandidates: 1,
    };
  }

  const allEvals: ReturnType<typeof evaluateScoreForSingleBets>[] = [];
  for (let htHome = 0; htHome <= maxGoals; htHome++) {
    for (let htAway = 0; htAway <= maxGoals; htAway++) {
      for (let ftHome = htHome; ftHome <= maxGoals; ftHome++) {
        for (let ftAway = htAway; ftAway <= maxGoals; ftAway++) {
          const score: ScoreInput = { htHome, htAway, ftHome, ftAway };
          allEvals.push(evaluateScoreForSingleBets(bets, score));
        }
      }
    }
  }

  const suggestions: Array<{
    strategy: SmartScoreStrategy;
    rank: number;
    evaluation: ScoreEvaluation;
  }> = [];
  const usedKeys = new Set<string>();

  for (const strategy of strategies) {
    const sorted = [...allEvals].sort(
      (a, b) =>
        strategyScore(a, strategy, targetHoldPct) -
        strategyScore(b, strategy, targetHoldPct),
    );
    let rank = 0;
    for (const ev of sorted) {
      const key = scoreKey(ev.score);
      if (usedKeys.has(`${strategy}:${key}`)) continue;
      usedKeys.add(`${strategy}:${key}`);
      rank += 1;
      suggestions.push({
        strategy,
        rank,
        evaluation: {
          htHome: ev.score.htHome,
          htAway: ev.score.htAway,
          ftHome: ev.score.ftHome,
          ftAway: ev.score.ftAway,
          totalStake: ev.totalStake,
          totalPayout: ev.totalPayout,
          totalRefund: ev.totalRefund,
          houseProfit: ev.houseProfit,
          houseHoldPct: ev.houseHoldPct,
          playerWinStakePct: ev.playerWinStakePct,
          winBets: ev.winBets,
          loseBets: ev.loseBets,
          pushBets: ev.pushBets,
        },
      });
      if (rank >= (options.alternativesPerStrategy ?? 1)) break;
    }
  }

  return { suggestions, searchedCandidates: allEvals.length };
}
