import { describe, expect, it, beforeEach, vi } from "vitest";
import { appRouter } from "./routers.ts";
import type { TrpcContext } from "./_core/context.ts";

// ─── Mock DB helpers ─────────────────────────────────────────────────────────
vi.mock("./db", async () => {
  const actual = await vi.importActual<typeof import("./db")>("./db");
  return {
    ...actual,
    getDb: vi.fn().mockResolvedValue(null),
    getWallet: vi.fn().mockResolvedValue({ gcBalance: "1000", scBalance: "10.00" }),
    creditWallet: vi.fn().mockResolvedValue(undefined),
    debitWallet: vi.fn().mockResolvedValue(undefined),
    getTransactionHistory: vi.fn().mockResolvedValue([]),
    getCoinPackages: vi.fn().mockResolvedValue([
      { id: 1, name: "Starter Pack", gcAmount: 10000, scBonusAmount: "1.00", priceUsd: "4.99", isActive: true, isPopular: false, isBestValue: false },
    ]),
    getDailyBonusStatus: vi.fn().mockResolvedValue({ canClaim: true, lastClaim: null }),
    getGames: vi.fn().mockResolvedValue([
      { id: 1, name: "Starburst", provider: "NetEnt", category: "slots", rtpPercent: 96.1, minBet: 100, maxBet: 50000, isActive: true, isFeatured: true, isNew: false, volatility: "medium", theme: "space", imageUrl: null, description: null },
    ]),
    getGameById: vi.fn().mockResolvedValue({ id: 1, name: "Starburst", provider: "NetEnt", category: "slots", rtpPercent: 96.1, minBet: 100, maxBet: 50000, isActive: true }),
    getSportEvents: vi.fn().mockResolvedValue([]),
    getPokerTables: vi.fn().mockResolvedValue([]),
    getBingoRooms: vi.fn().mockResolvedValue([]),
    writeAuditLog: vi.fn().mockResolvedValue(undefined),
    getAnalyticsSummary: vi.fn().mockResolvedValue({ totalUsers: 100, totalTransactions: 500, totalVolume: 9999.99, activeToday: 12, pendingKyc: 3, openAlerts: 1 }),
    getAllUsers: vi.fn().mockResolvedValue([]),
    getUserById: vi.fn().mockResolvedValue(null),
    updateUser: vi.fn().mockResolvedValue(undefined),
    getKycDocuments: vi.fn().mockResolvedValue([]),
    getTickets: vi.fn().mockResolvedValue([]),
    getTicketMessages: vi.fn().mockResolvedValue([]),
    getPlatformSetting: vi.fn().mockImplementation((key: string) => {
      if (key === "max_sc_win_per_spin") return Promise.resolve("20");
      if (key === "max_gc_win_per_spin") return Promise.resolve("0");
      return Promise.resolve(null);
    }),
    setPlatformSetting: vi.fn().mockResolvedValue(undefined),
    getAllPlatformSettings: vi.fn().mockResolvedValue([
      { settingKey: "max_sc_win_per_spin", settingValue: "20", description: "Max SC win per spin", updatedAt: new Date() },
      { settingKey: "max_gc_win_per_spin", settingValue: "0", description: "Max GC win per spin", updatedAt: new Date() },
    ]),
  };
});

// ─── Context factories ────────────────────────────────────────────────────────
function makeCtx(overrides: Partial<TrpcContext> = {}): TrpcContext {
  return {
    user: {
      id: 1,
      openId: "test-user",
      email: "test@playcoinkrazy.com",
      name: "Test User",
      loginMethod: "manus",
      role: "user",
      createdAt: new Date(),
      updatedAt: new Date(),
      lastSignedIn: new Date(),
    },
    req: { protocol: "https", headers: {} } as TrpcContext["req"],
    res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"],
    ...overrides,
  };
}

function makeAdminCtx(): TrpcContext {
  return makeCtx({ user: { ...makeCtx().user!, role: "admin" } });
}

function makeUnauthCtx(): TrpcContext {
  return makeCtx({ user: null });
}

