import { describe, expect, it } from "vitest";
import { BlueprintTarget, FieldType } from "@nexus/shared";
import {
  type CatalogCategory,
  type CatalogField,
  PROJECT_CATEGORIES,
  PROJECT_FIELD_CATALOG,
  RESOURCE_CATEGORIES,
  RESOURCE_FIELD_CATALOG,
  findCatalogField,
  getCatalogForTarget,
  getCategoriesForTarget,
} from "./blueprint-field-catalog.js";

// ---------------------------------------------------------------------------
// CatalogField shape
// ---------------------------------------------------------------------------

describe("CatalogField interface", () => {
  it("every PROJECT_FIELD_CATALOG entry has required fields", () => {
    for (const field of PROJECT_FIELD_CATALOG) {
      expect(field.key).toBeTypeOf("string");
      expect(field.key.length).toBeGreaterThan(0);
      expect(field.label).toBeTypeOf("string");
      expect(field.label.length).toBeGreaterThan(0);
      expect(field.type).toBeTypeOf("string");
      expect(field.category).toBeTypeOf("string");
      expect(field.description).toBeTypeOf("string");
      expect(field.builtIn).toBeTypeOf("boolean");
    }
  });

  it("every RESOURCE_FIELD_CATALOG entry has required fields", () => {
    for (const field of RESOURCE_FIELD_CATALOG) {
      expect(field.key).toBeTypeOf("string");
      expect(field.key.length).toBeGreaterThan(0);
      expect(field.label).toBeTypeOf("string");
      expect(field.label.length).toBeGreaterThan(0);
      expect(field.type).toBeTypeOf("string");
      expect(field.category).toBeTypeOf("string");
      expect(field.description).toBeTypeOf("string");
      expect(field.builtIn).toBeTypeOf("boolean");
    }
  });

  it("all fields in both catalogs have builtIn === false", () => {
    const all = [...PROJECT_FIELD_CATALOG, ...RESOURCE_FIELD_CATALOG];
    for (const field of all) {
      expect(field.builtIn).toBe(false);
    }
  });
});

// ---------------------------------------------------------------------------
// Field type coverage
// ---------------------------------------------------------------------------

describe("FieldType usage in PROJECT_FIELD_CATALOG", () => {
  const fieldsByType = (type: FieldType) => PROJECT_FIELD_CATALOG.filter((f) => f.type === type);

  it("contains TEXT fields", () => {
    expect(fieldsByType(FieldType.TEXT).length).toBeGreaterThan(0);
  });

  it("contains NUMBER fields", () => {
    expect(fieldsByType(FieldType.NUMBER).length).toBeGreaterThan(0);
  });

  it("contains SELECT fields", () => {
    expect(fieldsByType(FieldType.SELECT).length).toBeGreaterThan(0);
  });

  it("contains MULTI_SELECT fields", () => {
    expect(fieldsByType(FieldType.MULTI_SELECT).length).toBeGreaterThan(0);
  });

  it("contains DATE fields", () => {
    expect(fieldsByType(FieldType.DATE).length).toBeGreaterThan(0);
  });

  it("contains BOOLEAN fields", () => {
    expect(fieldsByType(FieldType.BOOLEAN).length).toBeGreaterThan(0);
  });

  it("contains URL fields", () => {
    expect(fieldsByType(FieldType.URL).length).toBeGreaterThan(0);
  });
});

