/**
 * Advanced Casino Game Engine
 * Implements provably fair slot mechanics with RTP control,
 * payline matching, scatter/wild symbols, and bonus features
 */

import crypto from "crypto";

// ─── SYMBOL DEFINITIONS ───────────────────────────────────────────────────────

// Use const object instead of enum for better deployment compatibility
export const SymbolType = {
  REGULAR: "regular",
  WILD: "wild",
  SCATTER: "scatter",
  BONUS: "bonus",
  MULTIPLIER: "multiplier",
} as const;

export type SymbolType = typeof SymbolType[keyof typeof SymbolType];

export interface Symbol {
  id: string;
  name: string;
  type: SymbolType;
  value: number; // Payout multiplier (e.g., 5 for 5x)
  emoji: string;
  rarity: number; // 0-1, higher = more common
}

export interface GameConfig {
  gameId: number;
  reels: number;
  rows: number;
  paylines: number;
  symbols: Symbol[];
  minBet: number;
  maxBet: number;
  rtp: number; // 90-98 typical
  volatility: "low" | "medium" | "high" | "very_high";
  features: {
    freeSpinsOnScatter?: { scatterCount: number; freeSpins: number; multiplier: number };
    wildExpand?: boolean;
    wildSticky?: boolean;
    bonusGame?: boolean;
    progressiveJackpot?: boolean;
  };
}

export interface SpinResult {
  reels: string[][];
  winAmount: number;
  multiplier: number;
  winningPaylines: PaylineWin[];
  outcome: "no_win" | "small_win" | "medium_win" | "big_win" | "jackpot" | "free_spins";
  freeSpinsAwarded: number;
  bonusTriggered: boolean;
  seed: string;
  hash: string;
}

export interface PaylineWin {
  paylineId: number;
  symbols: string[];
  multiplier: number;
  winAmount: number;
}

// ─── STANDARD SYMBOL SET ──────────────────────────────────────────────────────

export const STANDARD_SYMBOLS: Symbol[] = [
  { id: "cherry", name: "Cherry", type: SymbolType.REGULAR, value: 2, emoji: "🍒", rarity: 0.15 },
  { id: "lemon", name: "Lemon", type: SymbolType.REGULAR, value: 3, emoji: "🍋", rarity: 0.14 },
  { id: "orange", name: "Orange", type: SymbolType.REGULAR, value: 4, emoji: "🍊", rarity: 0.13 },
  { id: "grape", name: "Grape", type: SymbolType.REGULAR, value: 5, emoji: "🍇", rarity: 0.12 },
  { id: "star", name: "Star", type: SymbolType.REGULAR, value: 8, emoji: "⭐", rarity: 0.10 },
  { id: "diamond", name: "Diamond", type: SymbolType.REGULAR, value: 10, emoji: "💎", rarity: 0.08 },
  { id: "seven", name: "Seven", type: SymbolType.REGULAR, value: 15, emoji: "7️⃣", rarity: 0.06 },
  { id: "bell", name: "Bell", type: SymbolType.REGULAR, value: 20, emoji: "🔔", rarity: 0.04 },
  { id: "bar", name: "BAR", type: SymbolType.REGULAR, value: 25, emoji: "🏷️", rarity: 0.03 },
  { id: "wild", name: "Wild", type: SymbolType.WILD, value: 50, emoji: "🌟", rarity: 0.08 },
  { id: "scatter", name: "Scatter", type: SymbolType.SCATTER, value: 0, emoji: "💫", rarity: 0.06 },
  { id: "bonus", name: "Bonus", type: SymbolType.BONUS, value: 0, emoji: "🎁", rarity: 0.02 },
];

// ─── PAYLINE DEFINITIONS ──────────────────────────────────────────────────────

export function generatePaylines(reels: number, rows: number): number[][] {
  /**
   * Generate standard paylines for a slot machine
   * Returns array of payline definitions (row index per reel)
   */
  const paylines: number[][] = [];

  // Horizontal lines
  for (let row = 0; row < rows; row++) {
    paylines.push(Array(reels).fill(row));
  }

  // V-shaped lines (if 3+ rows)
  if (rows >= 3) {
    // V down
    paylines.push([0, 1, 2, 1, 0]);
    // V up
    paylines.push([2, 1, 0, 1, 2]);
  }

  // Zigzag lines
  if (rows >= 3) {
    paylines.push([0, 1, 1, 1, 0]);
    paylines.push([2, 1, 1, 1, 2]);
  }

  return paylines.slice(0, Math.min(paylines.length, 25)); // Cap at 25 paylines
}

// ─── PROVABLY FAIR SPIN ENGINE ────────────────────────────────────────────────

export class SlotMachine {
  private config: GameConfig;
  private paylines: number[][];

  constructor(config: GameConfig) {
    this.config = config;
    this.paylines = generatePaylines(config.reels, config.rows);
  }