// ─── Auth ─────────────────────────────────────────────────────────────────────
describe("auth", () => {
  it("me returns null for unauthenticated user", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    const result = await caller.auth.me();
    expect(result).toBeNull();
  });

  it("me returns user for authenticated user", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.auth.me();
    expect(result).not.toBeNull();
    expect(result?.email).toBe("test@playcoinkrazy.com");
  });

  it("logout clears session cookie", async () => {
    const ctx = makeCtx();
    const caller = appRouter.createCaller(ctx);
    const result = await caller.auth.logout();
    expect(result.success).toBe(true);
  });
});

// ─── Wallet ───────────────────────────────────────────────────────────────────
describe("wallet", () => {
  it("getBalance returns wallet for authenticated user", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.wallet.getBalance();
    expect(result).toBeDefined();
    expect(typeof result?.gcBalance).toBe("number");
  });

  it("getBalance throws UNAUTHORIZED for unauthenticated user", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.wallet.getBalance()).rejects.toThrow();
  });

  it("getTransactions returns array", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.wallet.getTransactions({ limit: 10 });
    expect(Array.isArray(result)).toBe(true);
  });

  it("getCoinPackages returns packages", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.wallet.getCoinPackages();
    expect(Array.isArray(result)).toBe(true);
    expect(result.length).toBeGreaterThan(0);
  });
});

// ─── Casino ───────────────────────────────────────────────────────────────────
describe("casino", () => {
  it("getGames returns game list", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getGames({ limit: 10 });
    expect(Array.isArray(result)).toBe(true);
  });

  it("getGame returns game by id", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getGame({ id: 1 });
    expect(result).toMatchObject({ id: 1, name: "Starburst" });
  });

  it("spin requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.casino.spin({ gameId: 1, betAmount: 100, currency: "GC" })).rejects.toThrow();
  });

  it("getGames accepts featured filter", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getGames({ limit: 20, featured: true });
    expect(Array.isArray(result)).toBe(true);
  });

  it("getGames works without featured filter", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getGames({ limit: 10, featured: false });
    expect(Array.isArray(result)).toBe(true);
  });
});

// ─── Sportsbook ───────────────────────────────────────────────────────────────
describe("sportsbook", () => {
  it("getEvents returns events list", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.sportsbook.getEvents({ limit: 20 });
    expect(Array.isArray(result)).toBe(true);
  });

  it("placeBet requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.sportsbook.placeBet({ eventId: 1, marketType: "moneyline", selection: "home", odds: 1.9, stakeAmount: 100, currency: "GC" })).rejects.toThrow();
  });
});

// ─── Poker ────────────────────────────────────────────────────────────────────
describe("poker", () => {
  it("getTables returns tables list", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.poker.getTables({ currency: "GC" });
    expect(Array.isArray(result)).toBe(true);
  });
});

// ─── Bingo ────────────────────────────────────────────────────────────────────
describe("bingo", () => {
  it("getRooms returns rooms list", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.bingo.getRooms();
    expect(Array.isArray(result)).toBe(true);
  });
});

// ─── Mini-Games ───────────────────────────────────────────────────────────────
describe("minigames", () => {
  it("playPlinko requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.minigames.playPlinko({ betAmount: 100, currency: "GC", rows: 8, risk: "medium" })).rejects.toThrow();
  });

  it("playDice requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.minigames.playDice({ betAmount: 100, currency: "GC", target: 50, isOver: true })).rejects.toThrow();
  });
});

// ─── KYC ─────────────────────────────────────────────────────────────────────
describe("kyc", () => {
  it("getStatus requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.kyc.getStatus()).rejects.toThrow();
  });

  it("getStatus returns kyc info for authenticated user", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.kyc.getStatus();
    expect(result).toBeDefined();
  });
});

