import { describe, expect, it, vi } from "vitest";

import { matchUkPostcode, resolveUkPostcode } from "./ukPostcode";

describe("matchUkPostcode", () => {
  it("recognises full postcodes and spaces them correctly", () => {
    expect(matchUkPostcode("bn11 1aa")).toEqual({
      kind: "full",
      token: "BN111AA",
      formatted: "BN11 1AA",
    });
    // No space, mixed case, still resolves to the same canonical form.
    expect(matchUkPostcode("m25db")).toMatchObject({ kind: "full", formatted: "M2 5DB" });
    expect(matchUkPostcode("SW1A1AA")).toMatchObject({ kind: "full", formatted: "SW1A 1AA" });
  });

  it("recognises partial outcodes", () => {
    expect(matchUkPostcode("BN11")).toMatchObject({ kind: "outcode", formatted: "BN11" });
    expect(matchUkPostcode("sw1a")).toMatchObject({ kind: "outcode", token: "SW1A" });
  });

  it("rejects non-postcode queries", () => {
    for (const q of ["Manchester", "Tower Bridge", "", "12345", "café"]) {
      expect(matchUkPostcode(q)).toBeNull();
    }
  });
});

function okResponse(result: unknown): Response {
  return { ok: true, json: async () => ({ result }) } as unknown as Response;
}

describe("resolveUkPostcode", () => {
  it("resolves a full postcode to a SearchResult with locality + distance", async () => {
    const fetchImpl = vi.fn(async () =>
      okResponse({
        postcode: "BN11 1AA",
        latitude: 50.812,
        longitude: -0.372,
        admin_ward: "Marine",
        admin_district: "Worthing",
        region: "South East",
      })
    );
    const near = { latitude: 50.8, longitude: -0.37 };
    const result = await resolveUkPostcode("bn111aa", near, fetchImpl as unknown as typeof fetch);
    expect(fetchImpl).toHaveBeenCalledWith("https://api.postcodes.io/postcodes/BN111AA");
    expect(result).toMatchObject({
      name: "BN11 1AA",
      address: "Marine, Worthing, South East",
      position: { latitude: 50.812, longitude: -0.372 },
    });
    expect(result?.distanceMeters).toBeGreaterThan(0);
  });

  it("resolves an outcode via the outcodes endpoint (arrays flattened)", async () => {
    const fetchImpl = vi.fn(async () =>
      okResponse({
        outcode: "BN11",
        latitude: 50.81,
        longitude: -0.37,
        admin_district: ["Worthing"],
        region: "South East",
      })
    );
    const result = await resolveUkPostcode("BN11", undefined, fetchImpl as unknown as typeof fetch);
    expect(fetchImpl).toHaveBeenCalledWith("https://api.postcodes.io/outcodes/BN11");
    expect(result).toMatchObject({
      name: "BN11",
      address: "Postcode district, Worthing, South East",
    });
  });

  it("returns null for a 404 (well-formed but nonexistent code)", async () => {
    const fetchImpl = vi.fn(async () => ({ ok: false, json: async () => ({}) }) as unknown as Response);
    expect(await resolveUkPostcode("ZZ99 9ZZ", undefined, fetchImpl as unknown as typeof fetch)).toBeNull();
  });

  it("returns null (no fetch) for a non-postcode query", async () => {
    const fetchImpl = vi.fn();
    expect(await resolveUkPostcode("Manchester", undefined, fetchImpl as unknown as typeof fetch)).toBeNull();
    expect(fetchImpl).not.toHaveBeenCalled();
  });

  it("swallows network errors and returns null", async () => {
    const fetchImpl = vi.fn(async () => {
      throw new Error("offline");
    });
    expect(await resolveUkPostcode("BN11 1AA", undefined, fetchImpl as unknown as typeof fetch)).toBeNull();
  });
});
