import { z } from "zod";
import { router, publicProcedure, protectedProcedure, adminProcedure } from "../_core/trpc.ts";
import { SlotGameEngine, recordSlotSpin, getSlotGameConfig, updateSlotGameRtp, toggleSlotGame, createSlotGame } from "../slotEngine.ts";
import { getDb } from "../db.ts";
import { casinoGames, gameSessions, users, wallets } from "../../drizzle/schema.ts";
import { eq, desc, sql } from "drizzle-orm";

export const slotsRouter = router({
  /**
   * Get all available slot games
   */
  getGames: publicProcedure.query(async () => {
    const db = await getDb();
    if (!db) throw new Error("Database unavailable");

    const games = await db
      .select()
      .from(casinoGames)
      .where(eq(casinoGames.category, "slots"))
      .limit(50);

    return games.map((game) => ({
      id: game.id,
      title: game.title,
      slug: game.slug,
      provider: game.provider,
      rtp: game.rtp,
      volatility: game.volatility,
      isActive: game.isActive,
      thumbnailUrl: game.thumbnailUrl,
    }));
  }),

  /**
   * Get single game configuration
   */
  getGame: publicProcedure.input(z.object({ gameId: z.number() })).query(async ({ input }) => {
    const config = await getSlotGameConfig(input.gameId);
    if (!config) throw new Error("Game not found");
    return config;
  }),

  /**
   * Spin the reels
   */
  spin: protectedProcedure
    .input(
      z.object({
        gameId: z.number(),
        betAmount: z.number().min(0.01).max(5.0),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const db = await getDb();
      if (!db) throw new Error("Database unavailable");

      // Get game config
      const config = await getSlotGameConfig(input.gameId);
      if (!config) throw new Error("Game not found");

      // Check user balance
      const wallet = await db.select().from(wallets).where(eq(wallets.userId, ctx.user.id)).limit(1);
      if (!wallet.length) throw new Error("Wallet not found");

      const userBalance = parseFloat(wallet[0].scBalance || "0");
      if (userBalance < input.betAmount) throw new Error("Insufficient balance");

      // Create engine and spin
      const engine = new SlotGameEngine(config);
      const spinResult = engine.spin(input.betAmount, config.rtp);

      // Deduct bet and add win to balance
      const newBalance = userBalance - input.betAmount + spinResult.winAmount;

      // Update user balance
      await db.update(wallets).set({ scBalance: newBalance.toString() }).where(eq(wallets.userId, ctx.user.id));

      // Record spin
      await recordSlotSpin(ctx.user.id, input.gameId, input.betAmount, spinResult.winAmount, spinResult);

      return {
        ...spinResult,
        newBalance,
      };
    }),

  /**
   * Get user's current wallet balance (Gold Coins and Sweeps Coins)
   */
  getBalance: protectedProcedure.query(async ({ ctx }) => {
    const db = await getDb();
    if (!db) throw new Error("Database unavailable");

    const wallet = await db.select().from(wallets).where(eq(wallets.userId, ctx.user.id)).limit(1);
    if (!wallet.length) throw new Error("Wallet not found");

    return {
      gcBalance: parseFloat(wallet[0].gcBalance || "0"),
      scBalance: parseFloat(wallet[0].scBalance || "0"),
    };
  }),

  /**
   * Get user's slot game statistics
   */
  getUserStats: protectedProcedure.query(async ({ ctx }) => {
    const db = await getDb();
    if (!db) throw new Error("Database unavailable");

    const sessions = await db
      .select()
      .from(gameSessions)
      .where(eq(gameSessions.userId, ctx.user.id))
      .limit(1000);

    const totalBet = sessions.reduce((sum, s) => sum + parseFloat(s.betAmount || "0"), 0);
    const totalWin = sessions.reduce((sum, s) => sum + parseFloat(s.winAmount || "0"), 0);
    const totalSpins = sessions.length;
    const winRate = totalSpins > 0 ? (sessions.filter((s) => parseFloat(s.winAmount || "0") > 0).length / totalSpins) * 100 : 0;

    return {
      totalSpins,
      totalBet,
      totalWin,
      netProfit: totalWin - totalBet,
      winRate: Math.round(winRate),
      avgBet: totalSpins > 0 ? totalBet / totalSpins : 0,
      avgWin: totalSpins > 0 ? totalWin / totalSpins : 0,
    };
  }),

  /**
   * Get slot leaderboard
   */
  getLeaderboard: publicProcedure
    .input(
      z.object({
        limit: z.number().min(1).max(100).default(10),
        offset: z.number().min(0).default(0),
      })
    )
    .query(async ({ input }) => {
      const db = await getDb();
      if (!db) throw new Error("Database unavailable");

      // Get top players by total wins
      const leaderboard = await db
        .select({
          userId: gameSessions.userId,
          username: users.username,
          totalWins: sql<number>`SUM(CAST(${gameSessions.winAmount} AS DECIMAL(10,2)))`,
          totalSpins: sql<number>`COUNT(*)`,
          winRate: sql<number>`(COUNT(CASE WHEN CAST(${gameSessions.winAmount} AS DECIMAL(10,2)) > 0 THEN 1 END) / COUNT(*)) * 100`,
        })
        .from(gameSessions)
        .innerJoin(users, eq(gameSessions.userId, users.id))
        .groupBy(gameSessions.userId)
        .orderBy(desc(sql<number>`SUM(CAST(${gameSessions.winAmount} AS DECIMAL(10,2)))`))
        .limit(input.limit)
        .offset(input.offset);

      return leaderboard.map((entry, index) => ({
        rank: input.offset + index + 1,
        userId: entry.userId,
        username: entry.username,
        totalWins: parseFloat(entry.totalWins?.toString() || "0"),
        totalSpins: entry.totalSpins,
        winRate: parseFloat(entry.winRate?.toString() || "0"),
      }));
    }),

  /**
   * Admin: Create new slot game
   */
  adminCreateGame: adminProcedure
    .input(
      z.object({
        name: z.string(),
        reels: z.number().min(3).max(6),
        rows: z.number().min(3).max(6),
        minBet: z.number().min(0.01),
        maxBet: z.number().max(5.0),
        maxWinCap: z.number().min(1).max(100),
        rtp: z.number().min(85).max(99),
        volatility: z.enum(["low", "medium", "high"]),
        paylines: z.array(
          z.object({
            id: z.number(),
            positions: z.array(z.number()),
            multiplier: z.number(),
          })
        ),
        symbols: z.array(
          z.object({
            id: z.string(),
            name: z.string(),
            multiplier: z.number(),
            isWild: z.boolean(),
            isScatter: z.boolean(),
            frequency: z.number(),
          })
        ),
      })
    )
    .mutation(async ({ input }) => {
      const game = await createSlotGame({
        name: input.name,
        reels: input.reels,
        rows: input.rows,
        minBet: input.minBet,
        maxBet: input.maxBet,
        maxWinCap: input.maxWinCap,
        rtp: input.rtp,
        volatility: input.volatility,
        paylines: input.paylines,
        symbols: input.symbols,
        bonusFeatures: [],
      });

      return game;
    }),

  /**
   * Admin: Update game (RTP and/or enabled status)
   */
  updateGame: adminProcedure
    .input(
      z.object({
        gameId: z.string(),
        rtp: z.number().min(85).max(99).optional(),
        enabled: z.boolean().optional(),
      })
    )
    .mutation(async ({ input }) => {
      const gameId = parseInt(input.gameId, 10);
      if (input.rtp) {
        await updateSlotGameRtp(gameId, input.rtp);
      }
      if (input.enabled !== undefined) {
        await toggleSlotGame(gameId, input.enabled);
      }
      return { success: true };
    }),

  /**
   * Admin: Update game RTP
   */
  adminUpdateRtp: adminProcedure
    .input(
      z.object({
        gameId: z.number(),
        newRtp: z.number().min(85).max(99),
      })
    )
    .mutation(async ({ input }) => {
      await updateSlotGameRtp(input.gameId, input.newRtp);
      return { success: true };
    }),

  /**
   * Admin: Toggle game active status
   */
  adminToggleGame: adminProcedure
    .input(
      z.object({
        gameId: z.number(),
        isActive: z.boolean(),
      })
    )
    .mutation(async ({ input }) => {
      await toggleSlotGame(input.gameId, input.isActive);
      return { success: true };
    }),

  /**
   * Admin: Get game analytics
   */
  adminGetAnalytics: adminProcedure
    .input(
      z.object({
        gameId: z.number(),
        days: z.number().min(1).max(365).default(30),
      })
    )
    .query(async ({ input }) => {
      const db = await getDb();
      if (!db) throw new Error("Database unavailable");

      const daysAgo = new Date(Date.now() - input.days * 24 * 60 * 60 * 1000);

      const sessions = await db
        .select()
        .from(gameSessions)
        .where(eq(gameSessions.gameId, input.gameId))
        .limit(10000);

      const totalBet = sessions.reduce((sum, s) => sum + parseFloat(s.betAmount || "0"), 0);
      const totalWin = sessions.reduce((sum, s) => sum + parseFloat(s.winAmount || "0"), 0);
      const totalSpins = sessions.length;
      const uniquePlayers = new Set(sessions.map((s) => s.userId)).size;

      return {
        totalSpins,
        totalBet,
        totalWin,
        houseProfit: totalBet - totalWin,
        uniquePlayers,
        avgBetPerSpin: totalSpins > 0 ? totalBet / totalSpins : 0,
        avgWinPerSpin: totalSpins > 0 ? totalWin / totalSpins : 0,
        rtp: totalBet > 0 ? (totalWin / totalBet) * 100 : 0,
      };
    }),
});