describe("FieldType usage in RESOURCE_FIELD_CATALOG", () => {
  const fieldsByType = (type: FieldType) => RESOURCE_FIELD_CATALOG.filter((f) => f.type === type);

  it("contains TEXT fields", () => {
    expect(fieldsByType(FieldType.TEXT).length).toBeGreaterThan(0);
  });

  it("contains NUMBER fields", () => {
    expect(fieldsByType(FieldType.NUMBER).length).toBeGreaterThan(0);
  });

  it("contains SELECT fields", () => {
    expect(fieldsByType(FieldType.SELECT).length).toBeGreaterThan(0);
  });

  it("contains MULTI_SELECT fields", () => {
    expect(fieldsByType(FieldType.MULTI_SELECT).length).toBeGreaterThan(0);
  });

  it("contains DATE fields", () => {
    expect(fieldsByType(FieldType.DATE).length).toBeGreaterThan(0);
  });

  it("contains BOOLEAN fields", () => {
    expect(fieldsByType(FieldType.BOOLEAN).length).toBeGreaterThan(0);
  });

  it("contains EMAIL fields", () => {
    expect(fieldsByType(FieldType.EMAIL).length).toBeGreaterThan(0);
  });

  it("contains URL fields", () => {
    expect(fieldsByType(FieldType.URL).length).toBeGreaterThan(0);
  });
});

// ---------------------------------------------------------------------------
// Options on SELECT / MULTI_SELECT fields
// ---------------------------------------------------------------------------

describe("SELECT and MULTI_SELECT fields have options", () => {
  const selectTypes = new Set([FieldType.SELECT, FieldType.MULTI_SELECT]);

  it("every SELECT/MULTI_SELECT in PROJECT_FIELD_CATALOG has at least one option", () => {
    const selectFields = PROJECT_FIELD_CATALOG.filter((f) => selectTypes.has(f.type));
    expect(selectFields.length).toBeGreaterThan(0);
    for (const field of selectFields) {
      expect(Array.isArray(field.options)).toBe(true);
      expect(field.options!.length).toBeGreaterThan(0);
    }
  });

  it("every SELECT/MULTI_SELECT in RESOURCE_FIELD_CATALOG has at least one option", () => {
    const selectFields = RESOURCE_FIELD_CATALOG.filter((f) => selectTypes.has(f.type));
    expect(selectFields.length).toBeGreaterThan(0);
    for (const field of selectFields) {
      expect(Array.isArray(field.options)).toBe(true);
      expect(field.options!.length).toBeGreaterThan(0);
    }
  });

  it("each option has non-empty value and label strings", () => {
    const all = [...PROJECT_FIELD_CATALOG, ...RESOURCE_FIELD_CATALOG];
    for (const field of all) {
      for (const opt of field.options ?? []) {
        expect(opt.value).toBeTypeOf("string");
        expect(opt.value.length).toBeGreaterThan(0);
        expect(opt.label).toBeTypeOf("string");
        expect(opt.label.length).toBeGreaterThan(0);
      }
    }
  });

  it("non-SELECT/MULTI_SELECT fields do not declare options", () => {
    const nonSelect = [...PROJECT_FIELD_CATALOG, ...RESOURCE_FIELD_CATALOG].filter(
      (f) => !selectTypes.has(f.type),
    );
    for (const field of nonSelect) {
      expect(field.options).toBeUndefined();
    }
  });
});

// ---------------------------------------------------------------------------
// defaultValue presence
// ---------------------------------------------------------------------------

describe("defaultValue on catalog fields", () => {
  it("clientApprovalRounds has defaultValue 2", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "clientApprovalRounds");
    expect(field).toBeDefined();
    expect(field!.defaultValue).toBe(2);
  });

  it("nda has defaultValue false", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "nda");
    expect(field).toBeDefined();
    expect(field!.defaultValue).toBe(false);
  });

  it("weeklyHours has defaultValue 40", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "weeklyHours");
    expect(field).toBeDefined();
    expect(field!.defaultValue).toBe(40);
  });

  it("remoteEligible has defaultValue false", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "remoteEligible");
    expect(field).toBeDefined();
    expect(field!.defaultValue).toBe(false);
  });

  it("fields without explicit defaultValue have undefined defaultValue", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "clientUnit");
    expect(field).toBeDefined();
    expect(field!.defaultValue).toBeUndefined();
  });
});

// ---------------------------------------------------------------------------
// Unique keys within each catalog
// ---------------------------------------------------------------------------