// ─── Admin ────────────────────────────────────────────────────────────────────
describe("admin", () => {
  it("getDashboard throws FORBIDDEN for regular user", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getDashboard()).rejects.toThrow();
  });

  it("getDashboard returns analytics for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    const result = await caller.admin.getDashboard();
    expect(result).toMatchObject({ totalUsers: 100 });
  });

  it("getUsers returns users list for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    const result = await caller.admin.getUsers({ limit: 10 });
    expect(Array.isArray(result)).toBe(true);
  });

  it("getAuditLogs returns logs for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    const result = await caller.admin.getAuditLogs({ limit: 10 });
    expect(Array.isArray(result)).toBe(true);
  });
});

// ─── Staff ────────────────────────────────────────────────────────────────────
function makeStaffCtx(): TrpcContext {
  return makeCtx({ user: { ...makeCtx().user!, role: "staff" as any } });
}

describe("staff", () => {
  it("getTickets requires staff role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.staff.getTickets({ status: "open" })).rejects.toThrow();
  });

  it("getTickets returns tickets for staff user", async () => {
    const caller = appRouter.createCaller(makeStaffCtx());
    const result = await caller.staff.getTickets({ status: "open" });
    expect(Array.isArray(result)).toBe(true);
  });

  it("getUserTickets returns user's own tickets", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.staff.getUserTickets();
    expect(Array.isArray(result)).toBe(true);
  });
});

// ─── Favorites ───────────────────────────────────────────────────────────────
describe("favorites", () => {
  it("getIds requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.favorites.getIds()).rejects.toThrow();
  });

  it("list requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.favorites.list()).rejects.toThrow();
  });

  it("toggle requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.favorites.toggle({ gameId: 1 })).rejects.toThrow();
  });

  it("isFavorited requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.favorites.isFavorited({ gameId: 1 })).rejects.toThrow();
  });

  it("count requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.favorites.count()).rejects.toThrow();
  });
});

// ─── Promo Banners ───────────────────────────────────────────────────────────
describe("promoBanners", () => {
  it("getActive is public (no auth required)", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    // Should not throw UNAUTHORIZED - may throw DB error since getDb returns null
    try {
      await caller.promoBanners.getActive();
    } catch (e: any) {
      // DB unavailable is expected in test, but UNAUTHORIZED should not happen
      expect(e.code).not.toBe("UNAUTHORIZED");
    }
  });

  it("getAll requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.promoBanners.getAll()).rejects.toThrow();
  });

  it("create requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.promoBanners.create({ title: "Test Banner" })
    ).rejects.toThrow();
  });

  it("delete requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.promoBanners.delete({ id: 1 })
    ).rejects.toThrow();
  });

  it("reorder requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.promoBanners.reorder({ orderedIds: [1, 2, 3] })
    ).rejects.toThrow();
  });
});

// ─── Game Management / Analytics ─────────────────────────────────────────────
describe("gameManagement", () => {
  it("listGames requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.gameManagement.listGames()).rejects.toThrow();
  });

  it("getGameAnalytics requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.gameManagement.getGameAnalytics({ gameId: 1 })
    ).rejects.toThrow();
  });

  it("getAnalyticsDashboard requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.gameManagement.getAnalyticsDashboard()
    ).rejects.toThrow();
  });

  it("toggleGame requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.gameManagement.toggleGame({ gameId: 1, isActive: false })
    ).rejects.toThrow();
  });

  it("updateGameSettings requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.gameManagement.updateGameSettings({ gameId: 1, rtp: 95 })
    ).rejects.toThrow();
  });

  it("createCustomGame requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.gameManagement.createCustomGame({
        title: "Test Game",
        provider: "TestProvider",
        rtp: 96,
        volatility: "medium",
      })
    ).rejects.toThrow();
  });
});

