import { expect } from "@esm-bundle/chai";
import sinon from "sinon";
import { GameLoop } from "../../../src/core/GameLoop.js";
import {
  createGameLoopSetup,
  cleanupGameLoop,
  createRunData,
  createMockGameStateManagerForDeployment,
  createMockMissionManager,
} from "./helpers.js";

describe("Core: GameLoop - Deployment", function () {
  this.timeout(30000);

  let gameLoop;
  let container;

  beforeEach(() => {
    const setup = createGameLoopSetup();
    gameLoop = setup.gameLoop;
    container = setup.container;
    gameLoop.init(container);
  });

  afterEach(() => {
    cleanupGameLoop(gameLoop, container);
  });

  it("CoA 3: Deployment Phase should separate zones and allow manual placement", async () => {
    const runData = createRunData({
      squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
    });

    // Mock gameStateManager for deployment phase
    const mockGameStateManager = createMockGameStateManagerForDeployment();
    if (!mockGameStateManager.rosterManager) {
      mockGameStateManager.rosterManager = { roster: [] };
    }
    gameLoop.gameStateManager = mockGameStateManager;

    // startLevel should now prepare the map but NOT spawn units immediately
    await gameLoop.startLevel(runData, { startAnimation: false });

    // 1. Verify Spawn Zones Generated
    // The generator/loop should identify valid tiles for player start and enemy start
    expect(gameLoop.playerSpawnZone).to.be.an("array").that.is.not.empty;
    expect(gameLoop.enemySpawnZone).to.be.an("array").that.is.not.empty;

    // 2. Verify Zone Separation
    // Create copies to ensure we don't test against mutated arrays later
    const pZone = [...gameLoop.playerSpawnZone];
    const eZone = [...gameLoop.enemySpawnZone];

    const overlap = pZone.some((pTile) =>
      eZone.some((eTile) => eTile.x === pTile.x && eTile.z === pTile.z)
    );
    expect(overlap).to.be.false;

    // 3. Test Manual Deployment (User Selection)
    const unitDef = runData.squad[0];
    const validTile = pZone[0]; // Pick first valid tile from player zone

    // Expect a method to manually place a unit from the roster onto a specific tile
    const unit = gameLoop.deployUnit(unitDef, validTile);

    expect(unit).to.exist;
    expect(unit.position.x).to.equal(validTile.x);
    expect(unit.position.z).to.equal(validTile.z);

    // Verify visual mesh created
    const mesh = gameLoop.unitMeshes.get(unit.id);
    expect(mesh).to.exist;
    expect(mesh.position.x).to.equal(validTile.x);

    // 4. Test Enemy Spawning (Finalize Deployment)
    // This triggers the actual start of combat/AI
    gameLoop.finalizeDeployment();

    const enemies = gameLoop.unitManager.getUnitsByTeam("ENEMY");
    expect(enemies.length).to.be.greaterThan(0);

    // Verify enemies are in their zone
    // Note: finalizeDeployment removes used spots from gameLoop.enemySpawnZone,
    // so we check against our copy `eZone`.
    const enemyPos = enemies[0].position;
    const isInZone = eZone.some(
      (t) => t.x === enemyPos.x && t.z === enemyPos.z
    );

    expect(
      isInZone,
      `Enemy spawned at ${enemyPos.x},${enemyPos.z} which is not in enemy zone`
    ).to.be.true;
  });

  it("CoA 5: finalizeDeployment should spawn enemies from mission enemy_spawns", async () => {
    const runData = createRunData({
      squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
    });

    // Mock gameStateManager for deployment phase
    const mockGameStateManager = createMockGameStateManagerForDeployment();
    if (!mockGameStateManager.rosterManager) {
      mockGameStateManager.rosterManager = { roster: [] };
    }
    gameLoop.gameStateManager = mockGameStateManager;

    // Mock MissionManager with enemy_spawns and required methods
    // Use ENEMY_DEFAULT which exists in the test environment
    const mockMissionManager = createMockMissionManager([
      { enemy_def_id: "ENEMY_DEFAULT", count: 2 },
    ]);
    mockMissionManager.setUnitManager = sinon.stub();
    mockMissionManager.setTurnSystem = sinon.stub();
    mockMissionManager.setupActiveMission = sinon.stub();
    gameLoop.missionManager = mockMissionManager;

    await gameLoop.startLevel(runData, { startAnimation: false });

    // Copy enemy spawn zone before finalizeDeployment modifies it
    const eZone = [...gameLoop.enemySpawnZone];

    // Finalize deployment should spawn enemies from mission definition
    gameLoop.finalizeDeployment();

    const enemies = gameLoop.unitManager.getUnitsByTeam("ENEMY");

    // Should have spawned 2 enemies (or as many as possible given spawn zone size)
    expect(enemies.length).to.be.greaterThan(0);
    expect(enemies.length).to.be.at.most(2);

    // Verify enemies are in their zone
    enemies.forEach((enemy) => {
      const enemyPos = enemy.position;
      const isInZone = eZone.some(
        (t) => t.x === enemyPos.x && t.z === enemyPos.z
      );
      expect(
        isInZone,
        `Enemy spawned at ${enemyPos.x},${enemyPos.z} which is not in enemy zone`
      ).to.be.true;
    });
  });

  it("CoA 6: finalizeDeployment should fall back to default if no enemy_spawns", async () => {
    const runData = createRunData({
      squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
    });

    // Mock gameStateManager for deployment phase
    const mockGameStateManager = createMockGameStateManagerForDeployment();
    if (!mockGameStateManager.rosterManager) {
      mockGameStateManager.rosterManager = { roster: [] };
    }
    gameLoop.gameStateManager = mockGameStateManager;

    // Mock MissionManager with no enemy_spawns and required methods
    const mockMissionManager = createMockMissionManager([]);
    mockMissionManager.setUnitManager = sinon.stub();
    mockMissionManager.setTurnSystem = sinon.stub();
    mockMissionManager.setupActiveMission = sinon.stub();
    gameLoop.missionManager = mockMissionManager;

    await gameLoop.startLevel(runData, { startAnimation: false });

    // Finalize deployment should fall back to default behavior
    const consoleWarnSpy = sinon.spy(console, "warn");
    gameLoop.finalizeDeployment();

    // Should have warned about missing enemy_spawns
    expect(consoleWarnSpy.calledWith(sinon.match(/No enemy_spawns defined/))).to
      .be.true;

    const enemies = gameLoop.unitManager.getUnitsByTeam("ENEMY");
    // Should still spawn at least one enemy (default behavior)
    expect(enemies.length).to.be.greaterThan(0);

    consoleWarnSpy.restore();
  });

  it("CoA 7: deployUnit should restore Explorer progression from roster", async () => {
    const runData = createRunData({
      squad: [{ id: "u1", classId: "CLASS_VANGUARD", name: "Test Explorer" }],
    });

    // Mock gameStateManager with roster containing progression data
    const mockGameStateManager = createMockGameStateManagerForDeployment();
    // Ensure rosterManager exists
    if (!mockGameStateManager.rosterManager) {
      mockGameStateManager.rosterManager = { roster: [] };
    }
    mockGameStateManager.rosterManager.roster = [
      {
        id: "u1",
        classId: "CLASS_VANGUARD",
        name: "Test Explorer",
        status: "READY",
        activeClassId: "CLASS_VANGUARD",
        classMastery: {
          CLASS_VANGUARD: {
            level: 5,
            xp: 250,
            skillPoints: 3,
            unlockedNodes: ["ROOT", "NODE_1"],
          },
        },
      },
    ];
    gameLoop.gameStateManager = mockGameStateManager;

    await gameLoop.startLevel(runData, { startAnimation: false });

    const unitDef = runData.squad[0];
    const validTile = gameLoop.playerSpawnZone[0];
    const unit = gameLoop.deployUnit(unitDef, validTile);

    expect(unit).to.exist;
    expect(unit.type).to.equal("EXPLORER");
    expect(unit.rosterId).to.equal("u1");

    // Verify progression was restored
    expect(unit.classMastery).to.exist;
    expect(unit.classMastery.CLASS_VANGUARD).to.exist;
    expect(unit.classMastery.CLASS_VANGUARD.level).to.equal(5);
    expect(unit.classMastery.CLASS_VANGUARD.xp).to.equal(250);
    expect(unit.classMastery.CLASS_VANGUARD.skillPoints).to.equal(3);
    expect(unit.classMastery.CLASS_VANGUARD.unlockedNodes).to.include("ROOT");
    expect(unit.classMastery.CLASS_VANGUARD.unlockedNodes).to.include("NODE_1");
    expect(unit.activeClassId).to.equal("CLASS_VANGUARD");

    // Verify stats were recalculated based on level
    // Level 5 means 4 level-ups, so health should be higher than base
    expect(unit.baseStats.health).to.be.greaterThan(100); // Base is 100
  });
});
