import { describe, it, expect, beforeEach, vi } from 'vitest';
import { slotGamesRouter } from './slotGames.ts';
import { TRPCError } from '@trpc/server';

// Mock context
const mockUserContext = {
  user: {
    id: 1,
    role: 'user',
    email: 'player@example.com',
  },
};

describe('Slot Games Router', () => {
  describe('getGames', () => {
    it('should return list of available games', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);
      const games = await caller.getGames();

      expect(Array.isArray(games)).toBe(true);
      expect(games.length).toBeGreaterThan(0);

      // Check game structure
      games.forEach((game) => {
        expect(game).toHaveProperty('gameId');
        expect(game).toHaveProperty('name');
        expect(game).toHaveProperty('description');
        expect(game).toHaveProperty('minBet');
        expect(game).toHaveProperty('maxBet');
        expect(game).toHaveProperty('rtp');
        expect(game).toHaveProperty('volatility');
        expect(game).toHaveProperty('image');
      });
    });

    it('should have 5 games available', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);
      const games = await caller.getGames();

      expect(games.length).toBe(5);
    });

    it('should have correct game names', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);
      const games = await caller.getGames();

      const gameNames = games.map((g) => g.name);
      expect(gameNames).toContain('Classic 3-Reel');
      expect(gameNames).toContain('Treasure Quest');
      expect(gameNames).toContain('Cosmic Spins');
      expect(gameNames).toContain("Dragon's Gold");
      expect(gameNames).toContain('Lucky 7s');
    });
  });

  describe('getGameDetails', () => {
    it('should return game details for valid game ID', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);
      const details = await caller.getGameDetails({ gameId: 1 });

      expect(details).toHaveProperty('name');
      expect(details).toHaveProperty('description');
      expect(details).toHaveProperty('config');
      expect(details.config).toHaveProperty('reels');
      expect(details.config).toHaveProperty('rows');
      expect(details.config).toHaveProperty('paylines');
    });

    it('should throw error for invalid game ID', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      try {
        await caller.getGameDetails({ gameId: 999 });
        expect.fail('Should have thrown error');
      } catch (error) {
        expect(error).toBeInstanceOf(TRPCError);
        expect((error as TRPCError<any>).code).toBe('NOT_FOUND');
      }
    });

    it('should have correct RTP values', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const game1 = await caller.getGameDetails({ gameId: 1 });
      expect(game1.rtp).toBe(94.5);

      const game2 = await caller.getGameDetails({ gameId: 2 });
      expect(game2.rtp).toBe(95.2);

      const game5 = await caller.getGameDetails({ gameId: 5 });
      expect(game5.rtp).toBe(97.0);
    });
  });

  describe('spin', () => {
    it('should validate bet amount range', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      try {
        await caller.spin({
          gameId: 1,
          betAmount: 0.01, // Below minimum
        });
        expect.fail('Should have thrown error');
      } catch (error) {
        expect(error).toBeInstanceOf(TRPCError);
        expect((error as TRPCError<any>).code).toBe('BAD_REQUEST');
      }
    });

    it('should reject spin with insufficient balance', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      try {
        await caller.spin({
          gameId: 1,
          betAmount: 10000, // Unrealistic amount
        });
        expect.fail('Should have thrown error');
      } catch (error) {
        expect(error).toBeInstanceOf(TRPCError);
        expect((error as TRPCError<any>).code).toBe('BAD_REQUEST');
      }
    });

    it('should return valid spin result', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      try {
        const result = await caller.spin({
          gameId: 1,
          betAmount: 1,
        });

        expect(result).toHaveProperty('spinId');
        expect(result).toHaveProperty('gameId');
        expect(result).toHaveProperty('gameName');
        expect(result).toHaveProperty('reels');
        expect(result).toHaveProperty('betAmount');
        expect(result).toHaveProperty('winAmount');
        expect(result).toHaveProperty('outcome');
        expect(result).toHaveProperty('newBalance');
        expect(result).toHaveProperty('seed');
        expect(result).toHaveProperty('hash');

        // Validate reel structure
        expect(Array.isArray(result.reels)).toBe(true);
        expect(result.reels.length).toBeGreaterThan(0);
      } catch (error) {
        // Expected if wallet doesn't exist in test environment
        if ((error as TRPCError<any>).code === 'NOT_FOUND') {
          expect(true).toBe(true);
        } else {
          throw error;
        }
      }
    });

    it('should have valid outcome types', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const validOutcomes = [
        'no_win',
        'small_win',
        'medium_win',
        'big_win',
        'jackpot',
        'free_spins',
      ];

      try {
        const result = await caller.spin({
          gameId: 1,
          betAmount: 1,
        });

        expect(validOutcomes).toContain(result.outcome);
      } catch (error) {
        // Expected if wallet doesn't exist
        if ((error as TRPCError<any>).code === 'NOT_FOUND') {
          expect(true).toBe(true);
        }
      }
    });

    it('should accept optional server seed', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      try {
        const result = await caller.spin({
          gameId: 1,
          betAmount: 1,
          serverSeed: 'test_seed_123',
        });

        expect(result.seed).toBeDefined();
      } catch (error) {
        if ((error as TRPCError<any>).code === 'NOT_FOUND') {
          expect(true).toBe(true);
        }
      }
    });
  });

  describe('getGameStats', () => {
    it('should return game statistics', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const stats = await caller.getGameStats({ gameId: 1 });

      if (stats) {
        expect(stats).toHaveProperty('totalSpins');
        expect(stats).toHaveProperty('totalWagered');
        expect(stats).toHaveProperty('totalWon');
        expect(stats).toHaveProperty('largestWin');

        expect(typeof stats.totalSpins).toBe('number');
        expect(typeof stats.totalWagered).toBe('number');
        expect(typeof stats.totalWon).toBe('number');
        expect(typeof stats.largestWin).toBe('number');
      }
    });
  });

  describe('getLeaderboard', () => {
    it('should return leaderboard data', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const leaderboard = await caller.getLeaderboard({
        gameId: 1,
        limit: 10,
      });

      expect(Array.isArray(leaderboard)).toBe(true);

      leaderboard.forEach((entry) => {
        expect(entry).toHaveProperty('rank');
        expect(entry).toHaveProperty('username');
        expect(entry).toHaveProperty('winAmount');
        expect(entry).toHaveProperty('timestamp');
      });
    });

    it('should respect limit parameter', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const leaderboard = await caller.getLeaderboard({
        gameId: 1,
        limit: 5,
      });

      expect(leaderboard.length).toBeLessThanOrEqual(5);
    });
  });

  describe('verifySpin', () => {
    it('should verify spin with valid seeds', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const result = await caller.verifySpin({
        spinId: 'spin_123',
        serverSeed: 'server_seed_123',
        clientSeed: 'client_seed_456',
      });

      expect(result).toHaveProperty('spinId');
      expect(result).toHaveProperty('verified');
      expect(result).toHaveProperty('hash');
      expect(result.verified).toBe(true);
    });

    it('should generate consistent hash for same seeds', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const result1 = await caller.verifySpin({
        spinId: 'spin_123',
        serverSeed: 'server_seed_123',
        clientSeed: 'client_seed_456',
      });

      const result2 = await caller.verifySpin({
        spinId: 'spin_123',
        serverSeed: 'server_seed_123',
        clientSeed: 'client_seed_456',
      });

      expect(result1.hash).toBe(result2.hash);
    });
  });

  describe('getRecentSpins', () => {
    it('should return recent spins', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const spins = await caller.getRecentSpins({
        gameId: 1,
        limit: 20,
      });

      expect(Array.isArray(spins)).toBe(true);

      spins.forEach((spin) => {
        expect(spin).toHaveProperty('spinId');
        expect(spin).toHaveProperty('gameId');
        expect(spin).toHaveProperty('gameName');
        expect(spin).toHaveProperty('betAmount');
        expect(spin).toHaveProperty('winAmount');
        expect(spin).toHaveProperty('outcome');
        expect(spin).toHaveProperty('timestamp');
      });
    });

    it('should respect limit parameter', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const spins = await caller.getRecentSpins({
        limit: 5,
      });

      expect(spins.length).toBeLessThanOrEqual(5);
    });
  });

  describe('getPlayerStats', () => {
    it('should return daily stats', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const stats = await caller.getPlayerStats({ period: 'daily' });

      expect(stats).toHaveProperty('period');
      expect(stats.period).toBe('daily');
      expect(stats).toHaveProperty('totalSpins');
      expect(stats).toHaveProperty('totalWagered');
      expect(stats).toHaveProperty('totalWon');
      expect(stats).toHaveProperty('netResult');
      expect(stats).toHaveProperty('winRate');
      expect(stats).toHaveProperty('startDate');
      expect(stats).toHaveProperty('endDate');
    });

    it('should return weekly stats', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const stats = await caller.getPlayerStats({ period: 'weekly' });

      expect(stats.period).toBe('weekly');
    });

    it('should return monthly stats', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const stats = await caller.getPlayerStats({ period: 'monthly' });

      expect(stats.period).toBe('monthly');
    });

    it('should have valid stat values', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const stats = await caller.getPlayerStats({ period: 'daily' });

      expect(stats.totalSpins).toBeGreaterThanOrEqual(0);
      expect(stats.totalWagered).toBeGreaterThanOrEqual(0);
      expect(stats.totalWon).toBeGreaterThanOrEqual(0);
      expect(stats.winRate).toBeGreaterThanOrEqual(0);
      expect(stats.winRate).toBeLessThanOrEqual(1);
    });
  });

  describe('Game Balance', () => {
    it('should have RTP between 90-98%', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const games = await caller.getGames();

      games.forEach((game) => {
        expect(game.rtp).toBeGreaterThanOrEqual(90);
        expect(game.rtp).toBeLessThanOrEqual(98);
      });
    });

    it('should have valid bet ranges', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const games = await caller.getGames();

      games.forEach((game) => {
        expect(game.minBet).toBeGreaterThan(0);
        expect(game.maxBet).toBeGreaterThan(game.minBet);
      });
    });

    it('should have valid volatility levels', async () => {
      const caller = slotGamesRouter.createCaller(mockUserContext);

      const games = await caller.getGames();

      const validVolatilities = ['low', 'medium', 'high', 'very_high'];

      games.forEach((game) => {
        expect(validVolatilities).toContain(game.volatility);
      });
    });
  });
});
