import { describe, it, expect, beforeEach } from "vitest";
import {
  SlotMachine,
  createGameConfig,
  ProgressiveJackpot,
  STANDARD_SYMBOLS,
  SymbolType,
  generatePaylines,
} from "./gameEngine.ts";

describe("Game Engine", () => {
  describe("Symbol System", () => {
    it("should have standard symbols defined", () => {
      expect(STANDARD_SYMBOLS.length).toBeGreaterThan(0);
      expect(STANDARD_SYMBOLS.some((s) => s.type === SymbolType.WILD)).toBe(true);
      expect(STANDARD_SYMBOLS.some((s) => s.type === SymbolType.SCATTER)).toBe(true);
    });

    it("should have valid symbol properties", () => {
      STANDARD_SYMBOLS.forEach((symbol) => {
        expect(symbol.id).toBeTruthy();
        expect(symbol.name).toBeTruthy();
        expect(symbol.emoji).toBeTruthy();
        expect(symbol.rarity).toBeGreaterThanOrEqual(0);
        expect(symbol.rarity).toBeLessThanOrEqual(1);
        expect(symbol.value).toBeGreaterThanOrEqual(0);
      });
    });
  });

  describe("Payline Generation", () => {
    it("should generate correct number of paylines", () => {
      const paylines = generatePaylines(5, 3);
      expect(paylines.length).toBeGreaterThan(0);
      expect(paylines.length).toBeLessThanOrEqual(25);
    });

    it("should generate valid paylines", () => {
      const paylines = generatePaylines(5, 3);
      paylines.forEach((payline) => {
        expect(payline.length).toBe(5); // 5 reels
        payline.forEach((row) => {
          expect(row).toBeGreaterThanOrEqual(0);
          expect(row).toBeLessThan(3); // 3 rows
        });
      });
    });
  });

  describe("Game Configuration", () => {
    it("should create valid game config", () => {
      const config = createGameConfig(1, 5, 3, 25, 96.0, "medium");
      expect(config.gameId).toBe(1);
      expect(config.reels).toBe(5);
      expect(config.rows).toBe(3);
      expect(config.paylines).toBe(25);
      expect(config.rtp).toBe(96.0);
      expect(config.symbols.length).toBeGreaterThan(0);
    });

    it("should have valid feature configuration", () => {
      const config = createGameConfig(1);
      expect(config.features.freeSpinsOnScatter).toBeDefined();
      expect(config.features.wildExpand).toBeDefined();
      expect(config.features.bonusGame).toBeDefined();
    });
  });

  describe("Slot Machine Spins", () => {
    let slotMachine: SlotMachine;

    beforeEach(() => {
      const config = createGameConfig(1, 5, 3, 25, 96.0, "medium");
      slotMachine = new SlotMachine(config);
    });

    it("should generate valid spin result", () => {
      const result = slotMachine.spin(100, "serverSeed", "clientSeed");
      expect(result.reels).toBeDefined();
      expect(result.reels.length).toBe(5);
      expect(result.reels[0].length).toBe(3);
      expect(result.winAmount).toBeGreaterThanOrEqual(0);
      expect(result.multiplier).toBeGreaterThanOrEqual(0);
      expect(result.outcome).toBeDefined();
      expect(result.seed).toBeTruthy();
      expect(result.hash).toBeTruthy();
    });

    it("should respect RTP distribution", () => {
      const lowRtpMachine = new SlotMachine(createGameConfig(1, 5, 3, 25, 85.0, "medium"));
      const highRtpMachine = new SlotMachine(createGameConfig(2, 5, 3, 25, 98.0, "medium"));

      let lowWins = 0;
      let highWins = 0;

      for (let i = 0; i < 100; i++) {
        const lowResult = lowRtpMachine.spin(100, `seed${i}`, `client${i}`);
        const highResult = highRtpMachine.spin(100, `seed${i}`, `client${i}`);

        if (lowResult.winAmount > 0) lowWins++;
        if (highResult.winAmount > 0) highWins++;
      }

      // High RTP should have more wins (statistically)
      expect(highWins).toBeGreaterThanOrEqual(lowWins - 15); // Allow some variance
    });

    it("should generate different results with different seeds", () => {
      const result1 = slotMachine.spin(100, "seed1", "client1");
      const result2 = slotMachine.spin(100, "seed2", "client2");
      expect(result1.seed).toBeTruthy();
      expect(result2.seed).toBeTruthy();
      expect(result1.hash).toBeTruthy();
      expect(result2.hash).toBeTruthy();
    });

    it("should generate same results with same seeds", () => {
      const result1 = slotMachine.spin(100, "seed", "client");
      const result2 = slotMachine.spin(100, "seed", "client");

      // Same seeds should produce same results (deterministic)
      expect(result1.seed).toBe(result2.seed);
      expect(result1.hash).toBe(result2.hash);
    });

    it("should contain valid symbols in reels", () => {
      const result = slotMachine.spin(100, "seed", "client");
      const validEmojis = STANDARD_SYMBOLS.map((s) => s.emoji);

      result.reels.forEach((reel) => {
        reel.forEach((symbol) => {
          expect(validEmojis).toContain(symbol);
        });
      });
    });

    it("should calculate win amount based on bet", () => {
      const bet100 = slotMachine.spin(100, "seed", "client");
      const bet200 = slotMachine.spin(200, "seed", "client");

      // If both have wins, the 200 bet should have proportionally higher win
      if (bet100.winAmount > 0 && bet200.winAmount > 0) {
        expect(bet200.winAmount).toBeGreaterThanOrEqual(bet100.winAmount);
      }
    });

    it("should detect free spins trigger", () => {
      let freeSpinFound = false;
      for (let i = 0; i < 500; i++) {
        const result = slotMachine.spin(100, `seed${i}`, `client${i}`);
        if (result.freeSpinsAwarded > 0) {
          freeSpinFound = true;
          expect(result.outcome).toBe("free_spins");
          break;
        }
      }
      // Free spins should trigger occasionally
      expect(typeof freeSpinFound).toBe("boolean");
    });

    it("should detect bonus game trigger", () => {
      let bonusFound = false;
      for (let i = 0; i < 200; i++) {
        const result = slotMachine.spin(100, `seed${i}`, `client${i}`);
        if (result.bonusTriggered) {
          bonusFound = true;
          break;
        }
      }
      // Bonus should trigger occasionally
      expect(bonusFound).toBe(true);
    });
  });

  describe("Progressive Jackpot", () => {
    let jackpot: ProgressiveJackpot;

    beforeEach(() => {
      jackpot = new ProgressiveJackpot(10000, 0.01);
    });

    it("should initialize with base amount", () => {
      expect(jackpot.getAmount()).toBe(10000);
    });

    it("should accumulate from bets", () => {
      jackpot.addBet(100);
      expect(jackpot.getAmount()).toBe(10001); // 10000 + (100 * 0.01)
    });

    it("should accumulate multiple bets", () => {
      jackpot.addBet(100);
      jackpot.addBet(200);
      jackpot.addBet(300);
      expect(jackpot.getAmount()).toBe(10000 + 6); // 1 + 2 + 3
    });

    it("should reset after win", () => {
      jackpot.addBet(1000);
      const winAmount = jackpot.winJackpot();
      expect(winAmount).toBe(10010);
      expect(jackpot.getAmount()).toBe(10000); // Reset to base
    });

    it("should reset manually", () => {
      jackpot.addBet(5000);
      jackpot.reset();
      expect(jackpot.getAmount()).toBe(10000);
    });
  });

  describe("Volatility Impact", () => {
    it("low volatility should have more frequent small wins", () => {
      const config = createGameConfig(1, 5, 3, 25, 96.0, "low");
      const machine = new SlotMachine(config);

      let totalResults = 0;

      for (let i = 0; i < 100; i++) {
        const result = machine.spin(100, `seed${i}`, `client${i}`);
        totalResults++;
        expect(result.reels).toBeDefined();
      }

      expect(totalResults).toBe(100);
    });

    it("high volatility should have less frequent but larger wins", () => {
      const config = createGameConfig(1, 5, 3, 25, 96.0, "high");
      const machine = new SlotMachine(config);

      let totalWins = 0;

      for (let i = 0; i < 100; i++) {
        const result = machine.spin(100, `seed${i}`, `client${i}`);
        if (result.winAmount > 0) totalWins++;
      }

      expect(totalWins).toBeGreaterThanOrEqual(0);
    });
  });

  describe("Outcome Distribution", () => {
    it("should have correct outcome distribution", () => {
      const config = createGameConfig(1, 5, 3, 25, 96.0, "medium");
      const machine = new SlotMachine(config);

      const outcomes: Record<string, number> = {
        no_win: 0,
        small_win: 0,
        medium_win: 0,
        big_win: 0,
        jackpot: 0,
        free_spins: 0,
      };

      for (let i = 0; i < 500; i++) {
        const result = machine.spin(100, `seed${i}`, `client${i}`);
        outcomes[result.outcome]++;
      }

      // No win should be most common
      expect(outcomes.no_win).toBeGreaterThan(outcomes.big_win);
      expect(outcomes.no_win).toBeGreaterThan(outcomes.jackpot);

      // Jackpot should be rare
      expect(outcomes.jackpot).toBeLessThan(10);
    });
  });

  describe("Edge Cases", () => {
    let slotMachine: SlotMachine;

    beforeEach(() => {
      const config = createGameConfig(1, 5, 3, 25, 96.0, "medium");
      slotMachine = new SlotMachine(config);
    });

    it("should handle zero bet", () => {
      const result = slotMachine.spin(0, "seed", "client");
      expect(result.winAmount).toBe(0);
      expect(result.multiplier).toBe(0);
    });

    it("should handle very large bet", () => {
      const result = slotMachine.spin(1000000, "seed", "client");
      expect(result.reels).toBeDefined();
      expect(result.reels.length).toBe(5);
    });

    it("should handle empty seeds", () => {
      const result = slotMachine.spin(100, "", "");
      expect(result.seed).toBeTruthy();
      expect(result.hash).toBeTruthy();
    });
  });
});
