/**
 * @fileoverview Utility module for BaseGameScene.test.js
 * 
 * @module shared.__tests__.BaseGameScene.test
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { BaseGameScene } from "../BaseGameScene";
import { gameEventBus } from "../../utils/gameEvents";
import { GameInterpreter } from "../../interpreters/GameInterpreter";

// Mock dos módulos externos
vi.mock("../../utils/gameEvents", () => ({
  gameEventBus: {
    gameSuccess: vi.fn(),
    gameFailure: vi.fn(),
  },
}));

vi.mock("../../interpreters/GameInterpreter", () => ({
  GameInterpreter: class {
    constructor(config) {
      this.config = config;
      this.executeCode = vi.fn();
      this.stop = vi.fn();
    }
  },
}));

vi.mock("../gameController", () => ({
  setupGameController: vi.fn(),
}));

// Mock do Phaser
vi.mock("phaser", () => ({
  default: {
    Scene: class {
      constructor(key) {
        this.key = key;
        this.time = {
          delayedCall: vi.fn((delay, callback) => callback()),
        };
        this.sound = {
          get: vi.fn(),
          play: vi.fn(),
          stop: vi.fn(),
          stopByKey: vi.fn(),
          context: {
            state: "running",
            resume: vi.fn().mockResolvedValue(),
          },
        };
        this.registry = {
          values: {},
        };
      }
    },
  },
}));

describe("BaseGameScene", () => {
  let scene;

  beforeEach(() => {
    vi.clearAllMocks();
    scene = new BaseGameScene("test-scene");
  });

  describe("Constructor", () => {
    it("deve inicializar com valores padrão", () => {
      expect(scene.historico).toEqual([]);
      expect(scene.isRunning).toBe(false);
      expect(scene.workspace).toBeNull();
    });

    it("deve armazenar a chave da cena", () => {
      expect(scene.key).toBe("test-scene");
    });
  });

  describe("init()", () => {
    it("deve inicializar com configurações vazias", () => {
      scene.init({});

      expect(scene.configFase).toEqual({});
      expect(scene.gameConfig).toEqual({});
      expect(scene.historico).toEqual([]);
      expect(scene.isRunning).toBe(false);
    });

    it("deve armazenar configurações fornecidas", () => {
      const data = {
        configFase: { nivel: 1 },
        gameConfig: { pontos: 100 },
        customFailureHandler: function () {},
        stepDelay: 100,
      };

      scene.init(data);

      expect(scene.configFase).toEqual({ nivel: 1 });
      expect(scene.gameConfig).toEqual({ pontos: 100 });
      expect(scene.customFailureHandler).toBe(data.customFailureHandler);
    });

    it("deve usar dados do registry quando não passados diretamente", () => {
      scene.registry.values = {
        configFase: { nivel: 2 },
        gameConfig: { pontos: 200 },
        stepDelay: 75,
      };

      scene.init({});

      expect(scene.configFase).toEqual({ nivel: 2 });
      expect(scene.gameConfig).toEqual({ pontos: 200 });
      expect(scene.gameInterpreter.config.stepDelay).toBe(75);
    });

    it("deve priorizar dados passados direto sobre registry", () => {
      scene.registry.values = {
        configFase: { nivel: 2 },
        gameConfig: { pontos: 200 },
        stepDelay: 75,
      };

      const data = {
        configFase: { nivel: 1 },
        gameConfig: { pontos: 100 },
        stepDelay: 100,
      };

      scene.init(data);

      expect(scene.configFase).toEqual({ nivel: 1 });
      expect(scene.gameConfig).toEqual({ pontos: 100 });
      expect(scene.gameInterpreter.config.stepDelay).toBe(100);
    });

    it("deve criar uma instância de GameInterpreter", () => {
      scene.init({ stepDelay: 200 });

      expect(scene.gameInterpreter).toBeDefined();
      expect(scene.gameInterpreter.config).toEqual({
        stepDelay: 200,
        pauseExec: true,
      });
    });

    it("deve usar stepDelay padrão de 50", () => {
      scene.init({});

      expect(scene.gameInterpreter.config).toEqual({
        stepDelay: 50,
        pauseExec: true,
      });
    });

    it("deve chamar onInit se definido", () => {
      scene.onInit = function (data) {};
      scene.onInit = vi.fn(scene.onInit);
      const data = { configFase: {} };

      scene.init(data);

      expect(scene.onInit).toHaveBeenCalledWith(data);
    });
  });

  describe("aguardar()", () => {
    it("deve retornar uma Promise", () => {
      const result = scene.aguardar(1);
      expect(result).toBeInstanceOf(Promise);
    });

    it("deve chamar time.delayedCall com tempo correto", async () => {
      scene.aguardar(2);

      expect(scene.time.delayedCall).toHaveBeenCalledWith(
        2000,
        expect.any(Function),
      );
    });
  });

  describe("highlightBlock()", () => {
    it("deve chamar workspace.highlightBlock quando workspace existe", () => {
      scene.workspace = {
        highlightBlock: vi.fn(),
      };

      scene.highlightBlock("block-123");

      expect(scene.workspace.highlightBlock).toHaveBeenCalledWith("block-123");
    });

    it("não deve lançar erro quando workspace é null", () => {
      scene.workspace = null;

      expect(() => scene.highlightBlock("block-123")).not.toThrow();
    });
  });

  describe("playAudio()", () => {
    it("deve reproduzir áudio", () => {
      scene.playAudio("som-teste", { volume: 0.5 });

      expect(scene.sound.play).toHaveBeenCalledWith("som-teste", {
        volume: 0.5,
      });
    });

    it("deve aceitar config vazia", () => {
      scene.playAudio("som-teste");

      expect(scene.sound.play).toHaveBeenCalledWith("som-teste", {});
    });

    it("não deve lançar erro se falhar", () => {
      scene.sound.play.mockImplementation(() => {
        throw new Error("Audio error");
      });

      expect(() => scene.playAudio("som-teste")).not.toThrow();
    });
  });

  describe("stopAudio()", () => {
    it("deve chamar stopByKey", () => {
      scene.stopAudio("loop");
      expect(scene.sound.stopByKey).toHaveBeenCalledWith("loop");
    });

    it("não deve lançar erro se falhar", () => {
      scene.sound.stopByKey.mockImplementation(() => {
        throw new Error("Stop audio error");
      });

      expect(() => scene.stopAudio("ghost")).not.toThrow();
    });
  });

  describe("checkRegex()", () => {
    it("deve retornar true quando não há regex configurada", () => {
      scene.configFase = {};

      const result = scene.checkRegex("qualquer codigo");

      expect(result).toBe(true);
    });

    it("deve validar código com regex string", () => {
      scene.configFase = {
        validationRegex: "function\\s+\\w+",
      };

      expect(scene.checkRegex("function teste() {}")).toBe(true);
      expect(scene.checkRegex("const x = 5")).toBe(false);
    });

    it("deve validar código com objeto RegExp", () => {
      scene.configFase = {
        validationRegex: /function\s+\w+/,
      };

      expect(scene.checkRegex("function teste() {}")).toBe(true);
      expect(scene.checkRegex("const x = 5")).toBe(false);
    });

    it("deve retornar true em caso de regex inválida", () => {
      scene.configFase = {
        validationRegex: "[invalid regex(",
      };

      const result = scene.checkRegex("codigo qualquer");

      expect(result).toBe(true);
    });
  });

  describe("handleExecutionStart()", () => {
    it("deve resetar estado de execução", () => {
      const workspace = { highlightBlock: vi.fn() };
      scene.historico = ["item1", "item2"];
      scene.isRunning = false;

      scene.handleExecutionStart(workspace);

      expect(scene.workspace).toBe(workspace);
      expect(scene.historico).toEqual([]);
      expect(scene.isRunning).toBe(true);
    });

    it("deve limpar highlight", () => {
      const workspace = { highlightBlock: vi.fn() };

      scene.handleExecutionStart(workspace);

      expect(workspace.highlightBlock).toHaveBeenCalledWith(null);
    });
  });

  describe("BaseGameScene (Assets Globais)", () => {
    let scene;

    beforeEach(() => {
      vi.useFakeTimers();
      scene = new BaseGameScene("TestScene");

      // Mock do Loader do Phaser
      scene.load = {
        audio: vi.fn(),
      };

      // Mock do Sound Manager
      scene.sound = {
        get: vi.fn().mockReturnValue(true),
        play: vi.fn(),
        stop: vi.fn(),
      };

      scene.init({});
    });

    it("preloadGlobalAssets: deve carregar os audios padrão de win/fail", () => {
      scene.preloadGlobalAssets();

      // Verifica se carregou com as chaves reservadas
      expect(scene.load.audio).toHaveBeenCalledWith(
        "global_win",
        expect.stringContaining("win.mp3"),
      );
      expect(scene.load.audio).toHaveBeenCalledWith(
        "global_fail",
        expect.stringContaining("fail.mp3"),
      );
    });
  });

  describe("handleValidation()", () => {
    beforeEach(() => {
      scene.init({});
      scene.workspace = { highlightBlock: vi.fn() };
      scene.isRunning = true;
    });

    it("não deve validar se não está executando", () => {
      scene.isRunning = false;
      const validator = function () {};
      const validatorSpy = vi.fn(validator);

      scene.handleValidation(validatorSpy);

      expect(validatorSpy).not.toHaveBeenCalled();
    });

    it("deve chamar gameSuccess quando validação passa", async () => {
      scene.isRunning = true; // Necessário para handleValidation executar
      scene.onSuccess = function () {};
      scene.onSuccess = vi.fn(scene.onSuccess);
      const validator = function () {
        return { success: true };
      };
      const validatorSpy = vi.fn(validator);

      await scene.handleValidation(validatorSpy);

      expect(validatorSpy).toHaveBeenCalledWith(
        scene.historico,
        scene.configFase,
        scene.gameConfig,
      );
      expect(scene.onSuccess).toHaveBeenCalled();
      expect(gameEventBus.gameSuccess).toHaveBeenCalled();
      expect(scene.isRunning).toBe(false);
    });

    it("deve chamar handleFailure quando validação falha", () => {
      scene.handleFailure = function () {};
      scene.handleFailure = vi.fn(scene.handleFailure);
      const validator = function () {
        return {
          success: false,
          reason: "Teste falhou",
        };
      };
      const validatorSpy = vi.fn(validator);

      scene.handleValidation(validatorSpy);

      expect(scene.handleFailure).toHaveBeenCalledWith("Teste falhou");
    });

    it("deve limpar highlight após validação", () => {
      const validator = function () {
        return { success: true };
      };
      const validatorSpy = vi.fn(validator);

      scene.handleValidation(validatorSpy);

      expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(null);
    });

    it("deve tocar som global_win automaticamente no sucesso", () => {
      // Setup
      scene.init({});
      scene.isRunning = true;
      scene.playAudio = vi.fn(); // Espião no método playAudio

      const validator = () => ({ success: true });

      scene.handleValidation(validator);

      expect(scene.playAudio).toHaveBeenCalledWith("global_win");
      expect(gameEventBus.gameSuccess).toHaveBeenCalled();
    });

    it("deve tocar som global_fail automaticamente na falha", () => {
      // Setup
      scene.init({});
      scene.isRunning = true;
      scene.playAudio = vi.fn(); // Espião no método playAudio

      const validator = () => ({ success: false, reason: "Erro" });

      scene.handleValidation(validator);

      expect(scene.playAudio).toHaveBeenCalledWith("global_fail");
      // Pequeno avanço de tempo pois o failure tem delay
      vi.advanceTimersByTime(100);
      expect(gameEventBus.gameFailure).toHaveBeenCalled();
    });
  });

  describe("handleFailure()", () => {
    beforeEach(() => {
      scene.init({});
      scene.isRunning = true;
    });

    it("deve chamar onFailure se definido", () => {
      scene.onFailure = function () {};
      scene.onFailure = vi.fn(scene.onFailure);

      scene.handleFailure("Erro teste");

      expect(scene.onFailure).toHaveBeenCalled();
    });

    it("deve chamar customFailureHandler se definido", () => {
      const customHandler = function (reason) {};
      scene.init({ customFailureHandler: vi.fn(customHandler) });

      scene.handleFailure("Erro teste");

      expect(scene.customFailureHandler).toHaveBeenCalledWith("Erro teste");
    });

    it("deve chamar gameEventBus.gameFailure", () => {
      scene.handleFailure("Erro teste");

      expect(gameEventBus.gameFailure).toHaveBeenCalled();
    });

    it("deve definir isRunning como false", () => {
      scene.handleFailure("Erro teste");

      expect(scene.isRunning).toBe(false);
    });
  });

  describe("cleanupExecution()", () => {
    beforeEach(() => {
      scene.init({});
      scene.workspace = { highlightBlock: vi.fn() };
      scene.historico = ["item1", "item2"];
      scene.isRunning = true;
    });

    it("deve parar o interpretador", () => {
      scene.cleanupExecution();

      expect(scene.gameInterpreter.stop).toHaveBeenCalled();
    });

    it("deve limpar highlight", () => {
      scene.cleanupExecution();

      expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(null);
    });

    it("deve resetar estado", () => {
      scene.cleanupExecution();

      expect(scene.isRunning).toBe(false);
      expect(scene.historico).toEqual([]);
    });
  });

  describe("setupStandardController()", () => {
    let apiFactory;
    let validatorFunc;
    let setupGameController;

    beforeEach(async () => {
      const { setupGameController: mockSetup } =
        await import("../gameController");
      setupGameController = mockSetup;

      scene.init({
        configFase: { timeout: 5 },
      });

      apiFactory = function () {
        return {
          funcao1: vi.fn(),
          funcao2: vi.fn(),
        };
      };
      apiFactory = vi.fn(apiFactory);

      validatorFunc = function () {
        return { success: true };
      };
      validatorFunc = vi.fn(validatorFunc);

      scene.setupStandardController(apiFactory, validatorFunc);
    });

    it("deve chamar setupGameController", () => {
      expect(setupGameController).toHaveBeenCalledWith(
        scene,
        expect.objectContaining({
          onExecuteCode: expect.any(Function),
          onReset: expect.any(Function),
          onCheckSuccess: expect.any(Function),
        }),
      );
    });

    it("handler de reset deve chamar cleanupExecution", () => {
      scene.cleanupExecution = function () {};
      scene.cleanupExecution = vi.fn(scene.cleanupExecution);

      const callArgs = setupGameController.mock.calls[0][1];
      callArgs.onReset();

      expect(scene.cleanupExecution).toHaveBeenCalled();
    });

    it("handler de reset deve chamar onReset se definido", () => {
      scene.onReset = function () {};
      scene.onReset = vi.fn(scene.onReset);

      const callArgs = setupGameController.mock.calls[0][1];
      callArgs.onReset();

      expect(scene.onReset).toHaveBeenCalled();
    });
  });

  describe("setupStandardController - executeHandler", () => {
    let apiFactory;
    let validatorFunc;
    let executeHandler;

    beforeEach(async () => {
      const { setupGameController } = await import("../gameController");

      scene.init({
        configFase: {
          timeout: 5,
          delayValidacao: 500,
        },
      });

      apiFactory = function () {
        return {};
      };
      validatorFunc = function () {
        return { success: true };
      };

      scene.setupStandardController(apiFactory, validatorFunc);

      executeHandler = setupGameController.mock.calls[0][1].onExecuteCode;
    });

    it("deve executar código sem validação regex", async () => {
      const codigo = 'console.log("teste")';
      const workspace = { highlightBlock: vi.fn() };

      scene.gameInterpreter.executeCode.mockResolvedValue();

      await executeHandler(codigo, workspace);

      expect(scene.gameInterpreter.executeCode).toHaveBeenCalledWith(
        codigo,
        expect.any(Object),
      );
    });

    it("deve validar regex antes de executar", async () => {
      scene.configFase.validationRegex = /function/;
      scene.handleFailure = function () {};
      scene.handleFailure = vi.fn(scene.handleFailure);
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("const x = 5", workspace);

      expect(scene.handleFailure).toHaveBeenCalledWith(
        expect.stringContaining("estrutural"),
      );
      expect(scene.gameInterpreter.executeCode).not.toHaveBeenCalled();
    });

    it("deve chamar onBeforeRun se definido", async () => {
      scene.onBeforeRun = function () {
        return Promise.resolve();
      };
      scene.onBeforeRun = vi.fn(scene.onBeforeRun);
      scene.gameInterpreter.executeCode.mockResolvedValue();
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("codigo", workspace);

      expect(scene.onBeforeRun).toHaveBeenCalled();
    });

    it("deve tratar timeout de execução", async () => {
      scene.configFase.timeout = 0.001; // 1ms
      scene.handleFailure = function () {};
      scene.handleFailure = vi.fn(scene.handleFailure);
      scene.gameInterpreter.executeCode.mockImplementation(
        () => new Promise((resolve) => setTimeout(resolve, 1000)),
      );
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("codigo", workspace);

      expect(scene.handleFailure).toHaveBeenCalledWith(
        expect.stringContaining("Tempo limite"),
      );
    });

    it("deve usar timeout padrão de 15 segundos", async () => {
      delete scene.configFase.timeout;
      scene.gameInterpreter.executeCode.mockResolvedValue();
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("codigo", workspace);

      // Verifica que não deu timeout imediato
      expect(scene.gameInterpreter.executeCode).toHaveBeenCalled();
    });

    it("deve tratar erros inesperados", async () => {
      scene.handleFailure = function () {};
      scene.handleFailure = vi.fn(scene.handleFailure);
      scene.gameInterpreter.executeCode.mockRejectedValue(
        new Error("Erro customizado"),
      );
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("codigo", workspace);

      expect(scene.handleFailure).toHaveBeenCalledWith(
        expect.stringContaining("inesperado"),
      );
    });

    it("deve aguardar delay antes de validar", async () => {
      scene.configFase.delayValidacao = 1000;
      scene.gameInterpreter.executeCode.mockResolvedValue();
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("codigo", workspace);

      expect(scene.time.delayedCall).toHaveBeenCalledWith(
        1000,
        expect.any(Function),
      );
    });

    it("deve usar delay padrão de 1000ms", async () => {
      delete scene.configFase.delayValidacao;
      scene.gameInterpreter.executeCode.mockResolvedValue();
      const workspace = { highlightBlock: vi.fn() };

      await executeHandler("codigo", workspace);

      expect(scene.time.delayedCall).toHaveBeenCalledWith(
        1000,
        expect.any(Function),
      );
    });

    it("deve armazenar o código em currentCode", async () => {
      scene.gameInterpreter.executeCode.mockResolvedValue();
      const workspace = { highlightBlock: vi.fn() };
      const codigo = "var passos = 1;\nmover();";

      await executeHandler(codigo, workspace);

      expect(scene.currentCode).toBe(codigo);
    });
  });
});
