/**
 * Slot Games Router - HTML5 Games Only
 * Handles only the new HTML5 games: Neon Nights, Dragon's Fury, Cosmic Quest, Retro Arcade
 */

import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { protectedProcedure, publicProcedure, router } from "../_core/trpc.ts";
import { debitWallet, creditWallet, getDb, writeAuditLog } from "../db.ts";
import { eq } from "drizzle-orm";
import { wallets, transactions } from "../../drizzle/schema.ts";
import crypto from "crypto";

// HTML5 Game IDs
const HTML5_GAMES = {
  NEON_NIGHTS: 'neon-nights',
  DRAGONS_FURY: 'dragons-fury',
  COSMIC_QUEST: 'cosmic-quest',
  RETRO_ARCADE: 'retro-arcade',
};

// Game configurations
const GAME_CONFIGS = {
  [HTML5_GAMES.NEON_NIGHTS]: {
    name: 'Neon Nights',
    description: 'Cascading reels with multipliers',
    rtp: 96.2,
    volatility: 'medium',
    minBet: 0.1,
    maxBet: 100,
    reels: 5,
    rows: 3,
    paylines: 25,
  },
  [HTML5_GAMES.DRAGONS_FURY]: {
    name: "Dragon's Fury",
    description: 'Hold & Spin mechanics with multipliers',
    rtp: 95.8,
    volatility: 'high',
    minBet: 0.1,
    maxBet: 100,
    reels: 5,
    rows: 3,
    paylines: 25,
  },
  [HTML5_GAMES.COSMIC_QUEST]: {
    name: 'Cosmic Quest',
    description: 'Megaways with 117,649 ways to win',
    rtp: 96.5,
    volatility: 'high',
    minBet: 0.1,
    maxBet: 100,
    reels: 6,
    rows: 7,
    paylines: 117649,
  },
  [HTML5_GAMES.RETRO_ARCADE]: {
    name: 'Retro Arcade',
    description: 'Classic 3-reel game',
    rtp: 94.2,
    volatility: 'low',
    minBet: 0.1,
    maxBet: 100,
    reels: 3,
    rows: 3,
    paylines: 9,
  },
};

/**
 * Generate cryptographically secure random number
 */
function generateSecureRandom(min: number, max: number): number {
  const range = max - min;
  const randomBytes = crypto.randomBytes(4);
  const randomValue = randomBytes.readUInt32BE(0);
  return min + (randomValue % range);
}

/**
 * Calculate spin outcome based on RTP
 */
function calculateOutcome(gameId: string, betAmount: number): {
  win: number;
  multiplier: number;
  isWin: boolean;
} {
  const config = GAME_CONFIGS[gameId as keyof typeof GAME_CONFIGS];
  if (!config) throw new Error('Invalid game ID');

  const rtp = config.rtp / 100;
  const random = Math.random();

  // Determine if this spin wins
  const isWin = random < rtp;

  if (!isWin) {
    return { win: 0, multiplier: 0, isWin: false };
  }

  // Calculate win amount based on volatility
  let multiplier = 1;
  const volatilityRandom = Math.random();

  if (config.volatility === 'low') {
    multiplier = volatilityRandom < 0.7 ? 1 : volatilityRandom < 0.95 ? 2 : 5;
  } else if (config.volatility === 'medium') {
    multiplier = volatilityRandom < 0.5 ? 1 : volatilityRandom < 0.8 ? 2 : volatilityRandom < 0.95 ? 5 : 10;
  } else {
    // high volatility
    multiplier = volatilityRandom < 0.3 ? 1 : volatilityRandom < 0.6 ? 2 : volatilityRandom < 0.85 ? 5 : volatilityRandom < 0.98 ? 10 : 50;
  }

  const win = betAmount * multiplier;

  return { win, multiplier, isWin: true };
}

