import crypto from 'crypto';

/**
 * Custom Game Engine Framework
 * Provides base classes and utilities for building original slot games
 */

export interface GameConfig {
  id: string;
  name: string;
  reels: number;
  symbols: string[];
  paylines: number;
  rtp: number;
  volatility: 'low' | 'medium' | 'high';
  minBet: number;
  maxBet: number;
  maxWin: number;
  bonusFeatures: string[];
}

export interface SpinResult {
  reels: number[][];
  win: number;
  multiplier: number;
  bonusTriggered: boolean;
  bonusType?: string;
  freeSpins?: number;
  winLines: WinLine[];
}

export interface WinLine {
  payline: number;
  symbols: number[];
  payout: number;
}

export interface PayoutTable {
  [key: string]: number;
}

/**
 * Provably Fair RNG
 * Uses cryptographic hashing for fairness verification
 */
export class ProveablyFairRNG {
  private serverSeed: string;
  private clientSeed: string;
  private nonce: number;

  constructor(serverSeed: string, clientSeed: string = '') {
    this.serverSeed = serverSeed;
    this.clientSeed = clientSeed || crypto.randomBytes(16).toString('hex');
    this.nonce = 0;
  }

  /**
   * Generate random number between 0 and max
   */
  generateRandom(max: number): number {
    this.nonce++;
    const combined = `${this.serverSeed}:${this.clientSeed}:${this.nonce}`;
    const hash = crypto.createHash('sha256').update(combined).digest();
    const randomValue = hash.readUInt32BE(0) % max;
    return randomValue;
  }

  /**
   * Generate reel spin result
   */
  generateReelSpin(reelCount: number, symbolCount: number): number[] {
    const reels: number[] = [];
    for (let i = 0; i < reelCount; i++) {
      reels.push(this.generateRandom(symbolCount));
    }
    return reels;
  }

  /**
   * Get verification hash
   */
  getVerificationHash(): string {
    const combined = `${this.serverSeed}:${this.clientSeed}`;
    return crypto.createHash('sha256').update(combined).digest('hex');
  }

  /**
   * Verify spin fairness
   */
  static verifyFairness(serverSeed: string, clientSeed: string, nonce: number, expectedHash: string): boolean {
    const combined = `${serverSeed}:${clientSeed}:${nonce}`;
    const hash = crypto.createHash('sha256').update(combined).digest('hex');
    return hash === expectedHash;
  }
}

/**
 * Base Game Class
 */
export abstract class BaseSlotGame {
  protected config: GameConfig;
  protected payoutTable: PayoutTable;
  protected rng: ProveablyFairRNG;

  constructor(config: GameConfig, serverSeed: string) {
    this.config = config;
    this.payoutTable = this.initializePayoutTable();
    this.rng = new ProveablyFairRNG(serverSeed);
  }

  /**
   * Initialize payout table (to be overridden by subclasses)
   */
  protected abstract initializePayoutTable(): PayoutTable;

  /**
   * Calculate win for a spin
   */
  protected abstract calculateWin(reels: number[][]): number;

  /**
   * Check for bonus trigger
   */
  protected abstract checkBonus(reels: number[][]): { triggered: boolean; type?: string; data?: any };

  /**
   * Execute spin
   */
  async spin(): Promise<SpinResult> {
    // Generate reel positions
    const reels: number[][] = [];
    for (let i = 0; i < this.config.reels; i++) {
      const reel: number[] = [];
      for (let j = 0; j < 3; j++) {
        reel.push(this.rng.generateRandom(this.config.symbols.length));
      }
      reels.push(reel);
    }

    // Calculate win
    const win = this.calculateWin(reels);

    // Check for bonus
    const bonus = this.checkBonus(reels);

    // Calculate multiplier
    let multiplier = 1;
    if (bonus.triggered && bonus.type === 'multiplier') {
      multiplier = bonus.data?.multiplier || 2;
    }

    return {
      reels,
      win: win * multiplier,
      multiplier,
      bonusTriggered: bonus.triggered,
      bonusType: bonus.type,
      freeSpins: bonus.data?.freeSpins,
      winLines: [],
    };
  }

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

  /**
   * Get payout table
   */
  getPayoutTable(): PayoutTable {
    return this.payoutTable;
  }
}

/**
 * Utility: Calculate cascading reels
 */
export function calculateCascadingReels(reels: number[][], matchedPositions: number[]): number[][] {
  const newReels = reels.map((reel) => [...reel]);

  // Remove matched symbols
  matchedPositions.forEach((pos) => {
    newReels[pos][1] = -1; // Mark as removed
  });

  // Drop symbols
  newReels.forEach((reel) => {
    const filtered = reel.filter((symbol) => symbol !== -1);
    while (filtered.length < 3) {
      filtered.unshift(Math.floor(Math.random() * 8));
    }
    reel.splice(0, reel.length, ...filtered);
  });

  return newReels;
}

/**
 * Utility: Check for matching symbols
 */
export function checkMatches(reels: number[][], symbolCount: number): { matched: boolean; positions: number[] } {
  const middle = reels.map((reel) => reel[1]); // Middle row
  const positions: number[] = [];

  for (let i = 0; i < middle.length - 2; i++) {
    if (middle[i] === middle[i + 1] && middle[i + 1] === middle[i + 2]) {
      positions.push(i, i + 1, i + 2);
    }
  }

  return {
    matched: positions.length > 0,
    positions: [...new Set(positions)],
  };
}

/**
 * Utility: Calculate payline wins
 */
export function calculatePaylineWins(reels: number[][], paylines: number[][], payoutTable: PayoutTable): WinLine[] {
  const wins: WinLine[] = [];

  paylines.forEach((payline, index) => {
    const symbols = payline.map((pos, reelIndex) => reels[reelIndex][pos]);
    const key = symbols.join('-');

    if (payoutTable[key]) {
      wins.push({
        payline: index,
        symbols,
        payout: payoutTable[key],
      });
    }
  });

  return wins;
}

/**
 * Utility: Generate standard paylines (5-reel, 3-row)
 */
export function generateStandardPaylines(): number[][] {
  return [
    [1, 1, 1, 1, 1], // Middle line
    [0, 0, 0, 0, 0], // Top line
    [2, 2, 2, 2, 2], // Bottom line
    [0, 1, 2, 1, 0], // V shape
    [2, 1, 0, 1, 2], // Inverted V
    [0, 0, 1, 2, 2], // Diagonal up
    [2, 2, 1, 0, 0], // Diagonal down
    [1, 0, 0, 0, 1], // W shape
    [1, 2, 2, 2, 1], // Inverted W
    [0, 1, 1, 1, 0], // Curved
  ];
}

/**
 * Utility: Calculate RTP from payout table
 */
export function calculateRTP(payoutTable: PayoutTable, totalCombinations: number): number {
  const totalPayout = Object.values(payoutTable).reduce((sum, payout) => sum + payout, 0);
  return (totalPayout / totalCombinations) * 100;
}

export default {
  ProveablyFairRNG,
  BaseSlotGame,
  calculateCascadingReels,
  checkMatches,
  calculatePaylineWins,
  generateStandardPaylines,
  calculateRTP,
};
