import { expect } from "@esm-bundle/chai";
import { MissionGenerator } from "../../src/systems/MissionGenerator.js";

describe("Systems: MissionGenerator", function () {
  describe("Data Arrays", () => {
    it("should have adjectives array", () => {
      expect(MissionGenerator.ADJECTIVES).to.be.an("array");
      expect(MissionGenerator.ADJECTIVES.length).to.be.greaterThan(0);
      expect(MissionGenerator.ADJECTIVES).to.include("Silent");
      expect(MissionGenerator.ADJECTIVES).to.include("Crimson");
    });

    it("should have type-specific noun arrays", () => {
      expect(MissionGenerator.NOUNS_SKIRMISH).to.be.an("array");
      expect(MissionGenerator.NOUNS_SALVAGE).to.be.an("array");
      expect(MissionGenerator.NOUNS_ELIMINATE_UNIT).to.be.an("array");
      expect(MissionGenerator.NOUNS_RECON).to.be.an("array");

      expect(MissionGenerator.NOUNS_SKIRMISH).to.include("Thunder");
      expect(MissionGenerator.NOUNS_SALVAGE).to.include("Cache");
      expect(MissionGenerator.NOUNS_ELIMINATE_UNIT).to.include("Viper");
      expect(MissionGenerator.NOUNS_RECON).to.include("Eye");
    });
  });

  describe("Utility Methods", () => {
    describe("toRomanNumeral", () => {
      it("should convert numbers to Roman numerals", () => {
        expect(MissionGenerator.toRomanNumeral(1)).to.equal("I");
        expect(MissionGenerator.toRomanNumeral(2)).to.equal("II");
        expect(MissionGenerator.toRomanNumeral(3)).to.equal("III");
        expect(MissionGenerator.toRomanNumeral(4)).to.equal("IV");
        expect(MissionGenerator.toRomanNumeral(5)).to.equal("V");
      });
    });

    describe("extractBaseName", () => {
      it("should extract base name from mission title", () => {
        expect(
          MissionGenerator.extractBaseName("Operation: Silent Viper")
        ).to.equal("Silent Viper");
        expect(
          MissionGenerator.extractBaseName("Operation: Silent Viper II")
        ).to.equal("Silent Viper");
        expect(
          MissionGenerator.extractBaseName("Operation: Crimson Cache III")
        ).to.equal("Crimson Cache");
      });
    });

    describe("findHighestNumeral", () => {
      it("should find highest Roman numeral in history", () => {
        const history = [
          "Operation: Silent Viper",
          "Operation: Silent Viper II",
          "Operation: Silent Viper III",
        ];
        expect(
          MissionGenerator.findHighestNumeral("Silent Viper", history)
        ).to.equal(3);
      });

      it("should return 0 if no matches found", () => {
        const history = ["Operation: Other Mission"];
        expect(
          MissionGenerator.findHighestNumeral("Silent Viper", history)
        ).to.equal(0);
      });
    });

    describe("selectBiome", () => {
      it("should select from unlocked regions", () => {
        const regions = ["BIOME_RUSTING_WASTES", "BIOME_CRYSTAL_SPIRES"];
        const biome = MissionGenerator.selectBiome(regions);
        expect(regions).to.include(biome);
      });

      it("should return default if no regions provided", () => {
        const biome = MissionGenerator.selectBiome([]);
        expect(biome).to.equal("BIOME_RUSTING_WASTES");
      });
    });
  });

  describe("generateSideOp", () => {
    const unlockedRegions = ["BIOME_RUSTING_WASTES", "BIOME_CRYSTAL_SPIRES"];
    const emptyHistory = [];

    it("CoA 1: Should generate a mission with required structure", () => {
      const mission = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );

      expect(mission).to.have.property("id");
      expect(mission).to.have.property("type", "SIDE_QUEST");
      expect(mission).to.have.property("config");
      expect(mission).to.have.property("biome");
      expect(mission).to.have.property("objectives");
      expect(mission).to.have.property("rewards");
      expect(mission).to.have.property("expiresIn", 3);
    });

    it("CoA 2: Should generate unique mission IDs", () => {
      const mission1 = MissionGenerator.generateSideOp(
        1,
        unlockedRegions,
        emptyHistory
      );
      const mission2 = MissionGenerator.generateSideOp(
        1,
        unlockedRegions,
        emptyHistory
      );

      expect(mission1.id).to.not.equal(mission2.id);
      expect(mission1.id).to.match(/^SIDE_OP_\d+_[a-z0-9]+$/);
      expect(mission2.id).to.match(/^SIDE_OP_\d+_[a-z0-9]+$/);
    });

    it("CoA 3: Should generate title in 'Operation: [Adj] [Noun]' format", () => {
      const mission = MissionGenerator.generateSideOp(
        1,
        unlockedRegions,
        emptyHistory
      );

      expect(mission.config.title).to.match(/^Operation: .+$/);
      const parts = mission.config.title.replace("Operation: ", "").split(" ");
      expect(parts.length).to.be.at.least(2);
    });

    it("CoA 4: Should select archetype and generate appropriate objectives", () => {
      // Run multiple times to test different archetypes
      const archetypes = new Set();
      const objectiveTypes = new Set();

      for (let i = 0; i < 20; i++) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        const primaryObj = mission.objectives.primary[0];
        objectiveTypes.add(primaryObj.type);

        // Infer archetype from objective type
        if (primaryObj.type === "ELIMINATE_ALL") {
          archetypes.add("SKIRMISH");
        } else if (primaryObj.type === "INTERACT") {
          archetypes.add("SALVAGE");
        } else if (primaryObj.type === "ELIMINATE_UNIT") {
          archetypes.add("ELIMINATE_UNIT");
        } else if (primaryObj.type === "REACH_ZONE") {
          archetypes.add("RECON");
        }
      }

      // Should have generated at least 2 different archetypes
      expect(archetypes.size).to.be.greaterThan(1);
    });

    it("CoA 5: Should generate series missions with Roman numerals", () => {
      const history = ["Operation: Silent Viper"];
      const mission = MissionGenerator.generateSideOp(
        1,
        unlockedRegions,
        history
      );

      // If it matches the base name, should have "II"
      if (mission.config.title.includes("Silent Viper")) {
        expect(mission.config.title).to.include("II");
      }
    });

    it("CoA 6: Should scale difficulty tier correctly", () => {
      for (let tier = 1; tier <= 5; tier++) {
        const mission = MissionGenerator.generateSideOp(
          tier,
          unlockedRegions,
          emptyHistory
        );
        expect(mission.config.difficulty_tier).to.equal(tier);
        expect(mission.config.recommended_level).to.be.a("number");
      }
    });

    it("CoA 7: Should generate biome configuration", () => {
      const mission = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );

      expect(mission.biome).to.have.property("type");
      expect(mission.biome).to.have.property("generator_config");
      expect(mission.biome.generator_config).to.have.property(
        "seed_type",
        "RANDOM"
      );
      expect(mission.biome.generator_config).to.have.property("size");
      expect(mission.biome.generator_config).to.have.property("room_count");
      expect(mission.biome.generator_config).to.have.property("density");
    });

    it("CoA 8: Should generate rewards with tier-based scaling", () => {
      const mission = MissionGenerator.generateSideOp(
        3,
        unlockedRegions,
        emptyHistory
      );

      expect(mission.rewards).to.have.property("guaranteed");
      expect(mission.rewards.guaranteed).to.have.property("xp");
      expect(mission.rewards.guaranteed).to.have.property("currency");
      expect(mission.rewards.guaranteed.currency).to.have.property(
        "aether_shards"
      );
      expect(mission.rewards).to.have.property("faction_reputation");

      // Higher tier should have higher rewards
      const lowTierMission = MissionGenerator.generateSideOp(
        1,
        unlockedRegions,
        emptyHistory
      );
      const highTierMission = MissionGenerator.generateSideOp(
        5,
        unlockedRegions,
        emptyHistory
      );

      expect(
        highTierMission.rewards.guaranteed.currency.aether_shards
      ).to.be.greaterThan(
        lowTierMission.rewards.guaranteed.currency.aether_shards
      );
    });

    it("CoA 9: Should generate archetype-specific objectives", () => {
      // Test Skirmish (ELIMINATE_ALL)
      let foundSkirmish = false;
      for (let i = 0; i < 30 && !foundSkirmish; i++) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        if (mission.objectives.primary[0].type === "ELIMINATE_ALL") {
          foundSkirmish = true;
          expect(mission.objectives.primary[0].description).to.include(
            "Clear the sector"
          );
        }
      }
      expect(foundSkirmish).to.be.true;

      // Test Salvage (INTERACT)
      let foundSalvage = false;
      for (let i = 0; i < 30 && !foundSalvage; i++) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        if (mission.objectives.primary[0].type === "INTERACT") {
          foundSalvage = true;
          expect(mission.objectives.primary[0].target_object_id).to.equal(
            "OBJ_SUPPLY_CRATE"
          );
          expect(mission.objectives.primary[0].target_count).to.be.at.least(3);
          expect(mission.objectives.primary[0].target_count).to.be.at.most(5);
        }
      }
      expect(foundSalvage).to.be.true;

      // Test Eliminate Unit
      let foundEliminateUnit = false;
      for (let i = 0; i < 30 && !foundEliminateUnit; i++) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        if (mission.objectives.primary[0].type === "ELIMINATE_UNIT") {
          foundEliminateUnit = true;
          expect(mission.objectives.primary[0]).to.have.property(
            "target_def_id"
          );
          expect(mission.objectives.primary[0].description).to.include(
            "High-Value Target"
          );
        }
      }
      expect(foundEliminateUnit).to.be.true;

      // Test Recon (REACH_ZONE)
      let foundRecon = false;
      for (let i = 0; i < 30 && !foundRecon; i++) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        if (mission.objectives.primary[0].type === "REACH_ZONE") {
          foundRecon = true;
          expect(mission.objectives.primary[0].target_count).to.equal(3);
          const hasTurnLimit = mission.objectives.failure_conditions.some(
            (fc) => fc.type === "TURN_LIMIT_EXCEEDED"
          );
          expect(hasTurnLimit).to.be.true;
        }
      }
      expect(foundRecon).to.be.true;
    });

    it("CoA 10: Should generate archetype-specific biome configs", () => {
      // Test multiple times to get different archetypes
      const configs = new Map();

      for (let i = 0; i < 50; i++) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        const objType = mission.objectives.primary[0].type;
        if (!configs.has(objType)) {
          configs.set(objType, mission.biome.generator_config);
        }
      }

      // Check that RECON has larger maps
      const reconConfig = Array.from(configs.entries()).find(([type]) => {
        // Find a mission with REACH_ZONE to check its config
        return type === "REACH_ZONE";
      });
      if (reconConfig) {
        const size = reconConfig[1].size;
        expect(size.x).to.be.at.least(24);
        expect(size.z).to.be.at.least(24);
        expect(reconConfig[1].density).to.equal("LOW");
      }
    });

    it("CoA 11: Should map biome to faction reputation", () => {
      const mission = MissionGenerator.generateSideOp(
        2,
        ["BIOME_RUSTING_WASTES"],
        emptyHistory
      );

      expect(mission.rewards.faction_reputation).to.have.property(
        "COGWORK_CONCORD"
      );
      expect(mission.rewards.faction_reputation.COGWORK_CONCORD).to.equal(10);
    });

    it("CoA 12: Should clamp tier to valid range (1-5)", () => {
      const lowMission = MissionGenerator.generateSideOp(
        0,
        unlockedRegions,
        emptyHistory
      );
      const highMission = MissionGenerator.generateSideOp(
        10,
        unlockedRegions,
        emptyHistory
      );

      expect(lowMission.config.difficulty_tier).to.equal(1);
      expect(highMission.config.difficulty_tier).to.equal(5);
    });
  });

  describe("refreshBoard", () => {
    const unlockedRegions = ["BIOME_RUSTING_WASTES"];
    const emptyHistory = [];

    it("CoA 13: Should fill board up to 5 missions", () => {
      const emptyBoard = [];
      const refreshed = MissionGenerator.refreshBoard(
        emptyBoard,
        2,
        unlockedRegions,
        emptyHistory
      );

      expect(refreshed.length).to.equal(5);
    });

    it("CoA 14: Should not exceed 5 missions", () => {
      const existingMissions = [
        MissionGenerator.generateSideOp(2, unlockedRegions, emptyHistory),
        MissionGenerator.generateSideOp(2, unlockedRegions, emptyHistory),
        MissionGenerator.generateSideOp(2, unlockedRegions, emptyHistory),
      ];
      const refreshed = MissionGenerator.refreshBoard(
        existingMissions,
        2,
        unlockedRegions,
        emptyHistory
      );

      expect(refreshed.length).to.equal(5);
    });

    it("CoA 15: Should remove expired missions on daily reset", () => {
      const mission1 = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );
      mission1.expiresIn = 1; // About to expire
      const mission2 = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );
      mission2.expiresIn = 3; // Still valid

      const board = [mission1, mission2];
      const refreshed = MissionGenerator.refreshBoard(
        board,
        2,
        unlockedRegions,
        emptyHistory,
        true
      );

      // Mission1 should be removed (expiresIn becomes 0), mission2 kept, then filled to 5
      expect(refreshed.length).to.equal(5);
      // Mission1 should not be in the list
      expect(refreshed.find((m) => m.id === mission1.id)).to.be.undefined;
      // Mission2 should be present with decremented expiresIn
      const foundMission2 = refreshed.find((m) => m.id === mission2.id);
      expect(foundMission2).to.exist;
      expect(foundMission2.expiresIn).to.equal(2);
    });

    it("CoA 16: Should not remove missions when not daily reset", () => {
      const mission1 = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );
      mission1.expiresIn = 1;
      const mission2 = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );
      mission2.expiresIn = 3;

      const board = [mission1, mission2];
      const refreshed = MissionGenerator.refreshBoard(
        board,
        2,
        unlockedRegions,
        emptyHistory,
        false
      );

      // Both should remain (not expired yet), expiresIn unchanged, then filled to 5
      expect(refreshed.length).to.equal(5);
      const foundMission1 = refreshed.find((m) => m.id === mission1.id);
      const foundMission2 = refreshed.find((m) => m.id === mission2.id);
      expect(foundMission1).to.exist;
      expect(foundMission2).to.exist;
      expect(foundMission1.expiresIn).to.equal(1);
      expect(foundMission2.expiresIn).to.equal(3);
    });

    it("CoA 17: Should preserve valid missions and add new ones", () => {
      const mission1 = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );
      mission1.expiresIn = 3;

      const board = [mission1];
      const refreshed = MissionGenerator.refreshBoard(
        board,
        2,
        unlockedRegions,
        emptyHistory
      );

      expect(refreshed.length).to.equal(5);
      expect(refreshed.find((m) => m.id === mission1.id)).to.exist;
    });

    it("CoA 18: Should handle missions without expiresIn", () => {
      const mission1 = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );
      delete mission1.expiresIn;

      const board = [mission1];
      const refreshed = MissionGenerator.refreshBoard(
        board,
        2,
        unlockedRegions,
        emptyHistory,
        true
      );

      // Mission without expiresIn should be preserved
      expect(refreshed.find((m) => m.id === mission1.id)).to.exist;
    });
  });

  describe("Reward Calculation", () => {
    const unlockedRegions = ["BIOME_RUSTING_WASTES"];

    it("CoA 19: Should calculate currency with tier multiplier and random factor", () => {
      // Generate multiple missions to account for different archetypes
      // Non-Eliminate Unit missions: Base 50 * 2.5 (tier 3) * random(0.8, 1.2) = 100-150 range
      // Eliminate Unit missions get 1.5x bonus: 150-225 range
      let foundNonEliminateUnit = false;
      for (let i = 0; i < 20 && !foundNonEliminateUnit; i++) {
        const mission = MissionGenerator.generateSideOp(3, unlockedRegions, []);
        const currency = mission.rewards.guaranteed.currency.aether_shards;
        const isEliminateUnit =
          mission.objectives.primary[0].type === "ELIMINATE_UNIT";

        if (!isEliminateUnit) {
          foundNonEliminateUnit = true;
          expect(currency).to.be.at.least(100); // 50 * 2.5 * 0.8 = 100
          expect(currency).to.be.at.most(150); // 50 * 2.5 * 1.2 = 150
        } else {
          // Assassination missions get 1.5x bonus
          expect(currency).to.be.at.least(150); // 50 * 2.5 * 0.8 * 1.5 = 150
          expect(currency).to.be.at.most(225); // 50 * 2.5 * 1.2 * 1.5 = 225
        }
      }
      expect(foundNonEliminateUnit).to.be.true;
    });

    it("CoA 20: Should give bonus currency for Eliminate Unit missions", () => {
      let foundEliminateUnit = false;
      let eliminateUnitCurrency = 0;
      let otherCurrency = 0;

      for (let i = 0; i < 50 && !foundEliminateUnit; i++) {
        const mission = MissionGenerator.generateSideOp(2, unlockedRegions, []);
        if (mission.objectives.primary[0].type === "ELIMINATE_UNIT") {
          foundEliminateUnit = true;
          eliminateUnitCurrency =
            mission.rewards.guaranteed.currency.aether_shards;
        } else {
          otherCurrency = mission.rewards.guaranteed.currency.aether_shards;
        }
      }

      if (foundEliminateUnit) {
        // Eliminate Unit should have higher currency (1.5x bonus)
        expect(eliminateUnitCurrency).to.be.greaterThan(otherCurrency * 0.9);
      }
    });

    it("CoA 21: Should have chance for item drops based on tier", () => {
      // Higher tier should have higher chance
      let foundItem = false;
      for (let i = 0; i < 50; i++) {
        const mission = MissionGenerator.generateSideOp(5, unlockedRegions, []);
        if (
          mission.rewards.guaranteed.items &&
          mission.rewards.guaranteed.items.length > 0
        ) {
          foundItem = true;
          expect(mission.rewards.guaranteed.items[0]).to.match(/^ITEM_/);
          break;
        }
      }
      // Tier 5 has 100% chance (5 * 0.2), so should always find one
      if (!foundItem) {
        // Fallback check if random failed (unlikely with 50 tries at 100% but safe)
      }
    });
  });

  describe("New Features: Narrative, Hazards, Bosses", () => {
    const unlockedRegions = ["BIOME_RUSTING_WASTES"];
    const emptyHistory = [];

    it("CoA 22: Should generate dynamic narrative with correct structure", () => {
      const mission = MissionGenerator.generateSideOp(
        2,
        unlockedRegions,
        emptyHistory
      );

      expect(mission).to.have.property("narrative");
      expect(mission.narrative).to.have.property("intro_sequence");
      expect(mission.narrative).to.have.property("outro_success");
      expect(mission.narrative).to.have.property("_dynamic_data");

      const introId = mission.narrative.intro_sequence;
      const dynamicData = mission.narrative._dynamic_data;

      expect(dynamicData).to.have.property(introId);
      expect(dynamicData[introId].nodes).to.be.an("array");
      expect(dynamicData[introId].nodes[0].text).to.be.a("string");
      expect(dynamicData[introId].nodes[0].text.length).to.be.greaterThan(10);
    });

    it("CoA 23: Should generate hazards for biomes (probabilistic)", () => {
      // Check that we eventually get a hazard
      let foundHazard = false;
      let attempts = 0;
      while (!foundHazard && attempts < 50) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        if (mission.biome.hazards && mission.biome.hazards.length > 0) {
          foundHazard = true;
          expect(
            MissionGenerator.BIOME_HAZARDS["BIOME_RUSTING_WASTES"]
          ).to.include(mission.biome.hazards[0]);
        }
        attempts++;
      }
      expect(foundHazard, "Should have generated a hazard within 50 attempts")
        .to.be.true;
    });

    it("CoA 24: Should generate boss config for Eliminate Unit missions", () => {
      let foundEliminateUnit = false;
      let attempts = 0;

      while (!foundEliminateUnit && attempts < 50) {
        const mission = MissionGenerator.generateSideOp(
          2,
          unlockedRegions,
          emptyHistory
        );
        if (mission.config.boss_config) {
          foundEliminateUnit = true;
          const config = mission.config.boss_config;

          expect(config).to.have.property("target_def_id");
          expect(config).to.have.property("name");
          expect(config).to.have.property("stats");
          expect(config.stats.hp_multiplier).to.equal(2.0);
          expect(config.stats.attack_multiplier).to.equal(1.5);
        }
        attempts++;
      }
      expect(
        foundEliminateUnit,
        "Should have generated an Eliminate Unit mission with boss config"
      ).to.be.true;
    });

    it("CoA 25: Narrative text should contain replaced variables", () => {
      const mission = MissionGenerator.generateSideOp(
        2,
        ["BIOME_RUSTING_WASTES"],
        emptyHistory
      );
      const dynamicData = mission.narrative._dynamic_data;
      const introId = mission.narrative.intro_sequence;
      const text = dynamicData[introId].nodes[0].text;

      // Check for replacements (lowercase biome name)
      expect(text).to.not.include("{biome}");
      expect(text).to.not.include("{enemy}");
      expect(text).to.include("rusting wastes");
    });
  });
});
