import { eq, and } from "drizzle-orm";
import { getDb } from "./db.ts";
import { gameSessions, casinoGames, transactions, auditLogs } from "../drizzle/schema.ts";

// ─── TYPES ────────────────────────────────────────────────────────────────────

export interface SlotSymbol {
  id: string;
  name: string;
  multiplier: number;
  isWild: boolean;
  isScatter: boolean;
  frequency: number; // 0-100 (probability weight)
}

export interface SlotPayline {
  id: number;
  positions: number[]; // e.g., [0, 1, 2, 3, 4] for top line
  multiplier: number;
}

export interface SlotReel {
  id: number;
  symbols: SlotSymbol[];
  stopPositions: number[];
}

export interface SlotGameConfig {
  id: number;
  name: string;
  reels: number; // 3, 4, 5, etc.
  rows: number; // 3, 4, 5, etc.
  paylines: SlotPayline[];
  symbols: SlotSymbol[];
  minBet: number;
  maxBet: number;
  maxWinCap: number;
  rtp: number; // 85-99
  volatility: "low" | "medium" | "high";
  bonusFeatures: BonusFeature[];
}

export interface BonusFeature {
  type: "free_spins" | "hold_win" | "expanding_wild" | "cascading" | "multiplier";
  triggerSymbol?: string;
  triggerCount?: number;
  reward?: number;
  multiplier?: number;
}

export interface SpinResult {
  reels: string[][];
  winAmount: number;
  winLines: number[];
  bonusTriggered?: BonusFeature;
  newBalance: number;
  isMaxWinCap: boolean;
}

// ─── SLOT ENGINE ──────────────────────────────────────────────────────────────

export class SlotGameEngine {
  private config: SlotGameConfig;
  private symbolWeights: { [key: string]: number } = {};

  constructor(config: SlotGameConfig) {
    this.config = config;
    this.initializeSymbolWeights();
  }

  private initializeSymbolWeights() {
    const totalFrequency = this.config.symbols.reduce((sum, s) => sum + s.frequency, 0);
    for (const symbol of this.config.symbols) {
      this.symbolWeights[symbol.id] = symbol.frequency / totalFrequency;
    }
  }

  /**
   * Spin the reels and calculate results
   */
  public spin(betAmount: number, rtp: number = this.config.rtp): SpinResult {
    // Validate bet
    if (betAmount < this.config.minBet || betAmount > this.config.maxBet) {
      throw new Error(`Bet must be between ${this.config.minBet} and ${this.config.maxBet}`);
    }

    // Generate reel results
    const reels = this.generateReels();

    // Calculate wins
    let totalWin = 0;
    const winLines: number[] = [];

    for (const payline of this.config.paylines) {
      const lineSymbols = payline.positions.map((pos) => reels[pos][0]);
      const lineWin = this.calculateLineWin(lineSymbols, betAmount, payline);

      if (lineWin > 0) {
        totalWin += lineWin;
        winLines.push(payline.id);
      }
    }

    // Apply RTP adjustment
    totalWin = this.applyRtpAdjustment(totalWin, betAmount, rtp);

    // Apply max win cap
    const isMaxWinCap = totalWin > this.config.maxWinCap;
    if (isMaxWinCap) {
      totalWin = this.config.maxWinCap;
    }

    // Check for bonus triggers
    let bonusTriggered: BonusFeature | undefined;
    for (const bonus of this.config.bonusFeatures) {
      if (this.checkBonusTrigger(reels, bonus)) {
        bonusTriggered = bonus;
        break;
      }
    }

    return {
      reels,
      winAmount: totalWin,
      winLines,
      bonusTriggered,
      newBalance: 0, // Will be set by caller
      isMaxWinCap,
    };
  }

