import { protectedProcedure, router } from "../_core/trpc.ts";
import { z } from "zod";
import { debitWallet, creditWallet, writeAuditLog, getOrCreateWallet, getMiniGameHistory, getDb } from "../db.ts";
import { miniGameRounds } from "../../drizzle/schema.ts";
import crypto from "crypto";

function generateServerSeed(): string {
  return crypto.randomBytes(32).toString("hex");
}

function hashOutcome(serverSeed: string, clientSeed: string, nonce: number): number {
  const hash = crypto.createHmac("sha256", serverSeed).update(`${clientSeed}:${nonce}`).digest("hex");
  return parseInt(hash.slice(0, 8), 16) / 0xffffffff;
}

// Plinko: 16-row board with multipliers
const PLINKO_MULTIPLIERS: Record<number, number[]> = {
  8: [5.6, 2.1, 1.1, 1, 0.5, 1, 1.1, 2.1, 5.6],
  12: [8.9, 3, 1.4, 1.1, 1, 0.5, 1, 1.1, 1.4, 3, 8.9],
  16: [16, 9, 2, 1.4, 1.4, 1.2, 1.1, 1, 0.5, 1, 1.1, 1.2, 1.4, 1.4, 2, 9, 16],
};

function plinkoOutcome(rand: number, rows: number): { slot: number; multiplier: number; path: string[] } {
  const mults = PLINKO_MULTIPLIERS[rows] || PLINKO_MULTIPLIERS[16];
  const slot = Math.floor(rand * mults.length);
  const path = Array.from({ length: rows }, (_, i) => rand > 0.5 ? "R" : "L");
  return { slot, multiplier: mults[slot], path };
}

// Mines: grid with mines
function minesOutcome(rand: number, mineCount: number, gridSize: number, revealedCells: number): { isMine: boolean; multiplier: number } {
  const safeCells = gridSize - mineCount;
  const hitMine = rand < mineCount / (gridSize - revealedCells);
  // Calculate multiplier based on revealed safe cells
  let multiplier = 1;
  for (let i = 0; i < revealedCells; i++) {
    multiplier *= (gridSize - mineCount - i) / (gridSize - i);
  }
  multiplier = hitMine ? 0 : (1 / multiplier) * 0.97; // 3% house edge
  return { isMine: hitMine, multiplier };
}

// Dice: over/under
function diceOutcome(rand: number, target: number, isOver: boolean): { roll: number; win: boolean; multiplier: number } {
  const roll = Math.floor(rand * 100) + 1;
  const win = isOver ? roll > target : roll < target;
  const probability = isOver ? (100 - target) / 100 : target / 100;
  const multiplier = win ? (0.97 / probability) : 0;
  return { roll, win, multiplier };
}

// Crash: exponential growth with random crash point
function crashPoint(rand: number): number {
  if (rand < 0.01) return 1.0; // 1% instant crash
  return Math.max(1.0, 0.99 / (1 - rand));
}

// Wheel: 54-segment wheel
const WHEEL_SEGMENTS = [
  { multiplier: 0, color: "red", count: 26 },
  { multiplier: 2, color: "blue", count: 15 },
  { multiplier: 3, color: "green", count: 7 },
  { multiplier: 5, color: "yellow", count: 4 },
  { multiplier: 10, color: "purple", count: 1 },
  { multiplier: 20, color: "gold", count: 1 },
];

function wheelOutcome(rand: number): { segment: number; multiplier: number; color: string } {
  const totalSegments = WHEEL_SEGMENTS.reduce((a, s) => a + s.count, 0);
  let cumulative = 0;
  const roll = rand * totalSegments;
  for (const seg of WHEEL_SEGMENTS) {
    cumulative += seg.count;
    if (roll < cumulative) return { segment: Math.floor(roll), multiplier: seg.multiplier, color: seg.color };
  }
  return { segment: 0, multiplier: 0, color: "red" };
}

