import {
  nameTokens,
  suggestCustomersByName,
  suggestionNote,
  type CustomerNameRow,
} from "./name-matcher";

/**
 * Every row here is a real name out of the customer book (1536 rows, dev
 * mirror of production), chosen because it is one of the shapes that breaks
 * naive matching: surname-first ordering, a middle initial, a Spanish double
 * surname, a joint account, a missing comma, and the `(SIN NOMBRE)`
 * placeholder the migration left for customers whose DATGRAL row had no name.
 */
const BOOK: CustomerNameRow[] = [
  { id: "c1", name: "WAGONER, PAMELA" },
  { id: "c2", name: "MCWILLIAMS, BRIAN MICHAEL" },
  { id: "c3", name: "MCWILLIAMS, BRIAN" },
  { id: "c4", name: "WEAKLAND, RICHARD E." },
  { id: "c5", name: "ESTRADA, JERRY & MARILYN" },
  { id: "c6", name: "CABALLERO PRIETO, GUILLERMO" },
  { id: "c7", name: "GREENE STEPHANIE" },
  { id: "c8", name: "(SIN NOMBRE)" },
  { id: "c9", name: "MUÑOZ, LUIS ALBERTO" },
  { id: "c10", name: "SMITH, DANIEL" },
  { id: "c11", name: "SMITH, JOHN" },
];

describe("nameTokens", () => {
  it("makes the two orderings the same set", () => {
    expect(nameTokens("PAMELA WAGONER").sort()).toEqual(
      nameTokens("WAGONER, PAMELA").sort(),
    );
  });

  it("drops initials, particles and corporate suffixes", () => {
    expect(nameTokens("WEAKLAND, RICHARD E.")).toEqual(["WEAKLAND", "RICHARD"]);
    expect(nameTokens("GARCIA DE LA TORRE, ANA")).toEqual(["GARCIA", "TORRE", "ANA"]);
    expect(nameTokens("CONSTRUCTORA BAJA S.A. DE C.V.")).toEqual([
      "CONSTRUCTORA",
      "BAJA",
    ]);
  });

  it("folds accents so OCR's MUNOZ reaches the book's MUÑOZ", () => {
    expect(nameTokens("MUÑOZ")).toEqual(["MUNOZ"]);
  });

  it("drops the phone number ANA prints against the insured name", () => {
    // Observed verbatim from the ANA automobile face.
    expect(nameTokens("MARIA GARCIA Ph.3102001538")).toEqual([
      "MARIA",
      "GARCIA",
      "PH",
    ]);
  });
});

describe("suggestCustomersByName", () => {
  it("matches the reversed name exactly", () => {
    const [top] = suggestCustomersByName("PAMELA WAGONER", BOOK);
    expect(top).toMatchObject({ customerId: "c1", tier: "EXACT", score: 1 });
  });

  it("treats a printed middle name the book lacks as a partial hit", () => {
    const hits = suggestCustomersByName("PAMELA DENISE WAGONER", BOOK);
    expect(hits[0]).toMatchObject({ customerId: "c1", tier: "PARTIAL" });
    expect(hits[0].score).toBeCloseTo(2 / 3);
  });

  it("ranks the exact row above the row that merely contains it", () => {
    // Both MCWILLIAMS rows are reachable from this name; the one that holds
    // the middle name is the exact set and must come first.
    const hits = suggestCustomersByName("BRIAN MICHAEL MCWILLIAMS", BOOK);
    expect(hits.map((h) => h.customerId)).toEqual(["c2", "c3"]);
    expect(hits[0].tier).toBe("EXACT");
    expect(hits[1].tier).toBe("PARTIAL");
  });

  it("reaches a joint account from the one spouse the carrier printed", () => {
    const hits = suggestCustomersByName("JERRY ESTRADA", BOOK);
    expect(hits[0]).toMatchObject({ customerId: "c5", tier: "PARTIAL" });
  });

  it("will not reach a joint account on given names alone", () => {
    // No surname printed: `JERRY MARILYN` overlaps ESTRADA, JERRY & MARILYN
    // on two tokens, and matching on that would book a stranger's policy.
    expect(suggestCustomersByName("JERRY MARILYN", BOOK)).toEqual([]);
  });

  it("matches a Spanish double surname regardless of where the comma fell", () => {
    const [top] = suggestCustomersByName("GUILLERMO CABALLERO PRIETO", BOOK);
    expect(top).toMatchObject({ customerId: "c6", tier: "EXACT" });
  });

  it("still matches a book row that has no comma", () => {
    const [top] = suggestCustomersByName("STEPHANIE GREENE", BOOK);
    expect(top).toMatchObject({ customerId: "c7", tier: "EXACT" });
  });

  it("never suggests the (SIN NOMBRE) placeholder", () => {
    expect(suggestCustomersByName("SIN NOMBRE", BOOK)).toEqual([]);
    expect(suggestCustomersByName("NOMBRE DEL ASEGURADO", BOOK)).toEqual([]);
  });

  it("returns nothing on a shared surname alone", () => {
    // 185 surnames are shared by 524 customers; one token is not evidence.
    expect(suggestCustomersByName("SMITH", BOOK)).toEqual([]);
  });

  it("returns nothing for a different person with the same surname", () => {
    expect(suggestCustomersByName("ROBERT SMITH", BOOK)).toEqual([]);
  });

  it("refuses a page-sized blob", () => {
    // GMX's especificación has no field labels and the parser has handed its
    // whole first page over as the insured name.
    const blob =
      "ESPECIFICACION DE LA POLIZA DE SEGURO DE RESPONSABILIDAD CIVIL " +
      "EXPEDIDA A FAVOR DE PAMELA WAGONER CON VIGENCIA DEL 01 DE ENERO";
    expect(suggestCustomersByName(blob, BOOK)).toEqual([]);
  });

  it("caps the list", () => {
    expect(suggestCustomersByName("BRIAN MICHAEL MCWILLIAMS", BOOK, 1)).toHaveLength(1);
  });

  it("handles a null insured name", () => {
    expect(suggestCustomersByName(null, BOOK)).toEqual([]);
  });
});

describe("suggestionNote", () => {
  it("says nothing when there is nothing", () => {
    expect(suggestionNote([])).toBeNull();
  });

  it("names a single exact hit", () => {
    expect(suggestionNote(suggestCustomersByName("PAMELA WAGONER", BOOK))).toBe(
      "posible cliente por nombre: WAGONER, PAMELA",
    );
  });

  it("reports a tie rather than picking one", () => {
    // The book really does hold EMERY, LAURA twice and KIRCHHOFF, CINDY
    // three times.
    const dupes: CustomerNameRow[] = [
      { id: "d1", name: "EMERY, LAURA" },
      { id: "d2", name: "EMERY, LAURA" },
    ];
    expect(suggestionNote(suggestCustomersByName("LAURA EMERY", dupes))).toBe(
      "2 clientes tienen ese mismo nombre; elija cuál",
    );
  });

  it("lists partial hits", () => {
    expect(suggestionNote(suggestCustomersByName("PAMELA DENISE WAGONER", BOOK))).toBe(
      "posibles clientes por nombre: WAGONER, PAMELA",
    );
  });
});
