import { describe, test, expect, mock, beforeEach } from "bun:test";
import { buildMockCatalogItem, buildMockConstants } from "../setup";

// ---------------------------------------------------------------------------
// Stable mock factory
// ---------------------------------------------------------------------------

function createStablePrismaMock(
  overrides: Record<string, Record<string, any>> = {}
) {
  return new Proxy(
    {},
    {
      get(_target, model: string) {
        if (model === "$connect" || model === "$disconnect")
          return mock(() => Promise.resolve());
        if (overrides[model]) return overrides[model];
        return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) });
      },
    }
  );
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("procurement manager", () => {
  beforeEach(() => {
    mock.restore();
  });

  // -------------------------------------------------------------------
  // fetchItem
  // -------------------------------------------------------------------
  describe("fetchItem()", () => {
    test("returns CatalogItemController by internal ID", async () => {
      const mockData = { ...buildMockCatalogItem(), linkedItems: [] };
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findFirst: mock(() => Promise.resolve(mockData)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.fetchItem("cat-1");
      expect(result).toBeDefined();
      expect(result.id).toBe("cat-1");
    });

    test("looks up by cwCatalogId for numeric identifiers", async () => {
      const mockData = { ...buildMockCatalogItem(), linkedItems: [] };
      const findFirst = mock(() => Promise.resolve(mockData));
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: { findFirst },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      await procurement.fetchItem(500);
      const where = findFirst.mock.calls[0]?.[0]?.where;
      expect(where).toHaveProperty("cwCatalogId", 500);
    });

    test("throws 404 when not found", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      try {
        await procurement.fetchItem("nonexistent");
        expect(true).toBe(false);
      } catch (e: any) {
        expect(e.name).toBe("CatalogItemNotFound");
        expect(e.status).toBe(404);
      }
    });
  });

  // -------------------------------------------------------------------
  // fetchPages
  // -------------------------------------------------------------------
  describe("fetchPages()", () => {
    test("returns paginated catalog items", async () => {
      const items = [
        { ...buildMockCatalogItem(), linkedItems: [] },
        { ...buildMockCatalogItem({ id: "cat-2" }), linkedItems: [] },
      ];
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany: mock(() => Promise.resolve(items)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.fetchPages(1, 10);
      expect(result).toBeArrayOfSize(2);
    });

    test("clamps page to minimum 1", async () => {
      const findMany = mock(() => Promise.resolve([]));
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany,
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      await procurement.fetchPages(0, 10);
      const opts = findMany.mock.calls[0]?.[0];
      expect(opts.skip).toBe(0); // (max(0,1)-1) * 10 = 0
    });

    test("falls back to safe pagination when page/rpp are invalid", async () => {
      const findMany = mock(() => Promise.resolve([]));
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany,
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      await procurement.fetchPages(Number.NaN, Number.NaN);
      const opts = findMany.mock.calls[0]?.[0];
      expect(opts.skip).toBe(0);
      expect(opts.take).toBe(30);
    });

    test("uses relational manufacturer filter when manufacturer option is provided", async () => {
      const findMany = mock(() => Promise.resolve([]));
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany,
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      await procurement.fetchPages(1, 20, { manufacturer: "Ubiquiti" });

      const where = findMany.mock.calls[0]?.[0]?.where;
      expect(JSON.stringify(where)).toContain(
        '"manufacturer":{"is":{"name":{"contains":"Ubiquiti"'
      );
    });
  });

  // -------------------------------------------------------------------
  // search
  // -------------------------------------------------------------------
  describe("search()", () => {
    test("returns matching catalog items", async () => {
      const items = [{ ...buildMockCatalogItem(), linkedItems: [] }];
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany: mock(() => Promise.resolve(items)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.search("switch", 1, 10);
      expect(result).toBeArrayOfSize(1);
    });

    test("ignores invalid numeric filters instead of passing NaN to Prisma", async () => {
      const findMany = mock(() => Promise.resolve([]));
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany,
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      await procurement.search("switch", Number.NaN, Number.NaN, {
        minPrice: Number.NaN,
        maxPrice: Number.NaN,
      });

      const where = findMany.mock.calls[0]?.[0]?.where;
      expect(where).toHaveProperty("OR");
      expect(JSON.stringify(where)).not.toContain("NaN");
    });

    test("uses relational manufacturer search clause", async () => {
      const findMany = mock(() => Promise.resolve([]));
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany,
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      await procurement.search("wir", 1, 20);

      const where = findMany.mock.calls[0]?.[0]?.where;
      expect(JSON.stringify(where)).toContain(
        '"manufacturer":{"is":{"name":{"contains":"wir"'
      );
    });
  });

  // -------------------------------------------------------------------
  // count
  // -------------------------------------------------------------------
  describe("count()", () => {
    test("returns total count", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              count: mock(() => Promise.resolve(50)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.count();
      expect(result).toBe(50);
    });
  });

  // -------------------------------------------------------------------
  // countSearch
  // -------------------------------------------------------------------
  describe("countSearch()", () => {
    test("returns count of matching items", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              count: mock(() => Promise.resolve(12)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.countSearch("switch");
      expect(result).toBe(12);
    });
  });

  // -------------------------------------------------------------------
  // fetchDistinctValues
  // -------------------------------------------------------------------
  describe("fetchDistinctValues()", () => {
    test("returns sorted distinct category values", async () => {
      const items = [{ category: "Technology" }, { category: "Accessories" }];
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany: mock(() => Promise.resolve(items)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.fetchDistinctValues("category");
      expect(result).toEqual(["Technology", "Accessories"]);
    });

    test("filters out null values", async () => {
      const items = [{ manufacturer: "Ubiquiti" }, { manufacturer: null }];
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findMany: mock(() => Promise.resolve(items)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      const result = await procurement.fetchDistinctValues("manufacturer");
      expect(result).toEqual(["Ubiquiti"]);
    });
  });

  // -------------------------------------------------------------------
  // fetchLaborCatalogItems
  // -------------------------------------------------------------------
  describe("fetchLaborCatalogItems()", () => {
    test("throws when labor items not found", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            catalogItem: {
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        })
      );

      const { procurement } = await import("../../src/managers/procurement");
      try {
        await procurement.fetchLaborCatalogItems();
        expect(true).toBe(false);
      } catch (e: any) {
        expect(e.name).toBe("LaborCatalogProductsNotFound");
        expect(e.status).toBe(500);
      }
    });
  });
});
