import { expect } from "@esm-bundle/chai";
import sinon from "sinon";
import * as THREE from "three";
import { GameLoop } from "../../../src/core/GameLoop.js";
import { skillRegistry } from "../../../src/managers/SkillRegistry.js";
import {
  createGameLoopSetup,
  cleanupGameLoop,
  createRunData,
  createMockGameStateManagerForCombat,
  setupCombatUnits,
  cleanupTurnSystem,
} from "./helpers.js";

describe("Core: GameLoop - Combat Skill Targeting and Execution", function () {
  this.timeout(30000);

  let gameLoop;
  let container;
  let mockGameStateManager;
  let playerUnit;
  let enemyUnit;

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

    gameLoop.stop();
    if (
      gameLoop.turnSystem &&
      typeof gameLoop.turnSystem.reset === "function"
    ) {
      gameLoop.turnSystem.reset();
    }

    gameLoop.init(container);
    mockGameStateManager = createMockGameStateManagerForCombat();
    gameLoop.gameStateManager = mockGameStateManager;

    const runData = createRunData({
      squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
    });
    await gameLoop.startLevel(runData, { startAnimation: false });

    const units = setupCombatUnits(gameLoop);
    playerUnit = units.playerUnit;
    enemyUnit = units.enemyUnit;

    // Start combat and set player unit as active
    if (gameLoop.turnSystem) {
      gameLoop.turnSystem.startCombat([playerUnit, enemyUnit]);
      // Manually set player unit as active for testing
      gameLoop.turnSystem.activeUnitId = playerUnit.id;
    }
  });

  afterEach(() => {
    gameLoop.clearMovementHighlights();
    gameLoop.clearSpawnZoneHighlights();
    cleanupTurnSystem(gameLoop);
    cleanupGameLoop(gameLoop, container);
  });

  describe("Skill Targeting Validation", () => {
    it("should only highlight valid targets when entering targeting mode", async () => {
      // Add a skill to the player unit
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      // Add skill to unit actions
      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      // Enter targeting mode
      gameLoop.onSkillClicked(skillId);

      // Verify we're in targeting mode
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
      expect(gameLoop.activeSkillId).to.equal(skillId);

      // Check that highlights were created (valid targets only)
      if (gameLoop.voxelManager && gameLoop.voxelManager.rangeHighlights) {
        const highlightCount = gameLoop.voxelManager.rangeHighlights.size;
        // Should have highlights for valid empty tiles within range
        expect(highlightCount).to.be.greaterThan(0);
      }
    });

    it("should not highlight invalid targets (out of range)", async () => {
      const skillId = "SKILL_SHORT_RANGE";
      const skillDef = {
        id: skillId,
        name: "Short Range",
        costs: { ap: 1 },
        targeting: {
          range: 2, // Very short range
          type: "ENEMY",
          line_of_sight: true,
        },
        effects: [],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      // Add skill to unit actions
      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Short Range",
        costAP: 1,
        cooldown: 0,
      });

      // Place enemy far away
      const farAwayPos = {
        x: playerUnit.position.x + 10,
        y: playerUnit.position.y,
        z: playerUnit.position.z + 10,
      };
      gameLoop.grid.moveUnit(enemyUnit, farAwayPos, { force: true });

      // Enter targeting mode
      gameLoop.onSkillClicked(skillId);

      // Verify we're in targeting mode
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");

      // The far away enemy should not be highlighted
      // (validation happens when checking individual tiles)
      expect(gameLoop.activeSkillId).to.equal(skillId);
    });
  });

  describe("Movement Button Toggle", () => {
    it("should return to movement mode when movement button is clicked", () => {
      // First enter skill targeting mode
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      if (gameLoop.skillTargetingSystem) {
        const registry = gameLoop.skillTargetingSystem.skillRegistry;
        if (registry instanceof Map) {
          registry.set(skillId, skillDef);
        } else if (registry.set) {
          registry.set(skillId, skillDef);
        } else {
          registry[skillId] = skillDef;
        }
      }

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      gameLoop.onSkillClicked(skillId);
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");

      // Click movement button
      gameLoop.onMovementClicked();

      // Should return to IDLE/movement mode
      expect(gameLoop.combatState).to.equal("IDLE");
      expect(gameLoop.activeSkillId).to.be.null;
    });

    it("should toggle skill off when clicking the same skill again", () => {
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      if (gameLoop.skillTargetingSystem) {
        const registry = gameLoop.skillTargetingSystem.skillRegistry;
        if (registry instanceof Map) {
          registry.set(skillId, skillDef);
        } else if (registry.set) {
          registry.set(skillId, skillDef);
        } else {
          registry[skillId] = skillDef;
        }
      }

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      // First click - enter targeting mode
      gameLoop.onSkillClicked(skillId);
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");

      // Second click - should cancel targeting
      gameLoop.onSkillClicked(skillId);
      expect(gameLoop.combatState).to.equal("IDLE");
      expect(gameLoop.activeSkillId).to.be.null;
    });
  });

  describe("Hotkey Support", () => {
    it("should trigger skill when number key is pressed", () => {
      const skillId = "SKILL_TEST";
      const skillDef = {
        id: skillId,
        name: "Test Skill",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "ENEMY",
          line_of_sight: true,
        },
        effects: [],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Test Skill",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      // Press number key 1 (first skill)
      gameLoop.handleKeyInput("Digit1");

      // Should enter targeting mode
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
      expect(gameLoop.activeSkillId).to.equal(skillId);
    });

    it("should trigger movement mode when M key is pressed", () => {
      // First enter skill targeting mode
      const skillId = "SKILL_TEST";
      const skillDef = {
        id: skillId,
        name: "Test Skill",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "ENEMY",
          line_of_sight: true,
        },
        effects: [],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Test Skill",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      gameLoop.onSkillClicked(skillId);
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");

      // Press M key
      gameLoop.handleKeyInput("KeyM");

      // Should return to movement mode
      expect(gameLoop.combatState).to.equal("IDLE");
    });
  });

  describe("TELEPORT Effect Execution", () => {
    it("should teleport unit to target position when TELEPORT effect is executed", async () => {
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      // Add skill to unit actions
      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      });

      const originalPos = { ...playerUnit.position };
      const targetPos = {
        x: originalPos.x + 3,
        y: originalPos.y,
        z: originalPos.z + 3,
      };

      // Ensure target position is valid and empty
      if (gameLoop.grid.isOccupied(targetPos)) {
        // Clear it if occupied
        const unitAtPos = gameLoop.grid.getUnitAt(targetPos);
        if (unitAtPos) {
          gameLoop.grid.removeUnit(unitAtPos);
        }
      }

      // Ensure target position is walkable (floor at y=0, air at y=1 and y=2)
      gameLoop.grid.setCell(targetPos.x, 0, targetPos.z, 1); // Floor
      gameLoop.grid.setCell(targetPos.x, targetPos.y, targetPos.z, 0); // Air at y=1
      gameLoop.grid.setCell(targetPos.x, targetPos.y + 1, targetPos.z, 0); // Air at y=2 (headroom)

      // Execute the skill
      await gameLoop.executeSkill(skillId, targetPos);

      // Unit should be at target position
      expect(playerUnit.position.x).to.equal(targetPos.x);
      expect(playerUnit.position.z).to.equal(targetPos.z);
      // Y might be adjusted to walkable level
      expect(playerUnit.position.y).to.be.a("number");

      // Unit mesh should be updated
      const mesh = gameLoop.unitMeshes.get(playerUnit.id);
      if (mesh) {
        expect(mesh.position.x).to.equal(playerUnit.position.x);
        expect(mesh.position.z).to.equal(playerUnit.position.z);
      }
    });

    it("should deduct AP when executing TELEPORT skill", async () => {
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;
      const initialAP = playerUnit.currentAP;
      const targetPos = {
        x: playerUnit.position.x + 2,
        y: playerUnit.position.y,
        z: playerUnit.position.z + 2,
      };

      // Ensure target is empty
      if (gameLoop.grid.isOccupied(targetPos)) {
        const unitAtPos = gameLoop.grid.getUnitAt(targetPos);
        if (unitAtPos) {
          gameLoop.grid.removeUnit(unitAtPos);
        }
      }

      await gameLoop.executeSkill(skillId, targetPos);

      // AP should be deducted
      expect(playerUnit.currentAP).to.equal(initialAP - 2);
    });

    it("should set cooldown when executing skill", async () => {
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        cooldown_turns: 4,
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      const skillAction = {
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      };
      playerUnit.actions.push(skillAction);

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      const targetPos = {
        x: playerUnit.position.x + 2,
        y: playerUnit.position.y,
        z: playerUnit.position.z + 2,
      };

      // Ensure target is empty
      if (gameLoop.grid.isOccupied(targetPos)) {
        const unitAtPos = gameLoop.grid.getUnitAt(targetPos);
        if (unitAtPos) {
          gameLoop.grid.removeUnit(unitAtPos);
        }
      }

      await gameLoop.executeSkill(skillId, targetPos);

      // Cooldown should be set
      expect(skillAction.cooldown).to.equal(1);
    });
  });

  describe("Skill Targeting State Management", () => {
    it("should clear highlights when ending turn with skill active", () => {
      const skillId = "SKILL_TELEPORT";
      const skillDef = {
        id: skillId,
        name: "Phase Shift",
        costs: { ap: 2 },
        targeting: {
          range: 5,
          type: "EMPTY",
          line_of_sight: false,
        },
        effects: [{ type: "TELEPORT" }],
      };

      // Register the skill in the skill registry
      skillRegistry.skills.set(skillId, skillDef);

      if (!playerUnit.actions) {
        playerUnit.actions = [];
      }
      playerUnit.actions.push({
        id: skillId,
        name: "Phase Shift",
        costAP: 2,
        cooldown: 0,
      });

      // Ensure unit has enough AP
      playerUnit.currentAP = 10;

      // Enter targeting mode
      gameLoop.onSkillClicked(skillId);
      expect(gameLoop.combatState).to.equal("TARGETING_SKILL");

      // End turn
      gameLoop.endTurn();

      // Should clear targeting state
      expect(gameLoop.combatState).to.equal("IDLE");
      expect(gameLoop.activeSkillId).to.be.null;
    });
  });
});