  /**
   * Generate a provably fair spin result
   * Uses seed-based PRNG for reproducibility
   */
  spin(betAmount: number, serverSeed: string, clientSeed: string): SpinResult {
    // Create deterministic hash from seeds
    const combined = `${serverSeed}:${clientSeed}`;
    const hash = crypto.createHash("sha256").update(combined).digest("hex");
    const seed = hash.substring(0, 16);

    // Deterministic RNG using seed
    const rng = this.createSeededRNG(seed);

    // Determine outcome based on RTP
    const outcomeRand = rng();
    const outcome = this.determineOutcome(outcomeRand, this.config.rtp);

    // Generate reels based on outcome
    const reels = this.generateReels(outcome, rng);

    // Calculate wins
    const { winAmount, paylineWins, multiplier } = this.calculateWins(reels, betAmount);

    // Check for bonus features
    const { freeSpinsAwarded, bonusTriggered } = this.checkBonusFeatures(reels);

    // Determine final outcome
    let finalOutcome: SpinResult["outcome"] = "no_win";
    if (freeSpinsAwarded > 0) finalOutcome = "free_spins";
    else if (bonusTriggered) finalOutcome = "no_win"; // Bonus game separate
    else if (winAmount > 0) {
      const mult = multiplier;
      if (mult > 50) finalOutcome = "jackpot";
      else if (mult > 10) finalOutcome = "big_win";
      else if (mult > 2) finalOutcome = "medium_win";
      else finalOutcome = "small_win";
    }

    return {
      reels,
      winAmount,
      multiplier,
      winningPaylines: paylineWins,
      outcome: finalOutcome,
      freeSpinsAwarded,
      bonusTriggered,
      seed,
      hash,
    };
  }

  /**
   * Seeded RNG for reproducible results
   */
  private createSeededRNG(seed: string) {
    let state = parseInt(seed, 16) || 0;
    return () => {
      state = (state * 1103515245 + 12345) & 0x7fffffff;
      return state / 0x7fffffff;
    };
  }

  /**
   * Determine spin outcome based on RTP
   * Returns outcome category: 0=loss, 1=small, 2=medium, 3=big, 4=jackpot
   */
  private determineOutcome(rand: number, rtp: number): number {
    const rtpDecimal = rtp / 100;
    const lossThreshold = 1 - rtpDecimal;

    if (rand < lossThreshold) return 0; // No win
    if (rand < lossThreshold + rtpDecimal * 0.60) return 1; // Small win
    if (rand < lossThreshold + rtpDecimal * 0.85) return 2; // Medium win
    if (rand < lossThreshold + rtpDecimal * 0.98) return 3; // Big win
    return 4; // Jackpot
  }

  /**
   * Generate reel symbols based on desired outcome
   */
  private generateReels(outcome: number, rng: () => number): string[][] {
    const reels: string[][] = [];

    for (let reel = 0; reel < this.config.reels; reel++) {
      const column: string[] = [];
      for (let row = 0; row < this.config.rows; row++) {
        const symbolRand = rng();
        const symbol = this.selectSymbol(symbolRand, outcome);
        column.push(symbol.emoji);
      }
      reels.push(column);
    }

    // Bias reels toward winning combinations for high outcomes
    if (outcome >= 2) {
      this.biasReelsForWin(reels, outcome);
    }

    return reels;
  }

  /**
   * Select symbol based on rarity and outcome
   */
  private selectSymbol(rand: number, outcome: number): Symbol {
    // Higher outcomes favor higher-value symbols
    const rarityThreshold = 0.5 + (outcome * 0.15);

    let accumulated = 0;
    for (const symbol of this.config.symbols) {
      accumulated += symbol.rarity;
      if (rand < accumulated * rarityThreshold) {
        return symbol;
      }
    }

    return this.config.symbols[this.config.symbols.length - 1];
  }

  /**
   * Bias reels to create winning combinations
   */
  private biasReelsForWin(reels: string[][], outcome: number): void {
    if (outcome < 2) return;

    // Select a random payline to win
    const winPayline = this.paylines[Math.floor(Math.random() * this.paylines.length)];
    const winSymbol = this.config.symbols[Math.floor(Math.random() * (this.config.symbols.length - 2))];

    // Place winning symbols on selected payline
    for (let reel = 0; reel < Math.min(3, this.config.reels); reel++) {
      const row = winPayline[reel];
      reels[reel][row] = winSymbol.emoji;
    }
  }