describe("catalog key uniqueness", () => {
  it("PROJECT_FIELD_CATALOG has no duplicate keys", () => {
    const keys = PROJECT_FIELD_CATALOG.map((f) => f.key);
    const unique = new Set(keys);
    expect(unique.size).toBe(keys.length);
  });

  it("RESOURCE_FIELD_CATALOG has no duplicate keys", () => {
    const keys = RESOURCE_FIELD_CATALOG.map((f) => f.key);
    const unique = new Set(keys);
    expect(unique.size).toBe(keys.length);
  });
});

// ---------------------------------------------------------------------------
// Category alignment
// ---------------------------------------------------------------------------

describe("catalog field categories match declared category lists", () => {
  it("every PROJECT_FIELD_CATALOG category exists in PROJECT_CATEGORIES", () => {
    const validNames = new Set(PROJECT_CATEGORIES.map((c) => c.name));
    for (const field of PROJECT_FIELD_CATALOG) {
      expect(validNames.has(field.category)).toBe(true);
    }
  });

  it("every RESOURCE_FIELD_CATALOG category exists in RESOURCE_CATEGORIES", () => {
    const validNames = new Set(RESOURCE_CATEGORIES.map((c) => c.name));
    for (const field of RESOURCE_FIELD_CATALOG) {
      expect(validNames.has(field.category)).toBe(true);
    }
  });

  it("each project category is used by at least one field", () => {
    const usedCategories = new Set(PROJECT_FIELD_CATALOG.map((f) => f.category));
    for (const cat of PROJECT_CATEGORIES) {
      expect(usedCategories.has(cat.name)).toBe(true);
    }
  });

  it("each resource category is used by at least one field", () => {
    const usedCategories = new Set(RESOURCE_FIELD_CATALOG.map((f) => f.category));
    for (const cat of RESOURCE_CATEGORIES) {
      expect(usedCategories.has(cat.name)).toBe(true);
    }
  });
});

// ---------------------------------------------------------------------------
// CatalogCategory shape
// ---------------------------------------------------------------------------

describe("CatalogCategory shape", () => {
  it("PROJECT_CATEGORIES entries have non-empty name and description", () => {
    expect(PROJECT_CATEGORIES.length).toBeGreaterThan(0);
    for (const cat of PROJECT_CATEGORIES) {
      expect(cat.name).toBeTypeOf("string");
      expect(cat.name.length).toBeGreaterThan(0);
      expect(cat.description).toBeTypeOf("string");
      expect(cat.description.length).toBeGreaterThan(0);
    }
  });

  it("RESOURCE_CATEGORIES entries have non-empty name and description", () => {
    expect(RESOURCE_CATEGORIES.length).toBeGreaterThan(0);
    for (const cat of RESOURCE_CATEGORIES) {
      expect(cat.name).toBeTypeOf("string");
      expect(cat.name.length).toBeGreaterThan(0);
      expect(cat.description).toBeTypeOf("string");
      expect(cat.description.length).toBeGreaterThan(0);
    }
  });
});

// ---------------------------------------------------------------------------
// getCatalogForTarget
// ---------------------------------------------------------------------------

