import { describe, it, expect, vi, beforeEach } from "vitest";
import { AIGameBuilderService, type GameGenerationRequest } from "./aiGameBuilder";
import * as llm from "../_core/llm.ts";
import * as imageGen from "../_core/imageGeneration.ts";

// Mock the LLM and image generation modules
vi.mock("../_core/llm");
vi.mock("../_core/imageGeneration");
vi.mock("../storage", () => ({
  storagePut: vi.fn().mockResolvedValue({ url: "https://storage.example.com/game.png" }),
}));

describe("AIGameBuilderService", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("generateGame", () => {
    it("should generate a valid game configuration", async () => {
      const mockGameConfig = {
        name: "Golden Dragon Slots",
        description: "A mystical dragon-themed slot game",
        rules: ["Spin the reels", "Match symbols to win", "Trigger bonus features"],
        mechanics: {
          paylines: 25,
          reels: 5,
          symbols: ["dragon", "gold", "ruby", "emerald", "sapphire"],
          bonusFeatures: ["Free Spins", "Multiplier", "Wild Symbol"],
          multipliers: [2, 3, 5, 10],
        },
        rtp: 96.5,
        volatility: "medium" as const,
        maxWin: 50000,
        minBet: 0.01,
        maxBet: 100,
        theme: "Asian",
        difficulty: "medium" as const,
      };

      vi.mocked(llm.invokeLLM).mockResolvedValueOnce({
        choices: [
          {
            message: {
              content: JSON.stringify(mockGameConfig),
            },
          },
        ],
      } as any);

      const request: GameGenerationRequest = {
        gameType: "slot",
        gameName: "Golden Dragon Slots",
        theme: "Asian",
        difficulty: "medium",
        description: "A mystical dragon-themed slot game",
        rtp: 96.5,
        volatility: "medium",
        maxWin: 50000,
        minBet: 0.01,
        maxBet: 100,
      };

      const result = await AIGameBuilderService.generateGame(request);

      expect(result).toEqual(mockGameConfig);
      expect(result.name).toBe("Golden Dragon Slots");
      expect(result.rtp).toBe(96.5);
      expect(result.volatility).toBe("medium");
      expect(result.mechanics.paylines).toBe(25);
    });

    it("should handle different game types", async () => {
      const gameTypes = ["slot", "card", "dice", "puzzle", "roulette", "bingo", "scratch"] as const;

      for (const gameType of gameTypes) {
        const mockConfig = {
          name: `Test ${gameType} Game`,
          description: `A test ${gameType} game`,
          rules: ["Test rule 1", "Test rule 2"],
          mechanics: { symbols: ["test"] },
          rtp: 95,
          volatility: "low" as const,
          maxWin: 1000,
          minBet: 0.01,
          maxBet: 50,
          theme: "Test",
          difficulty: "easy" as const,
        };

        vi.mocked(llm.invokeLLM).mockResolvedValueOnce({
          choices: [{ message: { content: JSON.stringify(mockConfig) } }],
        } as any);

        const result = await AIGameBuilderService.generateGame({
          gameType,
          gameName: `Test ${gameType} Game`,
          theme: "Test",
          difficulty: "easy",
        });

        expect(result.name).toContain(gameType);
      }
    });

    it("should throw error on invalid LLM response", async () => {
      vi.mocked(llm.invokeLLM).mockResolvedValueOnce({
        choices: [{ message: { content: "invalid json" } }],
      } as any);

      const request: GameGenerationRequest = {
        gameType: "slot",
        gameName: "Test Game",
        theme: "Test",
        difficulty: "medium",
      };

      await expect(AIGameBuilderService.generateGame(request)).rejects.toThrow();
    });
  });

  describe("generateGameAssets", () => {
    it("should generate thumbnail and symbol images", async () => {
      const mockImageUrl = "https://example.com/image.png";

      vi.mocked(imageGen.generateImage).mockResolvedValue({
        url: mockImageUrl,
      } as any);

      const result = await AIGameBuilderService.generateGameAssets(
        "Golden Dragon",
        "Asian",
        "slot"
      );

      expect(result.thumbnailUrl).toBe(mockImageUrl);
      expect(result.symbolUrls).toHaveLength(5);
      expect(result.symbolUrls[0]).toBe(mockImageUrl);

      // Verify generateImage was called 6 times (1 thumbnail + 5 symbols)
      expect(vi.mocked(imageGen.generateImage)).toHaveBeenCalledTimes(6);
    });

    it("should generate assets for different game types", async () => {
      vi.mocked(imageGen.generateImage).mockResolvedValue({
        url: "https://example.com/image.png",
      } as any);

      const gameTypes = ["slot", "card", "dice", "puzzle", "roulette"];

      for (const gameType of gameTypes) {
        vi.mocked(imageGen.generateImage).mockClear();

        const result = await AIGameBuilderService.generateGameAssets(
          "Test Game",
          "Modern",
          gameType
        );

        expect(result.thumbnailUrl).toBeDefined();
        expect(result.symbolUrls.length).toBeGreaterThan(0);
      }
    });
  });

  describe("System prompt generation", () => {
    it("should build appropriate system prompt for game type", async () => {
      const request: GameGenerationRequest = {
        gameType: "slot",
        gameName: "Test Slot",
        theme: "Test",
        difficulty: "medium",
      };

      vi.mocked(llm.invokeLLM).mockResolvedValueOnce({
        choices: [
          {
            message: {
              content: JSON.stringify({
                name: "Test",
                description: "Test",
                rules: [],
                mechanics: {},
                rtp: 96,
                volatility: "medium",
                maxWin: 1000,
                minBet: 0.01,
                maxBet: 50,
                theme: "Test",
                difficulty: "medium",
              }),
            },
          },
        ],
      } as any);

      await AIGameBuilderService.generateGame(request);

      const callArgs = vi.mocked(llm.invokeLLM).mock.calls[0][0];
      const systemMessage = (callArgs.messages as any[])[0];

      expect(systemMessage.role).toBe("system");
      expect(systemMessage.content).toContain("slot");
      expect(systemMessage.content).toContain("RTP");
      expect(systemMessage.content).toContain("responsible gaming");
    });
  });

  describe("User prompt generation", () => {
    it("should include all game parameters in user prompt", async () => {
      const request: GameGenerationRequest = {
        gameType: "slot",
        gameName: "Dragon Gold",
        theme: "Asian",
        difficulty: "hard",
        description: "A challenging dragon game",
        rtp: 95,
        volatility: "high",
        maxWin: 100000,
        minBet: 0.5,
        maxBet: 500,
      };

      vi.mocked(llm.invokeLLM).mockResolvedValueOnce({
        choices: [
          {
            message: {
              content: JSON.stringify({
                name: "Test",
                description: "Test",
                rules: [],
                mechanics: {},
                rtp: 95,
                volatility: "high",
                maxWin: 100000,
                minBet: 0.5,
                maxBet: 500,
                theme: "Asian",
                difficulty: "hard",
              }),
            },
          },
        ],
      } as any);

      await AIGameBuilderService.generateGame(request);

      const callArgs = vi.mocked(llm.invokeLLM).mock.calls[0][0];
      const userMessage = (callArgs.messages as any[])[1];

      expect(userMessage.content).toContain("Dragon Gold");
      expect(userMessage.content).toContain("Asian");
      expect(userMessage.content).toContain("hard");
      expect(userMessage.content).toContain("95");
      expect(userMessage.content).toContain("high");
    });
  });

  describe("RTP validation", () => {
    it("should generate games with RTP between 92-98%", async () => {
      const rtpValues = [92, 94, 96, 98];

      for (const rtp of rtpValues) {
        const mockConfig = {
          name: "Test Game",
          description: "Test",
          rules: [],
          mechanics: {},
          rtp,
          volatility: "medium" as const,
          maxWin: 1000,
          minBet: 0.01,
          maxBet: 50,
          theme: "Test",
          difficulty: "medium" as const,
        };

        vi.mocked(llm.invokeLLM).mockResolvedValueOnce({
          choices: [{ message: { content: JSON.stringify(mockConfig) } }],
        } as any);

        const result = await AIGameBuilderService.generateGame({
          gameType: "slot",
          gameName: "Test",
          theme: "Test",
          difficulty: "medium",
          rtp,
        });

        expect(result.rtp).toBe(rtp);
        expect(result.rtp).toBeGreaterThanOrEqual(92);
        expect(result.rtp).toBeLessThanOrEqual(98);
      }
    });
  });
});
