import { expect } from "@esm-bundle/chai";
import { SkillTargetingSystem } from "../../src/systems/SkillTargetingSystem.js";
import { VoxelGrid } from "../../src/grid/VoxelGrid.js";
import { UnitManager } from "../../src/managers/UnitManager.js";
import { Explorer } from "../../src/units/Explorer.js";
import { Enemy } from "../../src/units/Enemy.js";

describe("Systems: SkillTargetingSystem", function () {
  let targetingSystem;
  let grid;
  let unitManager;
  let mockRegistry;
  let skillRegistry;

  beforeEach(() => {
    // Create a 20x10x20 grid
    grid = new VoxelGrid(20, 10, 20);

    // Create floor at y=0
    for (let x = 0; x < 20; x++) {
      for (let z = 0; z < 20; z++) {
        grid.setCell(x, 0, z, 1); // Floor
        grid.setCell(x, 1, z, 0); // Air at y=1 (walkable)
      }
    }

    // Create mock unit registry
    mockRegistry = new Map();
    mockRegistry.set("CLASS_VANGUARD", {
      id: "CLASS_VANGUARD",
      name: "Vanguard",
      base_stats: {
        health: 100,
        attack: 10,
        defense: 5,
        speed: 10,
        movement: 4,
      },
    });
    mockRegistry.set("ENEMY_DEFAULT", {
      id: "ENEMY_DEFAULT",
      name: "Enemy",
      type: "ENEMY",
      base_stats: {
        health: 50,
        attack: 5,
        defense: 2,
        speed: 8,
        movement: 3,
      },
    });

    unitManager = new UnitManager(mockRegistry);

    // Create skill registry with various skill types (using nested format like actual JSON files)
    skillRegistry = new Map();
    skillRegistry.set("SKILL_SINGLE_TARGET", {
      id: "SKILL_SINGLE_TARGET",
      name: "Single Target",
      costs: { ap: 2 },
      targeting: {
        range: 5,
        type: "ENEMY",
        line_of_sight: true,
      },
      effects: [],
    });
    skillRegistry.set("SKILL_CIRCLE_AOE", {
      id: "SKILL_CIRCLE_AOE",
      name: "Circle AoE",
      costs: { ap: 3 },
      targeting: {
        range: 4,
        type: "ENEMY",
        line_of_sight: true,
        area_of_effect: { shape: "CIRCLE", size: 2 },
      },
      effects: [],
    });
    skillRegistry.set("SKILL_LINE_AOE", {
      id: "SKILL_LINE_AOE",
      name: "Line AoE",
      costs: { ap: 2 },
      targeting: {
        range: 6,
        type: "ENEMY",
        line_of_sight: true,
        area_of_effect: { shape: "LINE", size: 4 },
      },
      effects: [],
    });
    skillRegistry.set("SKILL_ALLY_HEAL", {
      id: "SKILL_ALLY_HEAL",
      name: "Heal",
      costs: { ap: 2 },
      targeting: {
        range: 3,
        type: "ALLY",
        line_of_sight: true,
      },
      effects: [],
    });
    skillRegistry.set("SKILL_EMPTY_TARGET", {
      id: "SKILL_EMPTY_TARGET",
      name: "Place Trap",
      costs: { ap: 1 },
      targeting: {
        range: 3,
        type: "EMPTY",
        line_of_sight: true,
      },
      effects: [],
    });
    skillRegistry.set("SKILL_IGNORE_COVER", {
      id: "SKILL_IGNORE_COVER",
      name: "Piercing Shot",
      costs: { ap: 3 },
      targeting: {
        range: 8,
        type: "ENEMY",
        line_of_sight: false, // false means ignore cover
      },
      effects: [],
    });

    targetingSystem = new SkillTargetingSystem(
      grid,
      unitManager,
      skillRegistry
    );
  });

  describe("Range Validation", () => {
    it("should validate target within range", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      // Add an enemy at target position for ENEMY target type validation
      const enemy = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      const targetPos = { x: 7, y: 1, z: 5 }; // 2 tiles away (within range 5)
      enemy.position = targetPos;
      grid.placeUnit(enemy, targetPos);

      const result = targetingSystem.validateTarget(
        source,
        targetPos,
        "SKILL_SINGLE_TARGET"
      );

      expect(result.valid).to.be.true;
    });

    it("should reject target out of range", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const targetPos = { x: 12, y: 1, z: 5 }; // 7 tiles away (out of range 5)

      const result = targetingSystem.validateTarget(
        source,
        targetPos,
        "SKILL_SINGLE_TARGET"
      );

      expect(result.valid).to.be.false;
      expect(result.reason).to.include("range");
    });
  });

  describe("Line of Sight (LOS)", () => {
    it("should validate target with clear line of sight", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      // Add an enemy at target position for ENEMY target type validation
      const enemy = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      const targetPos = { x: 8, y: 1, z: 5 }; // Clear path
      enemy.position = targetPos;
      grid.placeUnit(enemy, targetPos);

      const result = targetingSystem.validateTarget(
        source,
        targetPos,
        "SKILL_SINGLE_TARGET"
      );

      expect(result.valid).to.be.true;
    });

    it("should reject target blocked by solid voxel", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      // Place a wall between source and target
      grid.setCell(6, 1, 5, 1); // Solid block
      grid.setCell(6, 2, 5, 1); // Solid block

      const targetPos = { x: 8, y: 1, z: 5 };

      const result = targetingSystem.validateTarget(
        source,
        targetPos,
        "SKILL_SINGLE_TARGET"
      );

      expect(result.valid).to.be.false;
      expect(result.reason).to.include("line of sight");
    });

    it("should allow target blocked by cover if skill ignores cover", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      // Place a wall between source and target
      grid.setCell(6, 1, 5, 1);
      grid.setCell(6, 2, 5, 1);

      // Add an enemy at target position for ENEMY target type validation
      const enemy = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      const targetPos = { x: 8, y: 1, z: 5 };
      enemy.position = targetPos;
      grid.placeUnit(enemy, targetPos);

      const result = targetingSystem.validateTarget(
        source,
        targetPos,
        "SKILL_IGNORE_COVER"
      );

      expect(result.valid).to.be.true; // Should pass despite cover
    });
  });

  describe("Target Type Validation", () => {
    it("should validate ENEMY target type", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const enemy = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      enemy.position = { x: 7, y: 1, z: 5 };
      grid.placeUnit(enemy, enemy.position);

      const result = targetingSystem.validateTarget(
        source,
        enemy.position,
        "SKILL_SINGLE_TARGET"
      );

      expect(result.valid).to.be.true;
    });

    it("should reject ENEMY target type when targeting ally", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const ally = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      ally.position = { x: 7, y: 1, z: 5 };
      grid.placeUnit(ally, ally.position);

      const result = targetingSystem.validateTarget(
        source,
        ally.position,
        "SKILL_SINGLE_TARGET"
      );

      expect(result.valid).to.be.false;
      expect(result.reason).to.include("target type");
    });

    it("should validate ALLY target type", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const ally = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      ally.position = { x: 7, y: 1, z: 5 };
      grid.placeUnit(ally, ally.position);

      const result = targetingSystem.validateTarget(
        source,
        ally.position,
        "SKILL_ALLY_HEAL"
      );

      expect(result.valid).to.be.true;
    });

    it("should validate EMPTY target type", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const emptyPos = { x: 7, y: 1, z: 5 }; // No unit here

      const result = targetingSystem.validateTarget(
        source,
        emptyPos,
        "SKILL_EMPTY_TARGET"
      );

      expect(result.valid).to.be.true;
    });

    it("should reject EMPTY target type when tile is occupied", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const enemy = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      enemy.position = { x: 7, y: 1, z: 5 };
      grid.placeUnit(enemy, enemy.position);

      const result = targetingSystem.validateTarget(
        source,
        enemy.position,
        "SKILL_EMPTY_TARGET"
      );

      expect(result.valid).to.be.false;
      expect(result.reason).to.include("empty");
    });
  });

  describe("AoE Calculation: SINGLE", () => {
    it("should return single tile for SINGLE AoE", () => {
      const sourcePos = { x: 5, y: 1, z: 5 };
      const cursorPos = { x: 7, y: 1, z: 5 };

      const tiles = targetingSystem.getAoETiles(
        sourcePos,
        cursorPos,
        "SKILL_SINGLE_TARGET"
      );

      expect(tiles).to.have.length(1);
      expect(tiles[0]).to.deep.equal(cursorPos);
    });
  });

  describe("AoE Calculation: CIRCLE", () => {
    it("should return all tiles within radius for CIRCLE AoE", () => {
      const sourcePos = { x: 5, y: 1, z: 5 };
      const cursorPos = { x: 7, y: 1, z: 5 }; // Center of circle

      const tiles = targetingSystem.getAoETiles(
        sourcePos,
        cursorPos,
        "SKILL_CIRCLE_AOE"
      );

      // Circle with radius 2 should include center + surrounding tiles
      // Should have at least the center tile
      expect(tiles.length).to.be.greaterThan(0);
      expect(tiles.some((t) => t.x === 7 && t.z === 5)).to.be.true;
    });

    it("should include tiles at exact radius distance", () => {
      const sourcePos = { x: 5, y: 1, z: 5 };
      const cursorPos = { x: 7, y: 1, z: 5 };

      const tiles = targetingSystem.getAoETiles(
        sourcePos,
        cursorPos,
        "SKILL_CIRCLE_AOE"
      );

      // Check that tiles at radius 2 are included (Manhattan distance)
      const hasRadius2Tile = tiles.some((t) => {
        const dist =
          Math.abs(t.x - cursorPos.x) +
          Math.abs(t.y - cursorPos.y) +
          Math.abs(t.z - cursorPos.z);
        return dist === 2;
      });
      expect(hasRadius2Tile).to.be.true;
    });
  });

  describe("AoE Calculation: LINE", () => {
    it("should return line of tiles from source to target for LINE AoE", () => {
      const sourcePos = { x: 5, y: 1, z: 5 };
      const cursorPos = { x: 9, y: 1, z: 5 }; // 4 tiles east

      const tiles = targetingSystem.getAoETiles(
        sourcePos,
        cursorPos,
        "SKILL_LINE_AOE"
      );

      // Line should include tiles along the path
      expect(tiles.length).to.be.greaterThan(1);
      // Should include source position or nearby tiles in the line
      const hasSourceDirection = tiles.some(
        (t) => t.x >= sourcePos.x && t.x <= cursorPos.x && t.z === sourcePos.z
      );
      expect(hasSourceDirection).to.be.true;
    });
  });

  describe("getUnitsInAoE", () => {
    it("should return all units within AoE", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      // Place enemies in AoE range
      const enemy1 = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      enemy1.position = { x: 7, y: 1, z: 5 };
      grid.placeUnit(enemy1, enemy1.position);

      const enemy2 = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      enemy2.position = { x: 8, y: 1, z: 5 };
      grid.placeUnit(enemy2, enemy2.position);

      const sourcePos = source.position;
      const targetPos = { x: 7, y: 1, z: 5 };
      const units = targetingSystem.getUnitsInAoE(
        sourcePos,
        targetPos,
        "SKILL_CIRCLE_AOE"
      );

      expect(units.length).to.be.greaterThan(0);
      expect(units.some((u) => u.id === enemy1.id)).to.be.true;
    });

    it("should only return units within AoE radius", () => {
      const source = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      source.position = { x: 5, y: 1, z: 5 };
      grid.placeUnit(source, source.position);

      const enemy1 = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      enemy1.position = { x: 7, y: 1, z: 5 }; // Within radius 2
      grid.placeUnit(enemy1, enemy1.position);

      const enemy2 = unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
      enemy2.position = { x: 12, y: 1, z: 5 }; // Outside radius 2
      grid.placeUnit(enemy2, enemy2.position);

      const sourcePos = { x: 5, y: 1, z: 5 };
      const targetPos = { x: 7, y: 1, z: 5 };
      const units = targetingSystem.getUnitsInAoE(
        sourcePos,
        targetPos,
        "SKILL_CIRCLE_AOE"
      );

      expect(units.some((u) => u.id === enemy1.id)).to.be.true;
      expect(units.some((u) => u.id === enemy2.id)).to.be.false;
    });
  });
});
