import { describe, expect, test } from "bun:test";
import { SkipRowError } from "../types";
import { createTranslationContext, type TranslationContext } from "../context";

// ─── Test‑only translator helper ──────────────────────────────────────────
// We cannot import Translation<kF,kT,kC> generically, but every translator
// exposes a `.values` array.  We walk the entries exactly the way the real
// `translateRow` function does.

type AnyTranslation = {
  values: Array<{
    from: string;
    to: string;
    process?: (value: any, context: any, row: any) => any;
  }>;
};

function translateRow(
  row: Record<string, unknown>,
  translation: AnyTranslation,
  context: TranslationContext
): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  for (const entry of translation.values) {
    const input = row[entry.from];
    if (input === undefined) continue;
    out[entry.to] = entry.process
      ? entry.process(input, context, row)
      : input;
  }
  return out;
}

function expectSkipRow(fn: () => void): string {
  try {
    fn();
    throw new Error("Expected SkipRowError but nothing was thrown");
  } catch (err) {
    expect(err).toBeInstanceOf(SkipRowError);
    return (err as SkipRowError).message;
  }
}

// ─── TranslationContext factory ───────────────────────────────────────────
function makeContext(overrides?: Partial<TranslationContext>): TranslationContext {
  const ctx = createTranslationContext();
  if (overrides) Object.assign(ctx, overrides);
  return ctx;
}

