import { describe, test, expect } from "bun:test";
import { ForecastProductController } from "../../../src/controllers/ForecastProductController";
import { buildMockCWForecastItem } from "../../setup";

describe("ForecastProductController", () => {
  // -------------------------------------------------------------------
  // Constructor
  // -------------------------------------------------------------------
  describe("constructor", () => {
    test("sets core identification fields", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.cwForecastId).toBe(7001);
      expect(ctrl.forecastDescription).toBe("Network Switch");
    });

    test("maps opportunity reference", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.opportunityCwId).toBe(1001);
      expect(ctrl.opportunityName).toBe("Test Opportunity");
    });

    test("maps quantity", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.quantity).toBe(5);
    });

    test("maps status reference", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.statusCwId).toBe(1);
      expect(ctrl.statusName).toBe("Won");
    });

    test("maps catalogItem reference", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.catalogItemCwId).toBe(500);
      expect(ctrl.catalogItemIdentifier).toBe("USW-Pro-24");
    });

    test("maps product details", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.productDescription).toBe("UniFi Switch Pro 24");
      expect(ctrl.productClass).toBe("Product");
      expect(ctrl.forecastType).toBe("Product");
    });

    test("maps financials", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.revenue).toBe(2500.0);
      expect(ctrl.cost).toBe(1800.0);
      expect(ctrl.margin).toBe(700.0);
      expect(ctrl.percentage).toBe(100);
    });

    test("maps boolean flags", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.includeFlag).toBe(true);
      expect(ctrl.linkFlag).toBe(false);
      expect(ctrl.recurringFlag).toBe(false);
      expect(ctrl.taxableFlag).toBe(true);
    });

    test("maps sequence and sub number", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.sequenceNumber).toBe(1);
      expect(ctrl.subNumber).toBe(0);
    });

    test("maps recurring fields", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.recurringRevenue).toBe(0);
      expect(ctrl.recurringCost).toBe(0);
      expect(ctrl.cycles).toBe(0);
    });

    test("sets cancellation defaults", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.cancelledFlag).toBe(false);
      expect(ctrl.quantityCancelled).toBe(0);
      expect(ctrl.cancelledReason).toBeNull();
      expect(ctrl.cancelledBy).toBeNull();
      expect(ctrl.cancelledDate).toBeNull();
    });

    test("sets inventory defaults", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.onHand).toBeNull();
      expect(ctrl.inStock).toBeNull();
    });

    test("maps _info to cwLastUpdated", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.cwLastUpdated).toBeInstanceOf(Date);
      expect(ctrl.cwUpdatedBy).toBe("jroberts");
    });

    test("handles missing optional fields", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({
          opportunity: undefined,
          status: undefined,
          catalogItem: undefined,
          _info: undefined,
        }),
      );
      expect(ctrl.opportunityCwId).toBeNull();
      expect(ctrl.statusCwId).toBeNull();
      expect(ctrl.catalogItemCwId).toBeNull();
      expect(ctrl.cwLastUpdated).toBeNull();
    });
  });

  // -------------------------------------------------------------------
  // applyCancellationData
  // -------------------------------------------------------------------
  describe("applyCancellationData()", () => {
    test("applies cancellation data", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyCancellationData({
        cancelledFlag: true,
        quantityCancelled: 3,
        cancelledReason: "Out of stock",
        cancelledBy: 42,
        cancelledDate: "2026-02-20T00:00:00Z",
      });
      expect(ctrl.cancelledFlag).toBe(true);
      expect(ctrl.quantityCancelled).toBe(3);
      expect(ctrl.cancelledReason).toBe("Out of stock");
      expect(ctrl.cancelledBy).toBe(42);
      expect(ctrl.cancelledDate).toBeInstanceOf(Date);
    });

    test("handles partial cancellation data", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyCancellationData({});
      expect(ctrl.cancelledFlag).toBe(false);
      expect(ctrl.quantityCancelled).toBe(0);
      expect(ctrl.cancelledReason).toBeNull();
    });
  });

  // -------------------------------------------------------------------
  // applyProcurementCustomFields
  // -------------------------------------------------------------------
  describe("applyProcurementCustomFields()", () => {
    test("sets productNarrative from custom field id 46", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyProcurementCustomFields({
        customFields: [{ id: 46, value: "Custom narrative text" }],
      });
      expect(ctrl.productNarrative).toBe("Custom narrative text");
    });

    test("does not overwrite productNarrative when field 46 is missing", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.productNarrative = "existing";
      ctrl.applyProcurementCustomFields({
        customFields: [{ id: 99, value: "other" }],
      });
      expect(ctrl.productNarrative).toBe("existing");
    });

    test("handles empty customFields array", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyProcurementCustomFields({ customFields: [] });
      expect(ctrl.productNarrative).toBeNull();
    });

    test("handles undefined customFields", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyProcurementCustomFields({});
      expect(ctrl.productNarrative).toBeNull();
    });
  });

  // -------------------------------------------------------------------
  // applyInventoryData
  // -------------------------------------------------------------------
  describe("applyInventoryData()", () => {
    test("sets onHand and inStock true when quantity > 0", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyInventoryData({ onHand: 10 });
      expect(ctrl.onHand).toBe(10);
      expect(ctrl.inStock).toBe(true);
    });

    test("sets inStock false when onHand is 0", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyInventoryData({ onHand: 0 });
      expect(ctrl.onHand).toBe(0);
      expect(ctrl.inStock).toBe(false);
    });
  });

  // -------------------------------------------------------------------
  // Computed properties
  // -------------------------------------------------------------------
  describe("computed properties", () => {
    test("profit returns revenue - cost", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.profit).toBe(700.0);
    });

    test("cancelled returns false by default", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.cancelled).toBe(false);
    });

    test("cancelled returns true after applyCancellationData", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 1 });
      expect(ctrl.cancelled).toBe(true);
    });

    test("cancellationType returns null when not cancelled", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      expect(ctrl.cancellationType).toBeNull();
    });

    test("cancellationType returns 'full' when all units cancelled", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5 }),
      );
      ctrl.applyCancellationData({
        cancelledFlag: true,
        quantityCancelled: 5,
      });
      expect(ctrl.cancellationType).toBe("full");
    });

    test("cancellationType returns 'partial' when some units cancelled", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5 }),
      );
      ctrl.applyCancellationData({
        cancelledFlag: true,
        quantityCancelled: 2,
      });
      expect(ctrl.cancellationType).toBe("partial");
    });

    test("effectiveQuantity returns full quantity when not cancelled", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5 }),
      );
      expect(ctrl.effectiveQuantity).toBe(5);
    });

    test("effectiveQuantity returns reduced quantity for partial cancel", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5 }),
      );
      ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 2 });
      expect(ctrl.effectiveQuantity).toBe(3);
    });

    test("effectiveQuantity returns 0 for full cancellation", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5 }),
      );
      ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 5 });
      expect(ctrl.effectiveQuantity).toBe(0);
    });

    test("effectiveRevenue returns full revenue when not cancelled", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5, revenue: 2500 }),
      );
      expect(ctrl.effectiveRevenue).toBe(2500);
    });

    test("effectiveRevenue returns proportional revenue for partial cancel", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5, revenue: 2500 }),
      );
      ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 2 });
      expect(ctrl.effectiveRevenue).toBe(1500);
    });

    test("effectiveRevenue returns 0 for full cancellation", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5, revenue: 2500 }),
      );
      ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 5 });
      expect(ctrl.effectiveRevenue).toBe(0);
    });

    test("effectiveCost returns full cost when not cancelled", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5, cost: 1800 }),
      );
      expect(ctrl.effectiveCost).toBe(1800);
    });

    test("effectiveCost returns 0 for full cancellation", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5, cost: 1800 }),
      );
      ctrl.applyCancellationData({ cancelledFlag: true, quantityCancelled: 5 });
      expect(ctrl.effectiveCost).toBe(0);
    });
  });

  // -------------------------------------------------------------------
  // toJson
  // -------------------------------------------------------------------
  describe("toJson()", () => {
    test("returns id as cwForecastId", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.id).toBe(7001);
    });

    test("returns financial fields", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.revenue).toBe(2500.0);
      expect(json.cost).toBe(1800.0);
      expect(json.margin).toBe(700.0);
      expect(json.profit).toBe(700.0);
    });

    test("returns cancellation info", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.cancelled).toBe(false);
      expect(json.cancellationType).toBeNull();
    });

    test("returns status as reference object", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.status).toEqual({ id: 1, name: "Won" });
    });

    test("returns catalogItem as reference object", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.catalogItem).toEqual({
        id: 500,
        identifier: "USW-Pro-24",
      });
    });

    test("returns opportunity as reference object", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.opportunity).toEqual({
        id: 1001,
        name: "Test Opportunity",
      });
    });

    test("includes inventory data", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      ctrl.applyInventoryData({ onHand: 10 });
      const json = ctrl.toJson();
      expect(json.onHand).toBe(10);
      expect(json.inStock).toBe(true);
    });

    test("includes boolean flags", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.includeFlag).toBe(true);
      expect(json.linkFlag).toBe(false);
      expect(json.recurringFlag).toBe(false);
      expect(json.taxableFlag).toBe(true);
    });

    test("includes sequence and timing info", () => {
      const ctrl = new ForecastProductController(buildMockCWForecastItem());
      const json = ctrl.toJson();
      expect(json.sequenceNumber).toBe(1);
      expect(json.subNumber).toBe(0);
      expect(json.cwLastUpdated).toBeInstanceOf(Date);
    });

    test("includes customerDescription and productNarrative", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ customerDescription: "Customer desc" }),
      );
      const json = ctrl.toJson();
      expect(json.customerDescription).toBe("Customer desc");
      expect(json.productNarrative).toBeNull();
    });

    test("includes effective* computed fields", () => {
      const ctrl = new ForecastProductController(
        buildMockCWForecastItem({ quantity: 5, revenue: 2500, cost: 1800 }),
      );
      const json = ctrl.toJson();
      expect(json.effectiveQuantity).toBe(5);
      expect(json.effectiveRevenue).toBe(2500);
      expect(json.effectiveCost).toBe(1800);
    });
  });
});
