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

describe("Systems: MovementSystem", function () {
  let movementSystem;
  let grid;
  let unitManager;
  let mockRegistry;

  beforeEach(() => {
    // Create mock 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,
      },
    });

    unitManager = new UnitManager(mockRegistry);
    grid = new VoxelGrid(20, 20, 20);

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

    movementSystem = new MovementSystem(grid, unitManager);
  });

  describe("CoA 1: Validation", () => {
    it("should fail if tile is blocked/occupied", () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 10;
      grid.placeUnit(unit, unit.position);

      // Try to move to an occupied tile
      const occupiedPos = { x: 6, y: 1, z: 5 };
      const otherUnit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      grid.placeUnit(otherUnit, occupiedPos);

      const result = movementSystem.validateMove(unit, occupiedPos);
      expect(result.valid).to.be.false;
    });

    it("should fail if no path exists (out of range)", () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 10;
      unit.baseStats.movement = 2; // Limited movement
      grid.placeUnit(unit, unit.position);

      // Try to move too far
      const farPos = { x: 10, y: 1, z: 10 };

      const result = movementSystem.validateMove(unit, farPos);
      expect(result.valid).to.be.false;
    });

    it("should fail if unit has insufficient AP", () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 1; // Not enough AP
      grid.placeUnit(unit, unit.position);

      // Try to move 2 tiles away (costs 2 AP)
      const targetPos = { x: 7, y: 1, z: 5 };

      const result = movementSystem.validateMove(unit, targetPos);
      expect(result.valid).to.be.false;
      expect(result.cost).to.equal(2);
    });

    it("should succeed if all conditions are met", () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 10;
      grid.placeUnit(unit, unit.position);

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

      const result = movementSystem.validateMove(unit, targetPos);
      expect(result.valid).to.be.true;
      expect(result.cost).to.equal(1);
      expect(result.path.length).to.be.greaterThan(0);
    });
  });

  describe("CoA 2: Execution", () => {
    it("should update unit position in grid", async () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 10;
      grid.placeUnit(unit, unit.position);

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

      const success = await movementSystem.executeMove(unit, targetPos);
      expect(success).to.be.true;
      expect(unit.position.x).to.equal(6);
      expect(unit.position.z).to.equal(5);
    });

    it("should update grid occupancy map", async () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 10;
      grid.placeUnit(unit, unit.position);

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

      // Old position should be occupied
      expect(grid.isOccupied({ x: 5, y: 1, z: 5 })).to.be.true;

      await movementSystem.executeMove(unit, targetPos);

      // New position should be occupied
      expect(grid.isOccupied({ x: 6, y: 1, z: 5 })).to.be.true;
      // Old position should be free
      expect(grid.isOccupied({ x: 5, y: 1, z: 5 })).to.be.false;
    });

    it("should deduct correct AP cost", async () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.currentAP = 10;
      grid.placeUnit(unit, unit.position);

      const targetPos = { x: 7, y: 1, z: 5 }; // 2 tiles away

      const initialAP = unit.currentAP;
      await movementSystem.executeMove(unit, targetPos);

      // Should have deducted 2 AP (Manhattan distance)
      expect(unit.currentAP).to.equal(initialAP - 2);
    });
  });

  describe("getReachableTiles", () => {
    it("should return all reachable positions within movement range", () => {
      const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER");
      unit.position = { x: 5, y: 1, z: 5 };
      unit.baseStats.movement = 2;
      grid.placeUnit(unit, unit.position);

      const reachable = movementSystem.getReachableTiles(unit, 2);

      // Should include starting position and nearby tiles
      expect(reachable.length).to.be.greaterThan(0);
      expect(reachable.some((pos) => pos.x === 5 && pos.z === 5)).to.be.true;
    });
  });
});

