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

import { reverseGeocode } from "./reverseGeocode";

function fc(features: Array<Record<string, unknown>>): Response {
  return {
    ok: true,
    json: async () => ({ features: features.map((properties) => ({ properties })) }),
  } as unknown as Response;
}

const AT = { latitude: 50.8143, longitude: -0.3707 };

describe("reverseGeocode", () => {
  it("prefers a street/address hit over a bare postcode and builds a label", async () => {
    const fetchImpl = vi.fn(async () =>
      fc([
        { name: "BN11 1BG", postcode: "BN11 1BG", city: "Worthing", type: "postcode" },
        { name: "Union Place", street: "Union Place", city: "Worthing", state: "England", type: "street" },
      ])
    );
    const r = await reverseGeocode("snk_x", AT, fetchImpl as unknown as typeof fetch);
    expect(r).toEqual({ name: "Union Place", address: "Worthing, England" });
    // Reverse endpoint hit with lon/lat.
    const [url] = fetchImpl.mock.calls[0] as unknown as [string];
    expect(url).toContain("/geocode/reverse?lon=-0.3707&lat=50.8143");
  });

  it("falls back to the nearest feature (postcode) when no street/address hit", async () => {
    const fetchImpl = vi.fn(async () =>
      fc([{ name: "BN11 1BG", postcode: "BN11 1BG", city: "Worthing", type: "postcode" }])
    );
    const r = await reverseGeocode("snk_x", AT, fetchImpl as unknown as typeof fetch);
    // Postcode is the name, so it isn't duplicated into the address.
    expect(r).toEqual({ name: "BN11 1BG", address: "Worthing" });
  });

  it("composes house number + street when present", async () => {
    const fetchImpl = vi.fn(async () =>
      fc([{ housenumber: "12", street: "High Street", city: "Horsham", type: "address" }])
    );
    const r = await reverseGeocode("snk_x", AT, fetchImpl as unknown as typeof fetch);
    expect(r).toEqual({ name: "12 High Street", address: "High Street, Horsham" });
  });

  it("returns null without a key, on empty results, and on failure", async () => {
    const spy = vi.fn();
    expect(await reverseGeocode("", AT, spy as unknown as typeof fetch)).toBeNull();
    expect(spy).not.toHaveBeenCalled();

    const empty = vi.fn(async () => fc([]));
    expect(await reverseGeocode("snk_x", AT, empty as unknown as typeof fetch)).toBeNull();

    const bad = vi.fn(async () => ({ ok: false, json: async () => ({}) }) as unknown as Response);
    expect(await reverseGeocode("snk_x", AT, bad as unknown as typeof fetch)).toBeNull();

    const throws = vi.fn(async () => {
      throw new Error("offline");
    });
    expect(await reverseGeocode("snk_x", AT, throws as unknown as typeof fetch)).toBeNull();
  });
});