export const minigamesRouter = router({
  getHistory: protectedProcedure
    .input(z.object({ gameType: z.string().optional(), limit: z.number().default(20) }))
    .query(async ({ ctx, input }) => {
      return getMiniGameHistory(ctx.user.id, input.gameType, input.limit);
    }),

  playPlinko: protectedProcedure
    .input(z.object({
      betAmount: z.number().min(0.01),
      currency: z.enum(["GC", "SC"]),
      rows: z.number().int().min(8).max(16).default(16),
      clientSeed: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const wallet = await getOrCreateWallet(ctx.user.id);
      const balance = parseFloat(input.currency === "GC" ? wallet.gcBalance : wallet.scBalance);
      if (balance < input.betAmount) throw new Error("Insufficient balance");

      const serverSeed = generateServerSeed();
      const clientSeed = input.clientSeed || crypto.randomBytes(8).toString("hex");
      const rand = hashOutcome(serverSeed, clientSeed, 1);
      const result = plinkoOutcome(rand, input.rows);

      await debitWallet(ctx.user.id, input.currency, input.betAmount, "minigame_bet", "Plinko bet");
      const winAmount = input.betAmount * result.multiplier;
      if (winAmount > 0) await creditWallet(ctx.user.id, input.currency, winAmount, "minigame_win", `Plinko win x${result.multiplier}`);

      const db = await getDb();
      if (db) await db.insert(miniGameRounds).values({ userId: ctx.user.id, gameType: "plinko", currency: input.currency, betAmount: input.betAmount.toFixed(2), winAmount: winAmount.toFixed(2), multiplier: result.multiplier, serverSeed, clientSeed, nonce: 1, outcome: JSON.stringify(result) });

      return { ...result, betAmount: input.betAmount, winAmount, serverSeed, clientSeed };
    }),

  playDice: protectedProcedure
    .input(z.object({
      betAmount: z.number().min(0.01),
      currency: z.enum(["GC", "SC"]),
      target: z.number().int().min(2).max(98),
      isOver: z.boolean(),
      clientSeed: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const wallet = await getOrCreateWallet(ctx.user.id);
      const balance = parseFloat(input.currency === "GC" ? wallet.gcBalance : wallet.scBalance);
      if (balance < input.betAmount) throw new Error("Insufficient balance");

      const serverSeed = generateServerSeed();
      const clientSeed = input.clientSeed || crypto.randomBytes(8).toString("hex");
      const rand = hashOutcome(serverSeed, clientSeed, 1);
      const result = diceOutcome(rand, input.target, input.isOver);

      await debitWallet(ctx.user.id, input.currency, input.betAmount, "minigame_bet", "Dice bet");
      const winAmount = input.betAmount * result.multiplier;
      if (winAmount > 0) await creditWallet(ctx.user.id, input.currency, winAmount, "minigame_win", `Dice win x${result.multiplier.toFixed(2)}`);

      const db = await getDb();
      if (db) await db.insert(miniGameRounds).values({ userId: ctx.user.id, gameType: "dice", currency: input.currency, betAmount: input.betAmount.toFixed(2), winAmount: winAmount.toFixed(2), multiplier: result.multiplier, serverSeed, clientSeed, nonce: 1, outcome: JSON.stringify(result) });

      return { ...result, betAmount: input.betAmount, winAmount, serverSeed, clientSeed };
    }),

  playCrash: protectedProcedure
    .input(z.object({
      betAmount: z.number().min(0.01),
      currency: z.enum(["GC", "SC"]),
      autoCashOut: z.number().min(1.01).optional(),
      clientSeed: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const wallet = await getOrCreateWallet(ctx.user.id);
      const balance = parseFloat(input.currency === "GC" ? wallet.gcBalance : wallet.scBalance);
      if (balance < input.betAmount) throw new Error("Insufficient balance");

      const serverSeed = generateServerSeed();
      const clientSeed = input.clientSeed || crypto.randomBytes(8).toString("hex");
      const rand = hashOutcome(serverSeed, clientSeed, 1);
      const crashAt = crashPoint(rand);
      const cashedOutAt = input.autoCashOut && input.autoCashOut <= crashAt ? input.autoCashOut : null;
      const multiplier = cashedOutAt ?? 0;
      const winAmount = cashedOutAt ? input.betAmount * cashedOutAt : 0;

      await debitWallet(ctx.user.id, input.currency, input.betAmount, "minigame_bet", "Crash bet");
      if (winAmount > 0) await creditWallet(ctx.user.id, input.currency, winAmount, "minigame_win", `Crash win x${multiplier.toFixed(2)}`);

      const db = await getDb();
      if (db) await db.insert(miniGameRounds).values({ userId: ctx.user.id, gameType: "crash", currency: input.currency, betAmount: input.betAmount.toFixed(2), winAmount: winAmount.toFixed(2), multiplier, serverSeed, clientSeed, nonce: 1, outcome: JSON.stringify({ crashAt, cashedOutAt }) });

      return { crashAt, cashedOutAt, multiplier, betAmount: input.betAmount, winAmount, serverSeed, clientSeed };
    }),

  playWheel: protectedProcedure
    .input(z.object({
      betAmount: z.number().min(0.01),
      currency: z.enum(["GC", "SC"]),
      clientSeed: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const wallet = await getOrCreateWallet(ctx.user.id);
      const balance = parseFloat(input.currency === "GC" ? wallet.gcBalance : wallet.scBalance);
      if (balance < input.betAmount) throw new Error("Insufficient balance");

      const serverSeed = generateServerSeed();
      const clientSeed = input.clientSeed || crypto.randomBytes(8).toString("hex");
      const rand = hashOutcome(serverSeed, clientSeed, 1);
      const result = wheelOutcome(rand);
      const winAmount = input.betAmount * result.multiplier;

      await debitWallet(ctx.user.id, input.currency, input.betAmount, "minigame_bet", "Wheel bet");
      if (winAmount > 0) await creditWallet(ctx.user.id, input.currency, winAmount, "minigame_win", `Wheel win x${result.multiplier}`);

      const db = await getDb();
      if (db) await db.insert(miniGameRounds).values({ userId: ctx.user.id, gameType: "wheel", currency: input.currency, betAmount: input.betAmount.toFixed(2), winAmount: winAmount.toFixed(2), multiplier: result.multiplier, serverSeed, clientSeed, nonce: 1, outcome: JSON.stringify(result) });

      return { ...result, betAmount: input.betAmount, winAmount, serverSeed, clientSeed };
    }),

  playMines: protectedProcedure
    .input(z.object({
      betAmount: z.number().min(0.01),
      currency: z.enum(["GC", "SC"]),
      mineCount: z.number().int().min(1).max(24).default(3),
      revealedCount: z.number().int().min(0).default(0),
      action: z.enum(["reveal", "cashout"]),
      clientSeed: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const wallet = await getOrCreateWallet(ctx.user.id);
      const balance = parseFloat(input.currency === "GC" ? wallet.gcBalance : wallet.scBalance);

      const serverSeed = generateServerSeed();
      const clientSeed = input.clientSeed || crypto.randomBytes(8).toString("hex");
      const rand = hashOutcome(serverSeed, clientSeed, input.revealedCount + 1);
      const gridSize = 25;

      if (input.action === "cashout") {
        // Cashout: calculate multiplier based on revealed cells
        let multiplier = 1;
        for (let i = 0; i < input.revealedCount; i++) {
          multiplier *= (gridSize - input.mineCount - i) / (gridSize - i);
        }
        multiplier = input.revealedCount > 0 ? (1 / multiplier) * 0.97 : 0;
        const winAmount = input.betAmount * multiplier;
        if (winAmount > 0) await creditWallet(ctx.user.id, input.currency, winAmount, "minigame_win", `Mines cashout x${multiplier.toFixed(2)}`);

        const db = await getDb();
        if (db) await db.insert(miniGameRounds).values({ userId: ctx.user.id, gameType: "mines", currency: input.currency, betAmount: input.betAmount.toFixed(2), winAmount: winAmount.toFixed(2), multiplier, serverSeed, clientSeed, nonce: input.revealedCount, outcome: JSON.stringify({ action: "cashout", revealedCount: input.revealedCount }) });

        return { action: "cashout" as const, isMine: false, multiplier, winAmount, betAmount: input.betAmount, serverSeed, clientSeed };
      }

      // Reveal: check if mine
      if (input.revealedCount === 0) {
        // First reveal: debit the bet
        if (balance < input.betAmount) throw new Error("Insufficient balance");
        await debitWallet(ctx.user.id, input.currency, input.betAmount, "minigame_bet", "Mines bet");
      }

      const result = minesOutcome(rand, input.mineCount, gridSize, input.revealedCount);

      if (result.isMine) {
        // Hit mine - game over, no payout
        const db = await getDb();
        if (db) await db.insert(miniGameRounds).values({ userId: ctx.user.id, gameType: "mines", currency: input.currency, betAmount: input.betAmount.toFixed(2), winAmount: "0.00", multiplier: 0, serverSeed, clientSeed, nonce: input.revealedCount + 1, outcome: JSON.stringify({ action: "reveal", isMine: true, revealedCount: input.revealedCount }) });
        return { action: "reveal" as const, isMine: true, multiplier: 0, winAmount: 0, betAmount: input.betAmount, serverSeed, clientSeed };
      }

      return { action: "reveal" as const, isMine: false, multiplier: result.multiplier, winAmount: 0, betAmount: input.betAmount, serverSeed, clientSeed };
    }),

  verifyFairness: protectedProcedure
    .input(z.object({ serverSeed: z.string(), clientSeed: z.string(), nonce: z.number() }))
    .query(async ({ input }) => {
      const rand = hashOutcome(input.serverSeed, input.clientSeed, input.nonce);
      return { rand, verified: true, hash: crypto.createHmac("sha256", input.serverSeed).update(`${input.clientSeed}:${input.nonce}`).digest("hex") };
    }),
});
