import { describe, expect, test } from "vitest";
import * as z from "zod";

describe("z.creditCard", () => {
  // Luhn-valid PANs. The last four are the schemes an issuer allowlist would turn away — validation is shape plus Luhn only, so the BIN is never consulted.
  const valid: [string, string][] = [
    ["Visa", "4111111111111111"],
    ["Visa (13)", "4007000000027"],
    ["Mastercard", "5555555555554444"],
    ["AmEx", "378282246310005"],
    ["Discover", "6011111111111117"],
    ["JCB", "3530111333300000"],
    ["Diners Club", "30569309025904"],
    ["UnionPay", "6200000000000005"],
    ["Mir", "2200000000000004"],
    ["Elo", "6362000000000009"],
    ["Discover 644-649", "6450000000000002"],
    ["Troy", "9792000000000003"],
  ];

  test.each(valid)("accepts %s", (_label, n) => {
    expect(z.creditCard().parse(n)).toBe(n);
  });

  test("accepts single space or hyphen separators", () => {
    expect(z.creditCard().parse("4111 1111 1111 1111")).toBe("4111 1111 1111 1111");
    expect(z.creditCard().parse("4111-1111-1111-1111")).toBe("4111-1111-1111-1111");
  });

  test("rejects other separator shapes", () => {
    for (const n of ["4111  1111 1111 1111", " 4111111111111111", "4111111111111111 ", "4111.1111.1111.1111"]) {
      expect(() => z.creditCard().parse(n)).toThrow();
    }
  });

  test("accepts the 12- and 19-digit length bounds", () => {
    expect(z.creditCard().parse("400000000002")).toBe("400000000002");
    expect(z.creditCard().parse("6250000000000000005")).toBe("6250000000000000005");
  });

  test("rejects Luhn-valid input outside 12-19 digits", () => {
    expect(() => z.creditCard().parse("40000000006")).toThrow();
    expect(() => z.creditCard().parse("40000000000000000002")).toThrow();
  });

  test("rejects when Luhn fails (right shape, wrong checksum)", () => {
    expect(() => z.creditCard().parse("4111111111111112")).toThrow();
  });

  test("rejects non-digit input", () => {
    expect(() => z.creditCard().parse("not-a-card")).toThrow();
    expect(() => z.creditCard().parse("")).toThrow();
  });

  test("carries the shape regex as its JSON Schema pattern", () => {
    // Luhn is not expressible as a pattern, so `pattern` covers length and separators only.
    expect(z.toJSONSchema(z.creditCard())).toMatchObject({
      format: "credit_card",
      pattern: z.regexes.creditCard.source,
    });
  });

  test("round-trips through JSON Schema with the checksum intact", () => {
    const schema = z.fromJSONSchema(z.toJSONSchema(z.creditCard()));
    expect(schema.safeParse("4111111111111111").success).toBe(true);
    expect(schema.safeParse("4111111111111112").success).toBe(false);
  });

  test("error message references credit card", () => {
    const result = z.creditCard().safeParse("not-a-card");
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.error.issues[0]?.message).toMatch(/credit card/i);
    }
  });
});