// ─── Admin Coin Package Management ──────────────────────────────────────────
describe("admin coin packages", () => {
  it("getCoinPackages requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getCoinPackages()).rejects.toThrow();
  });

  it("getCoinPackages returns list for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    // getDb returns null, so should return empty array
    const result = await caller.admin.getCoinPackages();
    expect(Array.isArray(result)).toBe(true);
  });

  it("createCoinPackage requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.createCoinPackage({
        name: "Test Pack",
        gcAmount: 5000,
        scBonusAmount: 1,
        priceUsd: 4.99,
      })
    ).rejects.toThrow();
  });

  it("updateCoinPackage requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.updateCoinPackage({ id: 1, name: "Updated Pack" })
    ).rejects.toThrow();
  });

  it("deleteCoinPackage requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.deleteCoinPackage({ id: 1 })
    ).rejects.toThrow();
  });

  it("reorderCoinPackages requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.reorderCoinPackages({ orderedIds: [1, 2, 3] })
    ).rejects.toThrow();
  });
});

// ─── Admin Game Management (DB-backed CRUD) ─────────────────────────────────
describe("admin game management", () => {
  it("getAllGamesAdmin requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getAllGamesAdmin()).rejects.toThrow();
  });

  it("getAllGamesAdmin returns games for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    const result = await caller.admin.getAllGamesAdmin();
    expect(result).toMatchObject({ games: [], total: 0 });
  });

  it("createGame requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.createGame({
        title: "New Slot",
        provider: "TestProvider",
        category: "slots",
        rtp: 96,
        volatility: "medium",
      })
    ).rejects.toThrow();
  });

  it("updateGame requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.updateGame({ gameId: 1, title: "Updated" })
    ).rejects.toThrow();
  });

  it("deleteGame requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.deleteGame({ gameId: 1 })
    ).rejects.toThrow();
  });

  it("bulkToggleGames requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.bulkToggleGames({ gameIds: [1, 2, 3], isActive: false })
    ).rejects.toThrow();
  });

  it("toggleGame requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.toggleGame({ gameId: 1, isActive: false })
    ).rejects.toThrow();
  });
});

// ─── Payment Router ─────────────────────────────────────────────────────────
describe("payment", () => {
  it("createCheckoutSession requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(
      caller.payment.createCheckoutSession({ packageId: 1 })
    ).rejects.toThrow();
  });

  it("getPaymentHistory requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(
      caller.payment.getPaymentHistory({ limit: 20, offset: 0 })
    ).rejects.toThrow();
  });

  it("getPaymentHistory returns array for authenticated user", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.payment.getPaymentHistory({ limit: 20, offset: 0 });
    expect(Array.isArray(result)).toBe(true);
  });
});