  /**
   * Generate random reel results
   */
  private generateReels(): 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 symbol = this.selectRandomSymbol();
        column.push(symbol);
      }
      reels.push(column);
    }

    return reels;
  }

  /**
   * Select a random symbol based on weights
   */
  private selectRandomSymbol(): string {
    const random = Math.random();
    let cumulative = 0;

    for (const symbolId in this.symbolWeights) {
      const weight = this.symbolWeights[symbolId];
      cumulative += weight;
      if (random <= cumulative) {
        return symbolId;
      }
    }

    return this.config.symbols[0].id;
  }

  /**
   * Calculate win for a payline
   */
  private calculateLineWin(lineSymbols: string[], betAmount: number, payline: SlotPayline): number {
    if (lineSymbols.length === 0) return 0;

    const firstSymbol = lineSymbols[0];
    const symbolConfig = this.config.symbols.find((s) => s.id === firstSymbol);

    if (!symbolConfig) return 0;

    // Check if all symbols match (or are wilds)
    const allMatch = lineSymbols.every((s) => {
      const sym = this.config.symbols.find((x) => x.id === s);
      return s === firstSymbol || sym?.isWild || symbolConfig.isWild;
    });

    if (!allMatch) return 0;

    // Calculate payout
    const baseMultiplier = symbolConfig.multiplier;
    const paylineMultiplier = payline.multiplier;
    const totalMultiplier = baseMultiplier * paylineMultiplier;

    return betAmount * totalMultiplier;
  }

  /**
   * Apply RTP adjustment to win amount
   */
  private applyRtpAdjustment(winAmount: number, betAmount: number, rtp: number): number {
    const targetRtp = rtp / 100;
    const expectedWin = betAmount * targetRtp;

    // Simple RTP adjustment: if win is too high, reduce it proportionally
    if (winAmount > 0) {
      const ratio = expectedWin / (betAmount + winAmount);
      return Math.floor(winAmount * Math.max(ratio, 0.5));
    }

    return 0;
  }

  /**
   * Check if a bonus feature is triggered
   */
  private checkBonusTrigger(reels: string[][], bonus: BonusFeature): boolean {
    if (!bonus.triggerSymbol || !bonus.triggerCount) return false;

    let count = 0;
    for (const column of reels) {
      for (const symbol of column) {
        if (symbol === bonus.triggerSymbol) {
          count++;
        }
      }
    }

    return count >= bonus.triggerCount;
  }

  /**
   * Get game configuration
   */
  public getConfig(): SlotGameConfig {
    return this.config;
  }
}

// ─── DATABASE OPERATIONS ──────────────────────────────────────────────────────

export async function createSlotGame(gameConfig: Omit<SlotGameConfig, "id">): Promise<SlotGameConfig> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const result = await db.insert(casinoGames).values({
    title: gameConfig.name,
    slug: gameConfig.name.toLowerCase().replace(/\s+/g, "-"),
    provider: "PlayCoinKrazy",
    category: "slots",
    rtp: gameConfig.rtp,
    volatility: gameConfig.volatility,
    isActive: 1,
    tags: JSON.stringify({
      reels: gameConfig.reels,
      rows: gameConfig.rows,
      paylines: gameConfig.paylines,
      symbols: gameConfig.symbols,
      minBet: gameConfig.minBet,
      maxBet: gameConfig.maxBet,
      maxWinCap: gameConfig.maxWinCap,
      bonusFeatures: gameConfig.bonusFeatures,
    }),
  });

  return {
    ...gameConfig,
    id: result[0]?.insertId || 0,
  };
}

export async function getSlotGameConfig(gameId: number): Promise<SlotGameConfig | null> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const game = await db.select().from(casinoGames).where(eq(casinoGames.id, gameId)).limit(1);

  if (!game.length) return null;

  const tags = JSON.parse(game[0].tags ? JSON.stringify(game[0].tags) : "{}");

  return {
    id: game[0].id,
    name: game[0].title,
    rtp: game[0].rtp,
    volatility: game[0].volatility as "low" | "medium" | "high",
    ...tags,
  };
}

export async function recordSlotSpin(
  userId: number,
  gameId: number,
  betAmount: number,
  winAmount: number,
  spinResult: SpinResult,
  effectiveRtp: number = 0
): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Record game session
  const multiplier = winAmount > 0 ? winAmount / betAmount : 0;
  await db.insert(gameSessions).values({
    userId,
    gameId,
    betAmount: betAmount.toString(),
    winAmount: winAmount.toString(),
    multiplier,
    currency: "SC",
    outcome: JSON.stringify(spinResult),
    rtpApplied: effectiveRtp,
  });

  // Record transaction (only if there's a net change)
  if (betAmount > 0 || winAmount > 0) {
    const netAmount = winAmount - betAmount;
    await db.insert(transactions).values({
      userId,
      type: winAmount > 0 ? "game_win" : "game_bet",
      amount: netAmount.toString(),
      currency: "SC",
      description: `Slot game spin - ${spinResult.isMaxWinCap ? "MAX WIN CAP" : "normal"}`,
      balanceBefore: "0",
      balanceAfter: netAmount.toString(),
    });
  }

  // Record audit log
  await db.insert(auditLogs).values({
    category: "game",
    action: "slot_spin",
    details: JSON.stringify({
      gameId,
      betAmount,
      winAmount,
      isMaxWinCap: spinResult.isMaxWinCap,
      winLines: spinResult.winLines,
    }),
  });
}

export async function updateSlotGameRtp(gameId: number, newRtp: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  if (newRtp < 85 || newRtp > 99) {
    throw new Error("RTP must be between 85 and 99");
  }

  await db.update(casinoGames).set({ rtp: newRtp }).where(eq(casinoGames.id, gameId));
}

export async function toggleSlotGame(gameId: number, isActive: boolean): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Convert boolean to tinyint (0 or 1)
  await db.update(casinoGames).set({ isActive: isActive ? 1 : 0 }).where(eq(casinoGames.id, gameId));
}