// ═══════════════════════════════════════════════════════════════════════════
// User Translator
// ═══════════════════════════════════════════════════════════════════════════
describe("userTranslation", () => {
  // Lazy‑load to sidestep top‑level Prisma import issues in test env.
  let userTranslation: AnyTranslation;

  test("module loads", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    expect(userTranslation).toBeDefined();
  });

  test("valid email is normalised", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    const row = {
      emailAddress: "  John@Example.COM  ",
      firstName: "John",
      lastName: "Doe",
      memberRecId: 42,
      memberId: "jdoe",
      memberClass: "F",
      inactiveFlag: false,
      dateHire: new Date("2024-01-01"),
      lastUpdatedUtc: new Date("2024-06-01"),
    };
    const result = translateRow(row, userTranslation, ctx);
    expect(result.login).toBe("jdoe@cw.local");
    expect(result.email).toBe("john@example.com");
    expect(result.firstName).toBe("John");
    expect(result.active).toBe(true);
    expect(result.cwMemberId).toBe(42);
    expect(result.cwIdentifier).toBe("jdoe");
  });

  test("missing email generates placeholder", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    const result = translateRow(
      { emailAddress: null, firstName: "A", lastName: "B", memberRecId: 1, memberId: "a", memberClass: "F", inactiveFlag: false, lastUpdatedUtc: new Date(), dateHire: null },
      userTranslation,
      ctx
    );
    expect(result.email).toBe("a@cw.local");
  });

  test("email without @ generates placeholder", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    const result = translateRow(
      { emailAddress: "nodomain", firstName: "A", lastName: "B", memberRecId: 1, memberId: "a", memberClass: "F", inactiveFlag: false, lastUpdatedUtc: new Date(), dateHire: null },
      userTranslation,
      ctx
    );
    expect(result.email).toBe("a@cw.local");
  });

  test("empty firstName falls back to Unknown", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    const result = translateRow(
      { emailAddress: "x@y.com", firstName: "", lastName: "", memberRecId: 1, memberId: "x", inactiveFlag: false, lastUpdatedUtc: new Date(), dateHire: null },
      userTranslation,
      ctx
    );
    expect(result.firstName).toBe("Unknown");
    expect(result.lastName).toBe("Unknown");
  });

  test("inactiveFlag=true maps to active=false", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    const result = translateRow(
      { emailAddress: "x@y.com", firstName: "X", lastName: "Y", memberRecId: 1, memberId: "x", inactiveFlag: true, lastUpdatedUtc: new Date(), dateHire: null },
      userTranslation,
      ctx
    );
    expect(result.active).toBe(false);
  });

  test("createdAt falls back to lastUpdatedUtc when dateHire is null", async () => {
    const mod = await import("../user");
    userTranslation = mod.userTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    const updated = new Date("2025-03-01");
    const result = translateRow(
      { emailAddress: "x@y.com", firstName: "X", lastName: "Y", memberRecId: 1, memberId: "x", inactiveFlag: false, lastUpdatedUtc: updated, dateHire: null },
      userTranslation,
      ctx
    );
    expect(result.createdAt).toEqual(updated);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// Opportunity Translator
// ═══════════════════════════════════════════════════════════════════════════
describe("opportunityTranslation", () => {
  let opportunityTranslation: AnyTranslation;

  test("module loads", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    expect(opportunityTranslation).toBeDefined();
  });

  test("interest maps 1->COLD, 2->WARM, 3->HOT", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const ctx = makeContext({ defaultOpportunityTypeId: 1 });

    for (const [input, expected] of [
      [1, "COLD"],
      [2, "WARM"],
      [3, "HOT"],
    ] as const) {
      const result = translateRow(
        { opportunityRecId: 100, soInterestRecId: input, soTypeRecId: 1, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 50, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [] },
        opportunityTranslation,
        ctx
      );
      expect(result.interest).toBe(expected);
    }
  });

  test("interest value 0 maps to COLD", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const ctx = makeContext({ defaultOpportunityTypeId: 1 });
    const result = translateRow(
      { opportunityRecId: 100, soInterestRecId: 0, soTypeRecId: 1, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 0, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [] },
      opportunityTranslation,
      ctx
    );
    expect(result.interest).toBe("COLD");
  });

  test("interest value 4+ maps to HOT", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const ctx = makeContext({ defaultOpportunityTypeId: 1 });
    const result = translateRow(
      { opportunityRecId: 100, soInterestRecId: 4, soTypeRecId: 1, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 0, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [] },
      opportunityTranslation,
      ctx
    );
    expect(result.interest).toBe("HOT");
  });

  test("null interest returns null", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const ctx = makeContext({ defaultOpportunityTypeId: 1 });
    const result = translateRow(
      { opportunityRecId: 100, soInterestRecId: null, soTypeRecId: 1, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 0, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [] },
      opportunityTranslation,
      ctx
    );
    expect(result.interest).toBeNull();
  });

  test("missing typeId with default fallback succeeds", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const ctx = makeContext({ defaultOpportunityTypeId: 99 });
    const result = translateRow(
      { opportunityRecId: 100, soInterestRecId: null, soTypeRecId: null, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 0, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [] },
      opportunityTranslation,
      ctx
    );
    expect(result.typeId).toBe(99);
  });

  test("missing typeId with no default triggers skipRow", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const ctx = makeContext({ defaultOpportunityTypeId: null });
    expectSkipRow(() => {
      translateRow(
        { opportunityRecId: 100, soInterestRecId: null, soTypeRecId: null, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 0, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [] },
        opportunityTranslation,
        ctx
      );
    });
  });

  test("primary/secondary sales reps resolved from members", async () => {
    const mod = await import("../opportunity");
    opportunityTranslation = mod.opportunityTranslation as unknown as AnyTranslation;
    const identMap = new Map<number, string>([[10, "jdoe"], [20, "jsmith"]]);
    const ctx = makeContext({
      defaultOpportunityTypeId: 1,
      userIdentifiersByMemberRecId: identMap,
    });
    const result = translateRow(
      { opportunityRecId: 100, soInterestRecId: null, soTypeRecId: 1, opportunityName: "T", notes: null, soPipelineRecId: null, soOppStatusRecId: null, probabilityToClose: 0, source: null, companyRecId: 1, contactRecId: 1, companyAddressRecId: null, poNumber: null, dateCloseExpected: null, datePipelineChange: null, dateBecameLead: null, dateClosed: null, oldCloseFlag: false, closedBy: null, updatedBy: "admin", enteredBy: "admin", dateBecameLeadUtc: new Date(), lastUpdatedUtc: new Date(), members: [{ memberRecId: 10, primarySalesFlag: true, secondarySalesFlag: false }, { memberRecId: 20, primarySalesFlag: false, secondarySalesFlag: true }] },
      opportunityTranslation,
      ctx
    );
    expect(result.primarySalesRepId).toBe("jdoe");
    expect(result.secondarySalesRepId).toBe("jsmith");
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// Service Ticket Translator
// ═══════════════════════════════════════════════════════════════════════════
describe("serviceTicketTranslation", () => {
  let serviceTicketTranslation: AnyTranslation;

  test("module loads", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    expect(serviceTicketTranslation).toBeDefined();
  });

  test("valid row translates correctly", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      usersByMemberRecId: new Map([[5, "user-abc"]]),
      usersByIdentifier: new Map([["admin", "user-admin"]]),
      billingTypeByTicketId: new Map([[42, "Standard"]]),
      billingInstructionsByTicketId: new Map([[42, "Bill monthly"]]),
    });
    const result = translateRow(
      {
        srServiceRecId: 42, summary: "Server is down", srLocationRecId: 1, srSourceRecId: 2,
        srUrgencyRecId: 3, srSeverityRecId: 4, srImpactRecId: 5, srBoardRecId: 10,
        companyRecId: 100, contactRecId: 200, companyAddressRecId: 300,
        billingCompanyRecId: null, billingAddressRecId: null,
        ticketOwnerRecId: 5, enteredBy: "admin", updatedBy: "admin", closedBy: null,
        dateEnteredUtc: new Date("2025-01-01"), lastUpdatedUtc: new Date("2025-06-01"),
        dateClosedUtc: null, isClosedFlag: false, billMethod: "A",
        poNumber: "PO123", billingAmount: 500, rejectedFlag: false, publishFlag: true, redFlag: false,
      },
      serviceTicketTranslation,
      ctx
    );
    expect(result.id).toBe(42);
    expect(result.summary).toBe("Server is down");
    expect(result.severityId).toBe(4);
    expect(result.ticketOwnerId).toBe("user-abc");
    expect(result.createdById).toBe("user-admin");
    expect(result.billingMethod).toBe("ACTUAL_RATES");
    expect(result.billingType).toBe("STANDARD");
    expect(result.billingInstructions).toBe("Bill monthly");
  });

  test("empty summary triggers skipRow", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    expectSkipRow(() => {
      translateRow(
        { srServiceRecId: 1, summary: "", srLocationRecId: 1, srSourceRecId: 1, srSeverityRecId: 1, srImpactRecId: 1, srBoardRecId: null, companyRecId: null, contactRecId: null, companyAddressRecId: null, billingCompanyRecId: null, billingAddressRecId: null, ticketOwnerRecId: null, enteredBy: null, updatedBy: null, closedBy: null, dateEnteredUtc: null, lastUpdatedUtc: null, dateClosedUtc: null, isClosedFlag: false, billMethod: null, srUrgencyRecId: null, poNumber: null, billingAmount: null, rejectedFlag: false, publishFlag: false, redFlag: false },
        serviceTicketTranslation,
        ctx
      );
    });
  });

  test("null locationId triggers skipRow", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    const ctx = makeContext();
    expectSkipRow(() => {
      translateRow(
        { srServiceRecId: 1, summary: "problem", srLocationRecId: null, srSourceRecId: 1, srSeverityRecId: 1, srImpactRecId: 1, srBoardRecId: null, companyRecId: null, contactRecId: null, companyAddressRecId: null, billingCompanyRecId: null, billingAddressRecId: null, ticketOwnerRecId: null, enteredBy: null, updatedBy: null, closedBy: null, dateEnteredUtc: null, lastUpdatedUtc: null, dateClosedUtc: null, isClosedFlag: false, billMethod: null, srUrgencyRecId: null, poNumber: null, billingAmount: null, rejectedFlag: false, publishFlag: false, redFlag: false },
        serviceTicketTranslation,
        ctx
      );
    });
  });

  test("null dates return null instead of epoch", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      billingTypeByTicketId: new Map(),
      billingInstructionsByTicketId: new Map(),
    });
    const result = translateRow(
      {
        srServiceRecId: 1, summary: "test", srLocationRecId: 1, srSourceRecId: 1,
        srSeverityRecId: 1, srImpactRecId: 1, srBoardRecId: null,
        companyRecId: null, contactRecId: null, companyAddressRecId: null,
        billingCompanyRecId: null, billingAddressRecId: null,
        ticketOwnerRecId: null, enteredBy: null, updatedBy: null, closedBy: null,
        dateEnteredUtc: null, lastUpdatedUtc: null, dateClosedUtc: null,
        isClosedFlag: false, billMethod: null, srUrgencyRecId: null,
        poNumber: null, billingAmount: null, rejectedFlag: false, publishFlag: false, redFlag: false,
      },
      serviceTicketTranslation,
      ctx
    );
    expect(result.createdAt).toBeNull();
    expect(result.updatedAt).toBeNull();
  });

  test("billing method maps correctly", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      billingTypeByTicketId: new Map(),
      billingInstructionsByTicketId: new Map(),
    });
    const base = {
      srServiceRecId: 1, summary: "test", srLocationRecId: 1, srSourceRecId: 1,
      srSeverityRecId: 1, srImpactRecId: 1, srBoardRecId: null,
      companyRecId: null, contactRecId: null, companyAddressRecId: null,
      billingCompanyRecId: null, billingAddressRecId: null,
      ticketOwnerRecId: null, enteredBy: null, updatedBy: null, closedBy: null,
      dateEnteredUtc: new Date(), lastUpdatedUtc: new Date(), dateClosedUtc: null,
      isClosedFlag: false, srUrgencyRecId: null,
      poNumber: null, billingAmount: 0, rejectedFlag: false, publishFlag: false, redFlag: false,
    };

    for (const [input, expected] of [
      ["A", "ACTUAL_RATES"],
      ["F", "FIXED_FEE"],
      ["N", "NOT_TO_EXCEED"],
      ["O", "OVERRIDE_RATE"],
      [null, "ACTUAL_RATES"],
      ["UNKNOWN", "ACTUAL_RATES"],
    ] as const) {
      const result = translateRow({ ...base, billMethod: input }, serviceTicketTranslation, ctx);
      expect(result.billingMethod).toBe(expected);
    }
  });

  test("missing billingType defaults to STANDARD", async () => {
    const mod = await import("../service-ticket");
    serviceTicketTranslation = mod.serviceTicketTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      billingTypeByTicketId: new Map(), // No entry for ticket 999
      billingInstructionsByTicketId: new Map(),
    });
    const result = translateRow(
      {
        srServiceRecId: 999, summary: "test", srLocationRecId: 1, srSourceRecId: 1,
        srSeverityRecId: 1, srImpactRecId: 1, srBoardRecId: null,
        companyRecId: null, contactRecId: null, companyAddressRecId: null,
        billingCompanyRecId: null, billingAddressRecId: null,
        ticketOwnerRecId: null, enteredBy: null, updatedBy: null, closedBy: null,
        dateEnteredUtc: new Date(), lastUpdatedUtc: new Date(), dateClosedUtc: null,
        isClosedFlag: false, billMethod: null, srUrgencyRecId: null,
        poNumber: null, billingAmount: 0, rejectedFlag: false, publishFlag: false, redFlag: false,
      },
      serviceTicketTranslation,
      ctx
    );
    expect(result.billingType).toBe("STANDARD");
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// Service Ticket Note Translator
// ═══════════════════════════════════════════════════════════════════════════
describe("serviceTicketNoteTranslation", () => {
  let serviceTicketNoteTranslation: AnyTranslation;

  test("module loads", async () => {
    const mod = await import("../service-ticket-note");
    serviceTicketNoteTranslation = mod.serviceTicketNoteTranslation as unknown as AnyTranslation;
    expect(serviceTicketNoteTranslation).toBeDefined();
  });

  test("valid note translates correctly", async () => {
    const mod = await import("../service-ticket-note");
    serviceTicketNoteTranslation = mod.serviceTicketNoteTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      serviceTicketIds: new Set([42]),
      usersByMemberRecId: new Map([[10, "user-abc"]]),
    });
    const result = translateRow(
      { ticketNoteRecId: 1, srServiceRecId: 42, notesMarkdown: "# Title", notes: "Title", memberRecId: 10, problemFlag: true, resolutionFlag: false, internalAnalysisFlag: false, internalMemberFlag: false, mergedFlag: false, bundledFlag: false, createdByParentFlag: false, dateCreatedUtc: new Date("2025-01-01"), lastUpdatedUtc: new Date("2025-06-01") },
      serviceTicketNoteTranslation,
      ctx
    );
    expect(result.serviceTicketId).toBe(42);
    expect(result.authorId).toBe("user-abc");
    expect(result.notesMd).toBe("# Title");
  });

  test("missing parent ticket triggers skipRow", async () => {
    const mod = await import("../service-ticket-note");
    serviceTicketNoteTranslation = mod.serviceTicketNoteTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      serviceTicketIds: new Set([99]), // 42 NOT in set
      usersByMemberRecId: new Map([[10, "user-abc"]]),
    });
    expectSkipRow(() => {
      translateRow(
        { ticketNoteRecId: 1, srServiceRecId: 42, notesMarkdown: "", notes: "", memberRecId: 10, problemFlag: false, resolutionFlag: false, internalAnalysisFlag: false, internalMemberFlag: false, mergedFlag: false, bundledFlag: false, createdByParentFlag: false, dateCreatedUtc: null, lastUpdatedUtc: null },
        serviceTicketNoteTranslation,
        ctx
      );
    });
  });

  test("unknown memberRecId triggers skipRow", async () => {
    const mod = await import("../service-ticket-note");
    serviceTicketNoteTranslation = mod.serviceTicketNoteTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      serviceTicketIds: new Set([42]),
      usersByMemberRecId: new Map(), // empty
    });
    expectSkipRow(() => {
      translateRow(
        { ticketNoteRecId: 1, srServiceRecId: 42, notesMarkdown: "", notes: "", memberRecId: 999, problemFlag: false, resolutionFlag: false, internalAnalysisFlag: false, internalMemberFlag: false, mergedFlag: false, bundledFlag: false, createdByParentFlag: false, dateCreatedUtc: null, lastUpdatedUtc: null },
        serviceTicketNoteTranslation,
        ctx
      );
    });
  });

  test("null dates return null", async () => {
    const mod = await import("../service-ticket-note");
    serviceTicketNoteTranslation = mod.serviceTicketNoteTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      serviceTicketIds: new Set([42]),
      usersByMemberRecId: new Map([[10, "user-abc"]]),
    });
    const result = translateRow(
      { ticketNoteRecId: 1, srServiceRecId: 42, notesMarkdown: "", notes: "", memberRecId: 10, problemFlag: false, resolutionFlag: false, internalAnalysisFlag: false, internalMemberFlag: false, mergedFlag: false, bundledFlag: false, createdByParentFlag: false, dateCreatedUtc: null, lastUpdatedUtc: null },
      serviceTicketNoteTranslation,
      ctx
    );
    expect(result.createdAt).toBeNull();
    expect(result.updatedAt).toBeNull();
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// Product Data Translator
// ═══════════════════════════════════════════════════════════════════════════
describe("productDataTranslation", () => {
  let productDataTranslation: AnyTranslation;

  test("module loads", async () => {
    const mod = await import("../product-data");
    productDataTranslation = mod.productDataTranslation as unknown as AnyTranslation;
    expect(productDataTranslation).toBeDefined();
  });

  test("missing catalog item triggers skipRow", async () => {
    const mod = await import("../product-data");
    productDataTranslation = mod.productDataTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      catalogItemIds: new Set([1]), // 999 NOT in set
      corporateLocationIds: new Set([1]),
    });
    expectSkipRow(() => {
      translateRow(
        { ivProductRecId: 1, quantity: 1, catalogRecId: 999, ownerLevelRecId: 1, internalNote: null, shortDescription: null, sequenceNumber: null, unitPrice: 10, unitCost: 5, listPrice: 15, discountAmount: 0, recurringRevenue: 0, recurringCost: 0, qtyPicked: 0, qtyShipped: 0, cancelReason: null, quantityCancelled: null, billableFlag: true, taxableFlag: false, invoiceFlag: false, recurringFlag: false, poApprovedFlag: false, calcPriceFlag: false, calcCostFlag: false, cancelFlag: false, srServiceRecId: null, opportunityRecId: null, updatedBy: null, enteredBy: null, closedBy: null, cancelBy: null, dateClosedUtc: null, cancelDateUtc: null, lastUpdateUtc: new Date(), dateEnteredUtc: new Date() },
        productDataTranslation,
        ctx
      );
    });
  });

  test("missing corporate location triggers skipRow", async () => {
    const mod = await import("../product-data");
    productDataTranslation = mod.productDataTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      catalogItemIds: new Set([1]),
      corporateLocationIds: new Set(), // empty
    });
    expectSkipRow(() => {
      translateRow(
        { ivProductRecId: 1, quantity: 1, catalogRecId: 1, ownerLevelRecId: 999, internalNote: null, shortDescription: null, sequenceNumber: null, unitPrice: 10, unitCost: 5, listPrice: 15, discountAmount: 0, recurringRevenue: 0, recurringCost: 0, qtyPicked: 0, qtyShipped: 0, cancelReason: null, quantityCancelled: null, billableFlag: true, taxableFlag: false, invoiceFlag: false, recurringFlag: false, poApprovedFlag: false, calcPriceFlag: false, calcCostFlag: false, cancelFlag: false, srServiceRecId: null, opportunityRecId: null, updatedBy: null, enteredBy: null, closedBy: null, cancelBy: null, dateClosedUtc: null, cancelDateUtc: null, lastUpdateUtc: new Date(), dateEnteredUtc: new Date() },
        productDataTranslation,
        ctx
      );
    });
  });

  test("numeric conversions handle null and invalid values", async () => {
    const mod = await import("../product-data");
    productDataTranslation = mod.productDataTranslation as unknown as AnyTranslation;
    const ctx = makeContext({
      catalogItemIds: new Set([1]),
      corporateLocationIds: new Set([1]),
      serviceTicketIds: new Set(),
      opportunityIds: new Set(),
      productCustomByIvProductId: new Map(),
    });
    const result = translateRow(
      { ivProductRecId: 1, quantity: null, catalogRecId: 1, ownerLevelRecId: 1, internalNote: null, shortDescription: null, sequenceNumber: null, unitPrice: null, unitCost: null, listPrice: null, discountAmount: null, recurringRevenue: null, recurringCost: null, qtyPicked: null, qtyShipped: null, cancelReason: null, quantityCancelled: null, billableFlag: true, taxableFlag: false, invoiceFlag: false, recurringFlag: false, poApprovedFlag: false, calcPriceFlag: false, calcCostFlag: false, cancelFlag: false, srServiceRecId: null, opportunityRecId: null, updatedBy: null, enteredBy: null, closedBy: null, cancelBy: null, dateClosedUtc: null, cancelDateUtc: null, lastUpdateUtc: new Date(), dateEnteredUtc: new Date() },
      productDataTranslation,
      ctx
    );
    expect(result.qty).toBe(1); // null quantity defaults to 1
    expect(result.unitPrice).toBe(0);
    expect(result.unitCost).toBe(0);
    expect(result.listPrice).toBe(0);
    expect(result.discount).toBe(0);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// Service Ticket Priority Translator (Boolean fix)
// ═══════════════════════════════════════════════════════════════════════════
describe("serviceTicketPriorityTranslation", () => {
  test("defaultFlag is coerced to boolean", async () => {
    const mod = await import("../service-ticket-priority");
    const translation = mod.serviceTicketPriorityTranslation as unknown as AnyTranslation;
    const ctx = makeContext();

    // Truthy value
    const result1 = translateRow(
      { srUrgencyRecId: 1, description: "High", color: "#FF0000", defaultFlag: 1, createdBy: null, updatedBy: null, dateCreatedUtc: new Date(), lastUpdatedUtc: new Date() },
      translation, ctx
    );
    expect(result1.defaultFlag).toBe(true);

    // Falsy value
    const result2 = translateRow(
      { srUrgencyRecId: 2, description: "Low", color: null, defaultFlag: 0, createdBy: null, updatedBy: null, dateCreatedUtc: new Date(), lastUpdatedUtc: new Date() },
      translation, ctx
    );
    expect(result2.defaultFlag).toBe(false);

    // Null value
    const result3 = translateRow(
      { srUrgencyRecId: 3, description: "Med", color: null, defaultFlag: null, createdBy: null, updatedBy: null, dateCreatedUtc: new Date(), lastUpdatedUtc: new Date() },
      translation, ctx
    );
    expect(result3.defaultFlag).toBe(false);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// SkipRowError type
// ═══════════════════════════════════════════════════════════════════════════
describe("SkipRowError", () => {
  test("is distinct from generic Error", () => {
    const err = new SkipRowError("test reason");
    expect(err).toBeInstanceOf(SkipRowError);
    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("SkipRowError");
    expect(err.message).toBe("test reason");
    expect(err.__brand).toBe("SkipRowError");
  });

  test("does not match SKIP_ROW: prefix check", () => {
    const err = new SkipRowError("Something wrong");
    // Old code would check: error.message.startsWith("SKIP_ROW:")
    // New errors don't have that prefix
    expect(err.message.startsWith("SKIP_ROW:")).toBe(false);
  });
});