// ─── Minigames Router ─────────────────────────────────────────────────────────
vi.mock("./db", async (importOriginal) => {
  const actual = await importOriginal<typeof import("./db")>();
  return {
    ...actual,
    getDb: vi.fn().mockResolvedValue(null),
    getWallet: vi.fn().mockResolvedValue({ gcBalance: "1000", scBalance: "10.00" }),
    getOrCreateWallet: vi.fn().mockResolvedValue({ gcBalance: "1000", scBalance: "10.00" }),
    creditWallet: vi.fn().mockResolvedValue(undefined),
    debitWallet: vi.fn().mockResolvedValue(undefined),
    getTransactionHistory: vi.fn().mockResolvedValue([]),
    getCoinPackages: vi.fn().mockResolvedValue([
      { id: 1, name: "Starter Pack", gcAmount: 10000, scBonusAmount: "1.00", priceUsd: "4.99", isActive: true, isPopular: false, isBestValue: false },
    ]),
    getDailyBonusStatus: vi.fn().mockResolvedValue({ canClaim: true, lastClaim: null }),
    getGames: vi.fn().mockResolvedValue([
      { id: 1, name: "Starburst", provider: "NetEnt", category: "slots", rtpPercent: 96.1, minBet: 100, maxBet: 50000, isActive: true, isFeatured: true, isNew: false, volatility: "medium", theme: "space", imageUrl: null, description: null },
    ]),
    getGameById: vi.fn().mockResolvedValue({ id: 1, name: "Starburst", provider: "NetEnt", category: "slots", rtpPercent: 96.1, minBet: 100, maxBet: 50000, isActive: true }),
    getSportEvents: vi.fn().mockResolvedValue([]),
    getUserBets: vi.fn().mockResolvedValue([]),
    getPokerTables: vi.fn().mockResolvedValue([]),
    getPokerTournaments: vi.fn().mockResolvedValue([]),
    getHandHistory: vi.fn().mockResolvedValue([]),
    getBingoRooms: vi.fn().mockResolvedValue([]),
    getUserBingoCards: vi.fn().mockResolvedValue([]),
    getMiniGameHistory: vi.fn().mockResolvedValue([
      { id: 1, gameType: "plinko", currency: "GC", betAmount: "100.00", winAmount: "210.00", multiplier: 2.1, createdAt: new Date() },
    ]),
    writeAuditLog: vi.fn().mockResolvedValue(undefined),
    getAnalyticsSummary: vi.fn().mockResolvedValue({ totalUsers: 100, totalTransactions: 500, totalVolume: 9999.99, activeToday: 12, pendingKyc: 3, openAlerts: 1 }),
    getAllUsers: vi.fn().mockResolvedValue([]),
    getUserById: vi.fn().mockResolvedValue(null),
    updateUser: vi.fn().mockResolvedValue(undefined),
    getKycDocuments: vi.fn().mockResolvedValue([]),
    getTickets: vi.fn().mockResolvedValue([]),
    getTicketMessages: vi.fn().mockResolvedValue([]),
    getPlatformSetting: vi.fn().mockImplementation((key: string) => {
      if (key === "max_sc_win_per_spin") return Promise.resolve("20");
      if (key === "max_gc_win_per_spin") return Promise.resolve("0");
      return Promise.resolve(null);
    }),
    setPlatformSetting: vi.fn().mockResolvedValue(undefined),
    getAllPlatformSettings: vi.fn().mockResolvedValue([
      { settingKey: "max_sc_win_per_spin", settingValue: "20", description: "Max SC win per spin", updatedAt: new Date() },
      { settingKey: "max_gc_win_per_spin", settingValue: "0", description: "Max GC win per spin", updatedAt: new Date() },
    ]),
  };
});

describe("minigames", () => {
  it("playPlinko requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(
      caller.minigames.playPlinko({ betAmount: 100, currency: "GC", rows: 8 })
    ).rejects.toThrow();
  });

  it("playPlinko returns result with multiplier and winAmount", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.playPlinko({ betAmount: 100, currency: "GC", rows: 8 });
    expect(result).toHaveProperty("multiplier");
    expect(result).toHaveProperty("winAmount");
    expect(result).toHaveProperty("serverSeed");
    expect(result).toHaveProperty("clientSeed");
    expect(typeof result.multiplier).toBe("number");
  });

  it("playDice returns roll result", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.playDice({ betAmount: 100, currency: "GC", target: 50, isOver: true });
    expect(result).toHaveProperty("roll");
    expect(result).toHaveProperty("win");
    expect(result).toHaveProperty("multiplier");
    expect(result.roll).toBeGreaterThanOrEqual(1);
    expect(result.roll).toBeLessThanOrEqual(100);
  });

  it("playCrash returns crash point", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.playCrash({ betAmount: 100, currency: "GC", autoCashOut: 2.0 });
    expect(result).toHaveProperty("crashAt");
    expect(result.crashAt).toBeGreaterThanOrEqual(1.0);
  });

  it("playWheel returns segment result", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.playWheel({ betAmount: 100, currency: "GC" });
    expect(result).toHaveProperty("multiplier");
    expect(result).toHaveProperty("segment");
    expect(result).toHaveProperty("color");
  });

  it("playMines reveal returns action result", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.playMines({ betAmount: 100, currency: "GC", mineCount: 3, revealedCount: 0, action: "reveal" });
    expect(result).toHaveProperty("action");
    expect(result.action).toBe("reveal");
    expect(result).toHaveProperty("isMine");
    expect(typeof result.isMine).toBe("boolean");
  });

  it("playMines cashout returns winAmount", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.playMines({ betAmount: 100, currency: "GC", mineCount: 3, revealedCount: 3, action: "cashout" });
    expect(result.action).toBe("cashout");
    expect(result).toHaveProperty("winAmount");
    expect(result).toHaveProperty("multiplier");
  });

  it("getHistory returns array for authenticated user", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.getHistory({ limit: 20 });
    expect(Array.isArray(result)).toBe(true);
  });

  it("verifyFairness returns verified result", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.minigames.verifyFairness({ serverSeed: "abc123", clientSeed: "def456", nonce: 1 });
    expect(result.verified).toBe(true);
    expect(result).toHaveProperty("hash");
    expect(result).toHaveProperty("rand");
  });
});

