import { expect } from "@esm-bundle/chai";
import sinon from "sinon";
import { MarketManager } from "../../src/managers/MarketManager.js";
import { InventoryManager } from "../../src/managers/InventoryManager.js";
import { InventoryContainer } from "../../src/models/InventoryContainer.js";
import { Item } from "../../src/items/Item.js";
import { ItemRegistry } from "../../src/managers/ItemRegistry.js";

describe("Manager: MarketManager", () => {
  let marketManager;
  let mockPersistence;
  let mockItemRegistry;
  let mockInventoryManager;
  let mockMissionManager;
  let hubStash;

  beforeEach(() => {
    // Mock Persistence
    mockPersistence = {
      init: sinon.stub().resolves(),
      saveMarketState: sinon.stub().resolves(),
      loadMarketState: sinon.stub().resolves(null),
    };

    // Mock Item Registry
    mockItemRegistry = {
      loadAll: sinon.stub().resolves(),
      get: (defId) => {
        const items = {
          "ITEM_RUSTY_BLADE": new Item({
            id: "ITEM_RUSTY_BLADE",
            name: "Rusty Infantry Blade",
            type: "WEAPON",
            rarity: "COMMON",
            stats: { attack: 3 },
          }),
          "ITEM_SCRAP_PLATE": new Item({
            id: "ITEM_SCRAP_PLATE",
            name: "Scrap Plate Armor",
            type: "ARMOR",
            rarity: "COMMON",
            stats: { defense: 3 },
          }),
          "ITEM_APPRENTICE_WAND": new Item({
            id: "ITEM_APPRENTICE_WAND",
            name: "Apprentice Spark-Wand",
            type: "WEAPON",
            rarity: "COMMON",
            stats: { magic: 4 },
          }),
          "ITEM_ROBES": new Item({
            id: "ITEM_ROBES",
            name: "Novice Robes",
            type: "ARMOR",
            rarity: "COMMON",
            stats: { willpower: 3 },
          }),
          "ITEM_UNCOMMON_SWORD": new Item({
            id: "ITEM_UNCOMMON_SWORD",
            name: "Steel Blade",
            type: "WEAPON",
            rarity: "UNCOMMON",
            stats: { attack: 5 },
          }),
        };
        return items[defId] || null;
      },
      getAll: () => {
        return [
          new Item({
            id: "ITEM_RUSTY_BLADE",
            name: "Rusty Infantry Blade",
            type: "WEAPON",
            rarity: "COMMON",
            stats: { attack: 3 },
          }),
          new Item({
            id: "ITEM_SCRAP_PLATE",
            name: "Scrap Plate Armor",
            type: "ARMOR",
            rarity: "COMMON",
            stats: { defense: 3 },
          }),
          new Item({
            id: "ITEM_APPRENTICE_WAND",
            name: "Apprentice Spark-Wand",
            type: "WEAPON",
            rarity: "COMMON",
            stats: { magic: 4 },
          }),
          new Item({
            id: "ITEM_ROBES",
            name: "Novice Robes",
            type: "ARMOR",
            rarity: "COMMON",
            stats: { willpower: 3 },
          }),
          new Item({
            id: "ITEM_UNCOMMON_SWORD",
            name: "Steel Blade",
            type: "WEAPON",
            rarity: "UNCOMMON",
            stats: { attack: 5 },
          }),
        ];
      },
    };

    // Create real hub stash
    hubStash = new InventoryContainer("HUB_VAULT");
    hubStash.currency.aetherShards = 1000; // Give player some currency

    // Mock Inventory Manager
    mockInventoryManager = {
      hubStash: hubStash,
    };

    // Mock Mission Manager
    mockMissionManager = {
      completedMissions: new Set(),
    };

    marketManager = new MarketManager(
      mockPersistence,
      mockItemRegistry,
      mockInventoryManager,
      mockMissionManager
    );
  });

  afterEach(() => {
    // Clean up event listeners
    if (marketManager) {
      marketManager.destroy();
    }
    // Remove any event listeners
    window.removeEventListener("mission-victory", () => {});
  });

  describe("Initialization", () => {
    it("should initialize and load state from persistence", async () => {
      const savedState = {
        generationId: "TEST_123",
        stock: [],
        buyback: [],
      };
      mockPersistence.loadMarketState.resolves(savedState);

      await marketManager.init();

      expect(mockPersistence.loadMarketState.called).to.be.true;
      expect(mockItemRegistry.loadAll.called).to.be.true;
      expect(marketManager.marketState).to.deep.equal(savedState);
    });

    it("should generate initial Tier 1 stock if no saved state", async () => {
      mockPersistence.loadMarketState.resolves(null);

      await marketManager.init();

      expect(marketManager.marketState).to.exist;
      expect(marketManager.marketState.stock.length).to.be.greaterThan(0);
      expect(mockPersistence.saveMarketState.called).to.be.true;
    });
  });

  describe("Stock Generation", () => {
    beforeEach(async () => {
      await marketManager.init();
    });

    it("should generate Tier 1 stock with only Common items", async () => {
      await marketManager.generateStock(1);

      const stock = marketManager.marketState.stock;
      expect(stock.length).to.be.greaterThan(0);
      stock.forEach((item) => {
        expect(item.rarity).to.equal("COMMON");
      });
    });

    it("should generate Tier 2 stock with proper rarity distribution", async () => {
      await marketManager.generateStock(2);

      const stock = marketManager.marketState.stock;
      expect(stock.length).to.be.greaterThan(0);

      // Check that we have items of different rarities (at least some)
      const rarities = stock.map((item) => item.rarity);
      expect(rarities).to.include.members(["COMMON", "UNCOMMON"]);
    });

    it("should assign unique stock IDs", async () => {
      await marketManager.generateStock(1);

      const stock = marketManager.marketState.stock;
      const ids = stock.map((item) => item.id);
      const uniqueIds = new Set(ids);
      expect(uniqueIds.size).to.equal(ids.length);
    });

    it("should calculate prices with variance", async () => {
      await marketManager.generateStock(1);

      const stock = marketManager.marketState.stock;
      stock.forEach((item) => {
        expect(item.price).to.be.a("number");
        expect(item.price).to.be.greaterThan(0);
      });
    });

    it("should save state after generation", async () => {
      await marketManager.generateStock(1);

      expect(mockPersistence.saveMarketState.called).to.be.true;
      const savedState = mockPersistence.saveMarketState.firstCall.args[0];
      expect(savedState).to.equal(marketManager.marketState);
    });
  });

  describe("CoA 1: Persistence Integrity", () => {
    beforeEach(async () => {
      await marketManager.init();
      await marketManager.generateStock(1);
    });

    it("should maintain stock after reload", async () => {
      const originalStock = [...marketManager.marketState.stock];
      const originalGenerationId = marketManager.marketState.generationId;

      // Simulate reload
      mockPersistence.loadMarketState.resolves(marketManager.marketState);
      await marketManager.init();

      expect(marketManager.marketState.generationId).to.equal(
        originalGenerationId
      );
      expect(marketManager.marketState.stock.length).to.equal(
        originalStock.length
      );
    });

    it("should mark purchased items as sold out after buy", async () => {
      const stockItem = marketManager.marketState.stock[0];
      const originalPrice = stockItem.price;

      // Set currency
      hubStash.currency.aetherShards = originalPrice;

      const success = await marketManager.buyItem(stockItem.id);

      expect(success).to.be.true;
      expect(stockItem.purchased).to.be.true;

      // Reload and verify
      mockPersistence.loadMarketState.resolves(marketManager.marketState);
      await marketManager.init();

      const reloadedItem = marketManager.marketState.stock.find(
        (item) => item.id === stockItem.id
      );
      expect(reloadedItem.purchased).to.be.true;
    });
  });

  describe("CoA 2: Currency Math", () => {
    beforeEach(async () => {
      await marketManager.init();
      await marketManager.generateStock(1);
    });

    it("should deduct exact price when buying", async () => {
      const stockItem = marketManager.marketState.stock[0];
      const originalPrice = stockItem.price;
      const originalCurrency = 1000;
      hubStash.currency.aetherShards = originalCurrency;

      await marketManager.buyItem(stockItem.id);

      expect(hubStash.currency.aetherShards).to.equal(
        originalCurrency - originalPrice
      );
    });

    it("should refund exact sell price (25% of base)", async () => {
      // Add an item to stash first
      const itemInstance = {
        uid: "ITEM_TEST_123",
        defId: "ITEM_RUSTY_BLADE",
        isNew: false,
        quantity: 1,
      };
      hubStash.addItem(itemInstance);

      const originalCurrency = 1000;
      hubStash.currency.aetherShards = originalCurrency;

      // Get base price calculation
      const itemDef = mockItemRegistry.get("ITEM_RUSTY_BLADE");
      const basePrice = 50 + Object.values(itemDef.stats).reduce((sum, val) => sum + val, 0) * 10;
      const expectedSellPrice = Math.floor(basePrice * 0.25);

      const success = await marketManager.sellItem(itemInstance.uid);

      expect(success).to.be.true;
      expect(hubStash.currency.aetherShards).to.equal(
        originalCurrency + expectedSellPrice
      );
    });

    it("should allow buyback at exact sell price", async () => {
      // Add and sell an item
      const itemInstance = {
        uid: "ITEM_TEST_456",
        defId: "ITEM_RUSTY_BLADE",
        isNew: false,
        quantity: 1,
      };
      hubStash.addItem(itemInstance);

      const originalCurrency = 1000;
      hubStash.currency.aetherShards = originalCurrency;

      await marketManager.sellItem(itemInstance.uid);

      const sellPrice = hubStash.currency.aetherShards - originalCurrency;
      const buybackItem = marketManager.marketState.buyback[0];

      // Try to buy back
      hubStash.currency.aetherShards = originalCurrency + sellPrice;
      const buybackSuccess = await marketManager.buyItem(buybackItem.id);

      expect(buybackSuccess).to.be.true;
      expect(buybackItem.price).to.equal(sellPrice);
    });
  });

  describe("CoA 3: Atomic Transactions", () => {
    beforeEach(async () => {
      await marketManager.init();
      await marketManager.generateStock(1);
    });

    it("should not add item if currency is insufficient", async () => {
      const stockItem = marketManager.marketState.stock[0];
      hubStash.currency.aetherShards = stockItem.price - 1; // Not enough
      const originalItemCount = hubStash.items.length;

      const success = await marketManager.buyItem(stockItem.id);

      expect(success).to.be.false;
      expect(hubStash.items.length).to.equal(originalItemCount);
      expect(hubStash.currency.aetherShards).to.equal(stockItem.price - 1);
    });

    it("should not add currency if item removal fails", async () => {
      const originalCurrency = hubStash.currency.aetherShards;
      const nonExistentUid = "ITEM_NONEXISTENT";

      const success = await marketManager.sellItem(nonExistentUid);

      expect(success).to.be.false;
      expect(hubStash.currency.aetherShards).to.equal(originalCurrency);
    });

    it("should complete transaction atomically on success", async () => {
      const stockItem = marketManager.marketState.stock[0];
      hubStash.currency.aetherShards = stockItem.price;
      const originalItemCount = hubStash.items.length;

      const success = await marketManager.buyItem(stockItem.id);

      expect(success).to.be.true;
      expect(hubStash.items.length).to.equal(originalItemCount + 1);
      expect(stockItem.purchased).to.be.true;
      expect(hubStash.currency.aetherShards).to.equal(0);
    });
  });

  describe("CoA 4: Stock Generation", () => {
    it("should generate Tier 1 stock with only Common items", async () => {
      await marketManager.init();
      await marketManager.generateStock(1);

      const stock = marketManager.marketState.stock;
      stock.forEach((item) => {
        expect(item.rarity).to.equal("COMMON");
      });
    });

    it("should generate stock only when needsRefresh is true", async () => {
      await marketManager.init();
      const originalStock = [...marketManager.marketState.stock];

      // Check refresh without flag
      await marketManager.checkRefresh();
      expect(marketManager.marketState.stock).to.deep.equal(originalStock);

      // Set flag and check again
      marketManager.needsRefresh = true;
      await marketManager.checkRefresh();

      expect(marketManager.needsRefresh).to.be.false;
      // Stock should be different (new generation)
      expect(marketManager.marketState.generationId).to.not.equal(
        originalStock.length > 0 ? originalStock[0].id : "none"
      );
    });

    it("should listen for mission-victory event", async () => {
      await marketManager.init();
      // Reset needsRefresh to false first
      marketManager.needsRefresh = false;

      window.dispatchEvent(new CustomEvent("mission-victory"));

      // Give event listener time to process
      await new Promise((resolve) => setTimeout(resolve, 10));

      expect(marketManager.needsRefresh).to.be.true;
    });
  });

  describe("CoA 5: Buyback Limit", () => {
    beforeEach(async () => {
      await marketManager.init();
    });

    it("should limit buyback to 10 items", async () => {
      // Sell 11 items
      for (let i = 0; i < 11; i++) {
        const itemInstance = {
          uid: `ITEM_TEST_${i}`,
          defId: "ITEM_RUSTY_BLADE",
          isNew: false,
          quantity: 1,
        };
        hubStash.addItem(itemInstance);
        await marketManager.sellItem(itemInstance.uid);
      }

      expect(marketManager.marketState.buyback.length).to.equal(10);
    });

    it("should remove oldest item when limit exceeded", async () => {
      // Sell 10 items first
      const firstItemUid = "ITEM_TEST_FIRST";
      hubStash.addItem({
        uid: firstItemUid,
        defId: "ITEM_RUSTY_BLADE",
        isNew: false,
        quantity: 1,
      });
      await marketManager.sellItem(firstItemUid);

      const firstBuybackId = marketManager.marketState.buyback[0].id;

      // Sell 10 more items
      for (let i = 0; i < 10; i++) {
        const itemInstance = {
          uid: `ITEM_TEST_${i}`,
          defId: "ITEM_RUSTY_BLADE",
          isNew: false,
          quantity: 1,
        };
        hubStash.addItem(itemInstance);
        await marketManager.sellItem(itemInstance.uid);
      }

      // First item should be removed
      const buybackIds = marketManager.marketState.buyback.map(
        (item) => item.id
      );
      expect(buybackIds).to.not.include(firstBuybackId);
      expect(marketManager.marketState.buyback.length).to.equal(10);
    });
  });

  describe("Merchant Filtering", () => {
    beforeEach(async () => {
      await marketManager.init();
      await marketManager.generateStock(1);
    });

    it("should filter stock by merchant type", () => {
      const smithStock = marketManager.getStockForMerchant("SMITH");
      smithStock.forEach((item) => {
        expect(["WEAPON", "ARMOR"]).to.include(item.type);
      });

      const buybackStock = marketManager.getStockForMerchant("BUYBACK");
      expect(buybackStock).to.deep.equal(marketManager.marketState.buyback);
    });
  });

  describe("State Management", () => {
    it("should return current state", async () => {
      await marketManager.init();
      const state = marketManager.getState();

      expect(state).to.exist;
      expect(state).to.have.property("generationId");
      expect(state).to.have.property("stock");
      expect(state).to.have.property("buyback");
    });

    it("should clean up event listeners on destroy", () => {
      const removeSpy = sinon.spy(window, "removeEventListener");

      marketManager.destroy();

      expect(removeSpy.called).to.be.true;

      removeSpy.restore();
    });
  });
});

