import { describe, test, expect } from "bun:test";
import {
  computeProductsCacheTTL,
  PRODUCTS_TTL_HOT,
  PRODUCTS_TTL_LAZY,
  WON_LOST_STATUS_IDS,
} from "../../src/modules/algorithms/computeProductsCacheTTL";

const NOW = new Date("2026-03-02T12:00:00Z");
const DAY_MS = 24 * 60 * 60 * 1000;

describe("computeProductsCacheTTL", () => {
  // -- Constants ----------------------------------------------------------
  test("PRODUCTS_TTL_HOT is 45 seconds", () => {
    expect(PRODUCTS_TTL_HOT).toBe(45_000);
  });

  test("PRODUCTS_TTL_LAZY is 20 minutes", () => {
    expect(PRODUCTS_TTL_LAZY).toBe(1_200_000);
  });

  // -- Won/Lost status set ------------------------------------------------
  test("WON_LOST_STATUS_IDS contains Won canonical ID (29)", () => {
    expect(WON_LOST_STATUS_IDS.has(29)).toBe(true);
  });

  test("WON_LOST_STATUS_IDS contains Lost canonical ID (53) and Canceled (59)", () => {
    expect(WON_LOST_STATUS_IDS.has(53)).toBe(true);
    expect(WON_LOST_STATUS_IDS.has(59)).toBe(true);
  });

  test("WON_LOST_STATUS_IDS does not contain Pending Won (49) or Pending Lost (50)", () => {
    // Pending Won/Lost do not have wonFlag/lostFlag set in QuoteStatuses
    expect(WON_LOST_STATUS_IDS.has(49)).toBe(false);
    expect(WON_LOST_STATUS_IDS.has(50)).toBe(false);
  });

  test("WON_LOST_STATUS_IDS does not contain Active (58) or New (24)", () => {
    expect(WON_LOST_STATUS_IDS.has(58)).toBe(false);
    expect(WON_LOST_STATUS_IDS.has(24)).toBe(false);
  });

  // -- Rule 1: Won/Lost/Pending Won/Lost → null --------------------------
  test("returns null for Won status (CW ID 29)", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 29,
      closedFlag: true,
      closedDate: new Date(NOW.getTime() - 2 * DAY_MS),
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS),
      now: NOW,
    });
    expect(result).toBeNull();
  });

  test("returns PRODUCTS_TTL_HOT for Pending Won status (CW ID 49) with recent activity", () => {
    // Pending Won is not in WON_LOST_STATUS_IDS (no wonFlag), so it falls
    // through to the activity-based rules.
    const result = computeProductsCacheTTL({
      statusCwId: 49,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_HOT);
  });

  test("returns null for Lost status (CW ID 53)", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 53,
      closedFlag: true,
      closedDate: new Date(NOW.getTime() - 5 * DAY_MS),
      expectedCloseDate: null,
      lastUpdated: null,
      now: NOW,
    });
    expect(result).toBeNull();
  });

  test("returns PRODUCTS_TTL_HOT for Pending Lost status (CW ID 50) with recent activity", () => {
    // Pending Lost is not in WON_LOST_STATUS_IDS (no lostFlag), so it falls
    // through to the activity-based rules.
    const result = computeProductsCacheTTL({
      statusCwId: 50,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_HOT);
  });

  // -- Rule 2: Opp not cacheable → null ----------------------------------
  test("returns null when opp is closed > 30 days (main cache null)", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 58, // Active — but closed flag overrides
      closedFlag: true,
      closedDate: new Date(NOW.getTime() - 60 * DAY_MS),
      expectedCloseDate: null,
      lastUpdated: null,
      now: NOW,
    });
    expect(result).toBeNull();
  });

  // -- Rule 3: Updated within 3 days → 15s -------------------------------
  test("returns PRODUCTS_TTL_HOT when lastUpdated is within 3 days", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 58,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_HOT);
  });

  test("returns PRODUCTS_TTL_HOT when lastUpdated is exactly 3 days ago", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 58,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 3 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_HOT);
  });

  // -- Rule 4: Everything else → 30 min ----------------------------------
  test("returns PRODUCTS_TTL_LAZY when lastUpdated is > 3 days ago", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 58,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 10 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_LAZY);
  });

  test("returns PRODUCTS_TTL_LAZY when no lastUpdated is set", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 58,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: null,
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_LAZY);
  });

  test("returns PRODUCTS_TTL_LAZY for recently-closed (within 30 days) non-won/lost", () => {
    // Edge case: closedFlag true, but status is not Won/Lost (unusual but possible)
    const result = computeProductsCacheTTL({
      statusCwId: 56, // Internal Review (not won/lost)
      closedFlag: true,
      closedDate: new Date(NOW.getTime() - 10 * DAY_MS),
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 20 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_LAZY);
  });

  // -- Rule priority: Won/Lost takes priority over recent activity --------
  test("Won status takes priority even with very recent lastUpdated", () => {
    const result = computeProductsCacheTTL({
      statusCwId: 29,
      closedFlag: true,
      closedDate: new Date(NOW.getTime() - 1 * DAY_MS),
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 1000), // 1 second ago
      now: NOW,
    });
    expect(result).toBeNull();
  });

  // -- Null statusCwId (should not skip rule 1) ---------------------------
  test("null statusCwId falls through to other rules", () => {
    const result = computeProductsCacheTTL({
      statusCwId: null,
      closedFlag: false,
      closedDate: null,
      expectedCloseDate: null,
      lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS),
      now: NOW,
    });
    expect(result).toBe(PRODUCTS_TTL_HOT);
  });
});