describe("getCatalogForTarget", () => {
  it("returns PROJECT_FIELD_CATALOG for BlueprintTarget.PROJECT", () => {
    expect(getCatalogForTarget(BlueprintTarget.PROJECT)).toBe(PROJECT_FIELD_CATALOG);
  });

  it("returns RESOURCE_FIELD_CATALOG for BlueprintTarget.RESOURCE", () => {
    expect(getCatalogForTarget(BlueprintTarget.RESOURCE)).toBe(RESOURCE_FIELD_CATALOG);
  });

  it("returns PROJECT_FIELD_CATALOG for the string literal 'PROJECT'", () => {
    expect(getCatalogForTarget("PROJECT")).toBe(PROJECT_FIELD_CATALOG);
  });

  it("returns RESOURCE_FIELD_CATALOG for any other string (fallback)", () => {
    expect(getCatalogForTarget("RESOURCE")).toBe(RESOURCE_FIELD_CATALOG);
  });

  it("returns RESOURCE_FIELD_CATALOG for an unrecognised string", () => {
    // The implementation falls back to RESOURCE catalog for non-PROJECT targets.
    expect(getCatalogForTarget("unknown")).toBe(RESOURCE_FIELD_CATALOG);
  });

  it("returns an array", () => {
    expect(Array.isArray(getCatalogForTarget(BlueprintTarget.PROJECT))).toBe(true);
    expect(Array.isArray(getCatalogForTarget(BlueprintTarget.RESOURCE))).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// getCategoriesForTarget
// ---------------------------------------------------------------------------

describe("getCategoriesForTarget", () => {
  it("returns PROJECT_CATEGORIES for BlueprintTarget.PROJECT", () => {
    expect(getCategoriesForTarget(BlueprintTarget.PROJECT)).toBe(PROJECT_CATEGORIES);
  });

  it("returns RESOURCE_CATEGORIES for BlueprintTarget.RESOURCE", () => {
    expect(getCategoriesForTarget(BlueprintTarget.RESOURCE)).toBe(RESOURCE_CATEGORIES);
  });

  it("returns PROJECT_CATEGORIES for the string literal 'PROJECT'", () => {
    expect(getCategoriesForTarget("PROJECT")).toBe(PROJECT_CATEGORIES);
  });

  it("returns RESOURCE_CATEGORIES for any other string (fallback)", () => {
    expect(getCategoriesForTarget("RESOURCE")).toBe(RESOURCE_CATEGORIES);
  });

  it("returns RESOURCE_CATEGORIES for an unrecognised string", () => {
    expect(getCategoriesForTarget("unknown")).toBe(RESOURCE_CATEGORIES);
  });

  it("returns an array", () => {
    expect(Array.isArray(getCategoriesForTarget(BlueprintTarget.PROJECT))).toBe(true);
    expect(Array.isArray(getCategoriesForTarget(BlueprintTarget.RESOURCE))).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// findCatalogField
// ---------------------------------------------------------------------------

describe("findCatalogField", () => {
  // --- project fields ---
  it("finds a known project field by key", () => {
    const field = findCatalogField(BlueprintTarget.PROJECT, "billingModel");
    expect(field).toBeDefined();
    expect(field!.key).toBe("billingModel");
    expect(field!.type).toBe(FieldType.SELECT);
    expect(field!.category).toBe("Client & Billing");
  });

  it("finds 'deliveryFormat' as MULTI_SELECT in the project catalog", () => {
    const field = findCatalogField(BlueprintTarget.PROJECT, "deliveryFormat");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.MULTI_SELECT);
  });

  it("finds 'deliveryDate' as DATE in the project catalog", () => {
    const field = findCatalogField(BlueprintTarget.PROJECT, "deliveryDate");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.DATE);
  });

  it("finds 'nda' as BOOLEAN in the project catalog", () => {
    const field = findCatalogField(BlueprintTarget.PROJECT, "nda");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.BOOLEAN);
  });

  it("finds 'projectBrief' as URL in the project catalog", () => {
    const field = findCatalogField(BlueprintTarget.PROJECT, "projectBrief");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.URL);
  });

  // --- resource fields ---
  it("finds a known resource field by key", () => {
    const field = findCatalogField(BlueprintTarget.RESOURCE, "contractType");
    expect(field).toBeDefined();
    expect(field!.key).toBe("contractType");
    expect(field!.type).toBe(FieldType.SELECT);
    expect(field!.category).toBe("Contract");
  });

  it("finds 'personalEmail' as EMAIL in the resource catalog", () => {
    const field = findCatalogField(BlueprintTarget.RESOURCE, "personalEmail");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.EMAIL);
  });

  it("finds 'linkedInUrl' as URL in the resource catalog", () => {
    const field = findCatalogField(BlueprintTarget.RESOURCE, "linkedInUrl");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.URL);
  });

  it("finds 'primarySoftware' as MULTI_SELECT in the resource catalog", () => {
    const field = findCatalogField(BlueprintTarget.RESOURCE, "primarySoftware");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.MULTI_SELECT);
  });

  it("finds 'remoteEligible' as BOOLEAN in the resource catalog", () => {
    const field = findCatalogField(BlueprintTarget.RESOURCE, "remoteEligible");
    expect(field).toBeDefined();
    expect(field!.type).toBe(FieldType.BOOLEAN);
  });

  // --- string target variants ---
  it("finds a project field when target is the string 'PROJECT'", () => {
    const field = findCatalogField("PROJECT", "shotCount");
    expect(field).toBeDefined();
    expect(field!.key).toBe("shotCount");
  });

  it("finds a resource field when target is the string 'RESOURCE'", () => {
    const field = findCatalogField("RESOURCE", "timezone");
    expect(field).toBeDefined();
    expect(field!.key).toBe("timezone");
  });

  // --- not found ---
  it("returns undefined for an unknown key in the project catalog", () => {
    expect(findCatalogField(BlueprintTarget.PROJECT, "nonExistentKey")).toBeUndefined();
  });

  it("returns undefined for an unknown key in the resource catalog", () => {
    expect(findCatalogField(BlueprintTarget.RESOURCE, "nonExistentKey")).toBeUndefined();
  });

  it("returns undefined for an empty string key", () => {
    expect(findCatalogField(BlueprintTarget.PROJECT, "")).toBeUndefined();
    expect(findCatalogField(BlueprintTarget.RESOURCE, "")).toBeUndefined();
  });

  it("does not find a resource field when searching in the project catalog", () => {
    // 'contractType' lives only in the resource catalog
    expect(findCatalogField(BlueprintTarget.PROJECT, "contractType")).toBeUndefined();
  });

  it("does not find a project field when searching in the resource catalog", () => {
    // 'billingModel' lives only in the project catalog
    expect(findCatalogField(BlueprintTarget.RESOURCE, "billingModel")).toBeUndefined();
  });

  // --- returned object integrity ---
  it("returned field contains the expected options array for a SELECT field", () => {
    const field = findCatalogField(BlueprintTarget.PROJECT, "complexityLevel");
    expect(field).toBeDefined();
    const values = field!.options!.map((o) => o.value);
    expect(values).toContain("low");
    expect(values).toContain("medium");
    expect(values).toContain("high");
    expect(values).toContain("very_high");
  });
});

// ---------------------------------------------------------------------------
// Specific well-known fields – spot checks
// ---------------------------------------------------------------------------

describe("specific PROJECT_FIELD_CATALOG fields", () => {
  it("billingModel options include fixed, tm, and hybrid", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "billingModel")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("fixed");
    expect(values).toContain("tm");
    expect(values).toContain("hybrid");
  });

  it("renderEngine includes unreal and arnold", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "renderEngine")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("unreal");
    expect(values).toContain("arnold");
  });

  it("deliveryFormat includes exr, mp4, and glb", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "deliveryFormat")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("exr");
    expect(values).toContain("mp4");
    expect(values).toContain("glb");
  });

  it("invoiceCycle includes all 5 expected values", () => {
    const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "invoiceCycle")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toEqual(
      expect.arrayContaining(["weekly", "biweekly", "monthly", "milestone", "on_completion"]),
    );
  });
});

describe("specific RESOURCE_FIELD_CATALOG fields", () => {
  it("contractType options include freelance and permanent", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "contractType")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("freelance");
    expect(values).toContain("permanent");
  });

  it("primarySoftware includes houdini and maya", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "primarySoftware")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("houdini");
    expect(values).toContain("maya");
  });

  it("spokenLanguages includes de (German) and en (English)", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "spokenLanguages")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("de");
    expect(values).toContain("en");
  });

  it("specialization includes td (Technical Direction) and generalist", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "specialization")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("td");
    expect(values).toContain("generalist");
  });

  it("timezone includes Europe/Berlin and America/Los_Angeles", () => {
    const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "timezone")!;
    const values = field.options!.map((o) => o.value);
    expect(values).toContain("Europe/Berlin");
    expect(values).toContain("America/Los_Angeles");
  });
});