export const slotGamesRouter = router({
  /**
   * Get all available HTML5 games
   */
  getGames: publicProcedure.query(async () => {
    return Object.entries(GAME_CONFIGS).map(([id, config]) => ({
      id,
      ...config,
      emoji: {
        [HTML5_GAMES.NEON_NIGHTS]: '🌃',
        [HTML5_GAMES.DRAGONS_FURY]: '🐉',
        [HTML5_GAMES.COSMIC_QUEST]: '🚀',
        [HTML5_GAMES.RETRO_ARCADE]: '🕹️',
      }[id as keyof typeof HTML5_GAMES] || '🎰',
    }));
  }),

  /**
   * Get specific game details
   */
  getGame: publicProcedure
    .input(z.object({ gameId: z.string() }))
    .query(async ({ input }) => {
      const config = GAME_CONFIGS[input.gameId as keyof typeof GAME_CONFIGS];
      if (!config) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Game not found',
        });
      }

      return {
        id: input.gameId,
        ...config,
      };
    }),

  /**
   * Spin the reels
   */
  spin: protectedProcedure
    .input(
      z.object({
        gameId: z.string(),
        betAmount: z.number().positive(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const config = GAME_CONFIGS[input.gameId as keyof typeof GAME_CONFIGS];
      if (!config) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Game not found',
        });
      }

      // Validate bet amount
      if (input.betAmount < config.minBet || input.betAmount > config.maxBet) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `Bet must be between ${config.minBet} and ${config.maxBet}`,
        });
      }

      // Debit wallet
      const debitResult = await debitWallet(ctx.user.id, input.betAmount, 'spin', input.gameId);
      if (!debitResult.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: debitResult.message || 'Insufficient balance',
        });
      }

      // Calculate outcome
      const outcome = calculateOutcome(input.gameId, input.betAmount);

      // Credit wallet if win
      let creditResult = { success: true };
      if (outcome.isWin) {
        creditResult = await creditWallet(ctx.user.id, outcome.win, 'win', input.gameId);
      }

      // Record transaction
      const db = getDb();
      const transactionId = crypto.randomUUID();
      await db.insert(transactions).values({
        id: transactionId,
        userId: ctx.user.id,
        type: 'spin',
        amount: input.betAmount,
        winAmount: outcome.win,
        gameId: input.gameId,
        status: 'completed',
        metadata: {
          multiplier: outcome.multiplier,
          volatility: config.volatility,
        },
      });

      // Audit log
      await writeAuditLog(ctx.user.id, 'spin', {
        gameId: input.gameId,
        betAmount: input.betAmount,
        winAmount: outcome.win,
        multiplier: outcome.multiplier,
      });

      return {
        success: true,
        transactionId,
        betAmount: input.betAmount,
        winAmount: outcome.win,
        multiplier: outcome.multiplier,
        isWin: outcome.isWin,
        newBalance: debitResult.newBalance,
      };
    }),

  /**
   * Get player game statistics
   */
  getPlayerStats: protectedProcedure
    .input(z.object({ gameId: z.string() }))
    .query(async ({ ctx, input }) => {
      const db = getDb();

      const playerTransactions = await db.query.transactions.findMany({
        where: (t) => t.userId === ctx.user.id && t.gameId === input.gameId,
      });

      const totalSpins = playerTransactions.length;
      const totalWagered = playerTransactions.reduce((sum, t) => sum + (t.amount || 0), 0);
      const totalWon = playerTransactions.reduce((sum, t) => sum + (t.winAmount || 0), 0);
      const winCount = playerTransactions.filter((t) => t.winAmount && t.winAmount > 0).length;

      return {
        gameId: input.gameId,
        totalSpins,
        totalWagered,
        totalWon,
        winCount,
        winRate: totalSpins > 0 ? (winCount / totalSpins) * 100 : 0,
        averageWin: winCount > 0 ? totalWon / winCount : 0,
        biggestWin: Math.max(...playerTransactions.map((t) => t.winAmount || 0), 0),
      };
    }),

  /**
   * Get global game statistics
   */
  getGameStats: publicProcedure
    .input(z.object({ gameId: z.string() }))
    .query(async ({ input }) => {
      const db = getDb();

      const gameTransactions = await db.query.transactions.findMany({
        where: (t) => t.gameId === input.gameId,
      });

      const totalSpins = gameTransactions.length;
      const totalWagered = gameTransactions.reduce((sum, t) => sum + (t.amount || 0), 0);
      const totalWon = gameTransactions.reduce((sum, t) => sum + (t.winAmount || 0), 0);
      const winCount = gameTransactions.filter((t) => t.winAmount && t.winAmount > 0).length;

      return {
        gameId: input.gameId,
        totalSpins,
        totalWagered,
        totalWon,
        winCount,
        winRate: totalSpins > 0 ? (winCount / totalSpins) * 100 : 0,
        averageWin: winCount > 0 ? totalWon / winCount : 0,
        biggestWin: Math.max(...gameTransactions.map((t) => t.winAmount || 0), 0),
        playerCount: new Set(gameTransactions.map((t) => t.userId)).size,
      };
    }),
});