// ─── Sportsbook Router ─────────────────────────────────────────────────────────
describe("sportsbook", () => {
  it("getSports returns array", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.sportsbook.getSports();
    expect(Array.isArray(result)).toBe(true);
  });

  it("getEvents returns array", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.sportsbook.getEvents({});
    expect(Array.isArray(result)).toBe(true);
  });

  it("getMyBets requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.sportsbook.getMyBets({ limit: 20 })).rejects.toThrow();
  });
});

// ─── Poker Router ─────────────────────────────────────────────────────────────
describe("poker", () => {
  it("getTables returns array", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.poker.getTables({});
    expect(Array.isArray(result)).toBe(true);
  });

  it("getTournaments returns array", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.poker.getTournaments();
    expect(Array.isArray(result)).toBe(true);
  });

  it("joinTable requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.poker.joinTable({ tableId: 1, buyInAmount: 1000 })).rejects.toThrow();
  });
});

// ─── Bingo Router ─────────────────────────────────────────────────────────────
describe("bingo", () => {
  it("getRooms returns array", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.bingo.getRooms();
    expect(Array.isArray(result)).toBe(true);
  });

  it("getMyCards requires authentication", async () => {
    const caller = appRouter.createCaller(makeUnauthCtx());
    await expect(caller.bingo.getMyCards({ sessionId: 1 })).rejects.toThrow();
  });
});

// ─── Admin Make It Rain ─────────────────────────────────────────────────────
describe("admin make it rain", () => {
  it("makeItRain requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.makeItRain({ currency: "GC", amount: 1000, targetType: "all" })
    ).rejects.toThrow();
  });
});

// ─── Admin Payment Methods ──────────────────────────────────────────────────
describe("admin payment methods", () => {
  it("getPaymentMethods requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getPaymentMethods()).rejects.toThrow();
  });
});

// ─── Admin AI Employees ─────────────────────────────────────────────────────
describe("admin AI employees", () => {
  it("getAIEmployees requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getAIEmployees()).rejects.toThrow();
  });

  it("getAITasks requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getAITasks()).rejects.toThrow();
  });
});


// ─── Platform Settings ──────────────────────────────────────────────────────
describe("platform settings", () => {
  it("getPlatformSettings requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(caller.admin.getPlatformSettings()).rejects.toThrow();
  });

  it("getPlatformSettings returns settings for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    const result = await caller.admin.getPlatformSettings();
    expect(Array.isArray(result)).toBe(true);
    expect(result.length).toBe(2);
    expect(result[0].settingKey).toBe("max_sc_win_per_spin");
    expect(result[0].settingValue).toBe("20");
  });

  it("updatePlatformSetting requires admin role", async () => {
    const caller = appRouter.createCaller(makeCtx());
    await expect(
      caller.admin.updatePlatformSetting({ key: "max_sc_win_per_spin", value: "50" })
    ).rejects.toThrow();
  });

  it("updatePlatformSetting updates setting for admin", async () => {
    const caller = appRouter.createCaller(makeAdminCtx());
    const result = await caller.admin.updatePlatformSetting({ key: "max_sc_win_per_spin", value: "50" });
    expect(result.success).toBe(true);
  });
});


