import { expect } from "@esm-bundle/chai";
import sinon from "sinon";
// Import the singleton instance AND the class for constants
import {
  gameStateManager,
  GameStateManager,
} from "../../src/core/GameStateManager.js";

describe("Core: GameStateManager (Singleton)", () => {
  let mockPersistence;
  let mockGameLoop;

  beforeEach(() => {
    // 1. Reset Singleton State
    gameStateManager.reset();

    // 2. Mock Persistence
    mockPersistence = {
      init: sinon.stub().resolves(),
      saveRun: sinon.stub().resolves(),
      loadRun: sinon.stub().resolves(null),
      loadRoster: sinon.stub().resolves(null),
      saveRoster: sinon.stub().resolves(),
      loadCampaign: sinon.stub().resolves(null),
      saveCampaign: sinon.stub().resolves(),
      loadMarketState: sinon.stub().resolves(null),
      saveMarketState: sinon.stub().resolves(),
      loadHubStash: sinon.stub().resolves(null),
      saveHubStash: sinon.stub().resolves(),
      loadUnlocks: sinon.stub().resolves([]),
      saveUnlocks: sinon.stub().resolves(),
    };
    // Inject Mock (replacing the real Persistence instance)
    gameStateManager.persistence = mockPersistence;

    // 3. Mock GameLoop
    mockGameLoop = {
      init: sinon.spy(),
      startLevel: sinon.spy(),
      stop: sinon.spy(),
    };

    // 4. Mock MissionManager
    gameStateManager.missionManager = {
      setupActiveMission: sinon.stub(),
      getActiveMission: sinon.stub().returns({
        id: "MISSION_TUTORIAL_01",
        config: { title: "Test Mission" },
        biome: {
          generator_config: {
            seed_type: "RANDOM",
            seed: 12345,
          },
        },
        objectives: [],
      }),
      playIntro: sinon.stub().resolves(),
      load: sinon.stub(),
      save: sinon.stub().returns({ completedMissions: [] }),
      completedMissions: new Set(),
    };
  });

  it("CoA 1: Should initialize and transition to MAIN_MENU", async () => {
    const eventSpy = sinon.spy();
    window.addEventListener("gamestate-changed", eventSpy);

    await gameStateManager.init();

    expect(mockPersistence.init.calledOnce).to.be.true;
    expect(gameStateManager.currentState).to.equal(
      GameStateManager.STATES.MAIN_MENU
    );

    expect(eventSpy.called).to.be.true;
  });

  it("CoA 2: startNewGame should transition to TEAM_BUILDER", async () => {
    await gameStateManager.init();
    gameStateManager.startNewGame();

    expect(gameStateManager.currentState).to.equal(
      GameStateManager.STATES.TEAM_BUILDER
    );
  });

  it("Should load hub stash from IndexedDB on init", async () => {
    const hubStashData = {
      currency: { aetherShards: 500, ancientCores: 10 },
      items: [
        { uid: "ITEM_1", defId: "ITEM_RUSTY_BLADE", isNew: false, quantity: 1 },
      ],
    };
    mockPersistence.loadHubStash.resolves(hubStashData);

    await gameStateManager.init();

    expect(mockPersistence.loadHubStash.calledOnce).to.be.true;
    expect(gameStateManager.hubStash.currency.aetherShards).to.equal(500);
    expect(gameStateManager.hubStash.currency.ancientCores).to.equal(10);
    expect(gameStateManager.hubStash.getAllItems()).to.have.length(1);
  });

  it("Should save hub stash to IndexedDB", async () => {
    gameStateManager.hubStash.currency.aetherShards = 1000;
    gameStateManager.hubStash.addItem({
      uid: "ITEM_2",
      defId: "ITEM_SCRAP_PLATE",
      isNew: true,
      quantity: 1,
    });

    await gameStateManager._saveHubStash();

    expect(mockPersistence.saveHubStash.calledOnce).to.be.true;
    const savedData = mockPersistence.saveHubStash.firstCall.args[0];
    expect(savedData.currency.aetherShards).to.equal(1000);
    expect(savedData.items).to.have.length(1);
    expect(savedData.items[0].defId).to.equal("ITEM_SCRAP_PLATE");
  });

  it("CoA 3: handleEmbark should initialize run, save, and start engine", async () => {
    // Mock RosterManager.recruitUnit to return async unit with generated name
    const mockRecruitedUnit = {
      id: "UNIT_123",
      name: "Valerius", // Generated character name
      className: "Vanguard", // Class name
      classId: "CLASS_VANGUARD",
    };
    gameStateManager.rosterManager.recruitUnit = sinon.stub().resolves(mockRecruitedUnit);
    gameStateManager.setGameLoop(mockGameLoop);
    await gameStateManager.init();

    const mockSquad = [{ id: "u1", isNew: false }]; // Existing unit, not new

    // Mock startLevel to resolve immediately
    mockGameLoop.startLevel = sinon.stub().resolves();

    // Await the full async chain
    await gameStateManager.handleEmbark({ detail: { squad: mockSquad, mode: "SELECT" } });

    expect(gameStateManager.currentState).to.equal(
      GameStateManager.STATES.DEPLOYMENT
    );
    expect(mockPersistence.saveRun.calledWith(gameStateManager.activeRunData))
      .to.be.true;
    expect(mockGameLoop.startLevel.calledWith(gameStateManager.activeRunData))
      .to.be.true;
  });

  it("CoA 3b: handleEmbark should dispatch run-data-updated event", async () => {
    // Mock RosterManager.recruitUnit
    const mockRecruitedUnit = {
      id: "UNIT_123",
      name: "Valerius",
      className: "Vanguard",
      classId: "CLASS_VANGUARD",
    };
    gameStateManager.rosterManager.recruitUnit = sinon.stub().resolves(mockRecruitedUnit);
    gameStateManager.setGameLoop(mockGameLoop);
    await gameStateManager.init();

    const mockSquad = [{ id: "u1", isNew: true, name: "Vanguard", classId: "CLASS_VANGUARD" }];
    mockGameLoop.startLevel = sinon.stub().resolves();

    let eventDispatched = false;
    let eventData = null;
    window.addEventListener("run-data-updated", (e) => {
      eventDispatched = true;
      eventData = e.detail.runData;
    });

    await gameStateManager.handleEmbark({ detail: { squad: mockSquad, mode: "DRAFT" } });

    expect(eventDispatched).to.be.true;
    expect(eventData).to.exist;
    expect(eventData.squad).to.exist;
    expect(eventData.squad[0].name).to.equal("Valerius");
    expect(eventData.squad[0].className).to.equal("Vanguard");
  });

  it("CoA 4: continueGame should load save and resume engine", async () => {
    gameStateManager.setGameLoop(mockGameLoop);

    const savedData = { seed: 999, depth: 5, squad: [] };
    mockPersistence.loadRun.resolves(savedData);

    await gameStateManager.init();
    await gameStateManager.continueGame();

    expect(mockPersistence.loadRun.called).to.be.true;
    expect(gameStateManager.activeRunData).to.deep.equal(savedData);
    expect(mockGameLoop.startLevel.calledWith(savedData)).to.be.true;
  });

  it("CoA 5: init should load campaign data if available", async () => {
    const savedCampaignData = {
      completedMissions: ["MISSION_TUTORIAL_01"],
    };
    mockPersistence.loadCampaign.resolves(savedCampaignData);

    await gameStateManager.init();

    expect(mockPersistence.loadCampaign.called).to.be.true;
    expect(gameStateManager.missionManager.load.calledWith(savedCampaignData)).to.be.true;
  });

  it("CoA 6: init should handle missing campaign data gracefully", async () => {
    mockPersistence.loadCampaign.resolves(null);

    await gameStateManager.init();

    expect(mockPersistence.loadCampaign.called).to.be.true;
    expect(gameStateManager.missionManager.load.called).to.be.false;
  });

  it("CoA 7: campaign-data-changed event should trigger save", async () => {
    await gameStateManager.init();

    // Clear any previous calls
    mockPersistence.saveCampaign.resetHistory();

    // Dispatch campaign-data-changed event
    window.dispatchEvent(new CustomEvent("campaign-data-changed", {
      detail: { missionCompleted: "MISSION_TUTORIAL_01" }
    }));

    // Wait for async save (event listener is synchronous but save is async)
    await new Promise((resolve) => setTimeout(resolve, 50));

    expect(mockPersistence.saveCampaign.called).to.be.true;
    const savedData = mockPersistence.saveCampaign.firstCall.args[0];
    expect(savedData).to.exist;
    expect(savedData.completedMissions).to.be.an("array");
  });
});