  /**
   * Calculate wins from reel combinations
   */
  private calculateWins(
    reels: string[][],
    betAmount: number
  ): { winAmount: number; paylineWins: PaylineWin[]; multiplier: number } {
    const paylineWins: PaylineWin[] = [];
    let totalWinAmount = 0;

    for (let paylineId = 0; paylineId < this.paylines.length; paylineId++) {
      const payline = this.paylines[paylineId];
      const symbols: string[] = [];
      let matchCount = 0;
      let hasWild = false;
      let hasScatter = false;

      // Collect symbols on this payline
      for (let reel = 0; reel < this.config.reels; reel++) {
        const row = payline[reel];
        const symbol = reels[reel][row];
        symbols.push(symbol);

        const symbolDef = this.config.symbols.find((s) => s.emoji === symbol);
        if (symbolDef?.type === SymbolType.WILD) hasWild = true;
        if (symbolDef?.type === SymbolType.SCATTER) hasScatter = true;
      }

      // Check for matches (including wilds)
      const firstSymbol = symbols[0];
      for (let i = 0; i < symbols.length; i++) {
        const sym = symbols[i];
        const symDef = this.config.symbols.find((s) => s.emoji === sym);
        if (sym === firstSymbol || symDef?.type === SymbolType.WILD) {
          matchCount++;
        } else {
          break;
        }
      }

      // Calculate payout for this payline
      if (matchCount >= 3) {
        const symbolDef = this.config.symbols.find((s) => s.emoji === firstSymbol) || this.config.symbols[0];
        const multiplier = symbolDef.value * matchCount;
        const payout = betAmount * multiplier;

        paylineWins.push({
          paylineId,
          symbols: symbols.slice(0, matchCount),
          multiplier,
          winAmount: payout,
        });

        totalWinAmount += payout;
      }

      // Scatter pays (any position)
      if (hasScatter) {
        const scatterCount = symbols.filter((s) => {
          const def = this.config.symbols.find((sym) => sym.emoji === s);
          return def?.type === SymbolType.SCATTER;
        }).length;

        if (scatterCount >= 2) {
          const scatterPayout = betAmount * scatterCount * 5;
          totalWinAmount += scatterPayout;
        }
      }
    }

    const multiplier = totalWinAmount > 0 ? totalWinAmount / betAmount : 0;

    return { winAmount: totalWinAmount, paylineWins, multiplier };
  }

  /**
   * Check for bonus features (free spins, bonus games)
   */
  private checkBonusFeatures(
    reels: string[][]
  ): { freeSpinsAwarded: number; bonusTriggered: boolean } {
    let freeSpinsAwarded = 0;
    let bonusTriggered = false;

    if (!this.config.features.freeSpinsOnScatter) {
      return { freeSpinsAwarded, bonusTriggered };
    }

    // Count scatter symbols
    let scatterCount = 0;
    for (const reel of reels) {
      for (const symbol of reel) {
        const def = this.config.symbols.find((s) => s.emoji === symbol);
        if (def?.type === SymbolType.SCATTER) {
          scatterCount++;
        }
      }
    }

    // Award free spins
    if (scatterCount >= this.config.features.freeSpinsOnScatter.scatterCount) {
      freeSpinsAwarded = this.config.features.freeSpinsOnScatter.freeSpins;
    }

    // Trigger bonus game
    if (this.config.features.bonusGame) {
      let bonusCount = 0;
      for (const reel of reels) {
        for (const symbol of reel) {
          const def = this.config.symbols.find((s) => s.emoji === symbol);
          if (def?.type === SymbolType.BONUS) {
            bonusCount++;
          }
        }
      }
      if (bonusCount >= 3) {
        bonusTriggered = true;
      }
    }

    return { freeSpinsAwarded, bonusTriggered };
  }
}

// ─── GAME FACTORY ─────────────────────────────────────────────────────────────

export function createGameConfig(
  gameId: number,
  reels: number = 5,
  rows: number = 3,
  paylines: number = 25,
  rtp: number = 96.0,
  volatility: "low" | "medium" | "high" | "very_high" = "medium"
): GameConfig {
  return {
    gameId,
    reels,
    rows,
    paylines,
    symbols: STANDARD_SYMBOLS,
    minBet: 0.01,
    maxBet: 1000,
    rtp,
    volatility,
    features: {
      freeSpinsOnScatter: { scatterCount: 3, freeSpins: 10, multiplier: 2 },
      wildExpand: true,
      wildSticky: false,
      bonusGame: true,
      progressiveJackpot: volatility === "very_high",
    },
  };
}

// ─── PROGRESSIVE JACKPOT TRACKER ──────────────────────────────────────────────

export class ProgressiveJackpot {
  private baseAmount: number;
  private currentAmount: number;
  private contributionRate: number; // % of each bet that feeds the jackpot

  constructor(baseAmount: number = 10000, contributionRate: number = 0.01) {
    this.baseAmount = baseAmount;
    this.currentAmount = baseAmount;
    this.contributionRate = contributionRate;
  }

  addBet(betAmount: number): void {
    this.currentAmount += betAmount * this.contributionRate;
  }

  getAmount(): number {
    return this.currentAmount;
  }

  winJackpot(): number {
    const amount = this.currentAmount;
    this.currentAmount = this.baseAmount;
    return amount;
  }

  reset(): void {
    this.currentAmount = this.baseAmount;
  }
}