// ─── Branded Games Tests ────────────────────────────────────────────────────
describe("branded games", () => {
  it("getBrandedTheme returns null for non-branded game", async () => {
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getBrandedTheme({ gameId: 1 });
    // Game ID 1 is mocked as NetEnt, not PlayCoinKrazy
    expect(result).toBeNull();
  });

  it("getBrandedTheme returns null for non-existent game", async () => {
    const { getGameById } = await import("./db");
    (getGameById as any).mockResolvedValueOnce(null);
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getBrandedTheme({ gameId: 99999 });
    expect(result).toBeNull();
  });

  it("getBrandedTheme returns theme for PlayCoinKrazy game", async () => {
    const { getGameById } = await import("./db");
    (getGameById as any).mockResolvedValueOnce({
      id: 30001, title: "CoinKrazy Gold Rush", provider: "PlayCoinKrazy",
      slug: "pck-gold-rush", category: "slots", rtp: 96.5, volatility: "high",
      reels: 5, paylines: 25, isActive: true,
    });
    const caller = appRouter.createCaller(makeCtx());
    const result = await caller.casino.getBrandedTheme({ gameId: 30001 });
    expect(result).not.toBeNull();
    expect(result!.slug).toBe("pck-gold-rush");
    expect(result!.title).toBe("CoinKrazy Gold Rush");
    expect(result!.gradient).toBeDefined();
    expect(result!.accentColor).toBeDefined();
    expect(result!.themeEmoji).toBeDefined();
    expect(Array.isArray(result!.symbols)).toBe(true);
    expect(result!.symbols.length).toBeGreaterThan(0);
    expect(result!.symbols[0]).toHaveProperty("id");
    expect(result!.symbols[0]).toHaveProperty("name");
    expect(result!.symbols[0]).toHaveProperty("emoji");
    expect(result!.symbols[0]).toHaveProperty("type");
    expect(Array.isArray(result!.features)).toBeFalsy(); // features is an object, not array
    expect(result!.features).toHaveProperty("freeSpinsOnScatter");
    expect(Array.isArray(result!.tags)).toBe(true);
  });
});

describe("branded games config", () => {
  it("all 10 branded games have valid configs", async () => {
    const { BRANDED_GAMES, getBrandedGameBySlug } = await import("./brandedGames");
    expect(BRANDED_GAMES.length).toBe(10);
    for (const game of BRANDED_GAMES) {
      expect(game.slug).toMatch(/^pck-/);
      expect(game.title).toBeTruthy();
      expect(game.description).toBeTruthy();
      expect(game.gradient).toBeTruthy();
      expect(game.accentColor).toMatch(/^#/);
      expect(game.themeEmoji).toBeTruthy();
      expect(game.symbols.length).toBeGreaterThanOrEqual(8);
      expect(game.tags.length).toBeGreaterThan(0);
      // Verify lookup works
      const found = getBrandedGameBySlug(game.slug);
      expect(found).toBeDefined();
      expect(found!.slug).toBe(game.slug);
    }
  });

  it("getBrandedGameBySlug returns undefined for unknown slug", async () => {
    const { getBrandedGameBySlug } = await import("./brandedGames");
    const result = getBrandedGameBySlug("unknown-game");
    expect(result).toBeUndefined();
  });

  it("brandedToGameConfig creates valid game config", async () => {
    const { BRANDED_GAMES, brandedToGameConfig } = await import("./brandedGames");
    const game = BRANDED_GAMES[0]; // Gold Rush
    const config = brandedToGameConfig(game, 30001);
    expect(config.gameId).toBe(30001);
    expect(config.reels).toBe(5);
    expect(config.rows).toBe(3);
    expect(config.paylines).toBe(25);
    expect(config.rtp).toBeGreaterThan(0);
    expect(config.rtp).toBeLessThan(100);
    expect(config.volatility).toBeTruthy();
    expect(config.symbols.length).toBeGreaterThan(0);
    expect(config.symbols[0]).toHaveProperty("id");
    expect(config.symbols[0]).toHaveProperty("name");
    expect(config.symbols[0]).toHaveProperty("rarity");
    expect(config.symbols[0]).toHaveProperty("value");
  });
});
