import { describe, it, expect, vi, afterEach } from "vitest";
import { aresLookupByIco, aresSearchByName } from "../services/ares.service";

// ARES is a true external — mock global fetch (the only allowed mock class).
const SUBJECT = {
  ico: "22599851",
  obchodniJmeno: "BOHA Automation s.r.o.",
  dic: "CZ22599851",
  sidlo: {
    nazevStatu: "Česká republika",
    nazevObce: "Turnov",
    nazevCastiObce: "Turnov",
    nazevUlice: "Nádražní",
    cisloDomovni: 485,
    psc: 51101,
    textovaAdresa: "Nádražní 485, 51101 Turnov",
  },
};

function mockFetchOnce(status: number, body?: unknown) {
  return vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({
    ok: status >= 200 && status < 300,
    status,
    json: async () => body,
  } as Response);
}

afterEach(() => {
  vi.restoreAllMocks();
});

describe("aresLookupByIco", () => {
  it("maps a subject to the normalized prefill shape", async () => {
    mockFetchOnce(200, SUBJECT);
    const res = await aresLookupByIco("22599851");
    expect(res).toEqual({
      ico: "22599851",
      dic: "CZ22599851",
      name: "BOHA Automation s.r.o.",
      street: "Nádražní 485",
      city: "Turnov",
      postal_code: "511 01",
      country: "Česká republika",
    });
  });

  it("builds Prague-style orientation numbers as 'Ulice 485/12a'", async () => {
    mockFetchOnce(200, {
      ...SUBJECT,
      sidlo: {
        ...SUBJECT.sidlo,
        cisloOrientacni: 12,
        cisloOrientacniPismeno: "a",
      },
    });
    const res = await aresLookupByIco("22599851");
    expect("street" in res && res.street).toBe("Nádražní 485/12a");
  });

  it("rejects a non-8-digit IČO without calling ARES", async () => {
    const spy = vi.spyOn(globalThis, "fetch");
    expect(await aresLookupByIco("123")).toEqual({ error: "invalid_ico" });
    expect(await aresLookupByIco("abcdefgh")).toEqual({
      error: "invalid_ico",
    });
    expect(spy).not.toHaveBeenCalled();
  });

  it("accepts an IČO with stray whitespace", async () => {
    const spy = mockFetchOnce(200, SUBJECT);
    const res = await aresLookupByIco(" 225 99851 ");
    expect("ico" in res && res.ico).toBe("22599851");
    expect(String(spy.mock.calls[0][0])).toContain("/22599851");
  });

  it("maps 404 to not_found and network failure to ares_unavailable", async () => {
    mockFetchOnce(404);
    expect(await aresLookupByIco("12345678")).toEqual({ error: "not_found" });

    vi.spyOn(globalThis, "fetch").mockRejectedValueOnce(new Error("ETIMEDOUT"));
    expect(await aresLookupByIco("12345678")).toEqual({
      error: "ares_unavailable",
    });
  });
});

describe("aresSearchByName", () => {
  it("maps the result list and sends the name in the POST body", async () => {
    const spy = mockFetchOnce(200, { ekonomickeSubjekty: [SUBJECT] });
    const res = await aresSearchByName("BOHA Automation");
    expect(Array.isArray(res)).toBe(true);
    if (Array.isArray(res)) {
      expect(res).toHaveLength(1);
      expect(res[0].name).toBe("BOHA Automation s.r.o.");
      expect(res[0].postal_code).toBe("511 01");
    }
    const init = spy.mock.calls[0][1] as RequestInit;
    expect(JSON.parse(String(init.body))).toMatchObject({
      obchodniJmeno: "BOHA Automation",
      pocet: 10,
    });
  });

  it("returns [] for a blank query without calling ARES", async () => {
    const spy = vi.spyOn(globalThis, "fetch");
    expect(await aresSearchByName("   ")).toEqual([]);
    expect(spy).not.toHaveBeenCalled();
  });

  it("maps upstream failure to ares_unavailable", async () => {
    mockFetchOnce(503);
    expect(await aresSearchByName("BOHA")).toEqual({
      error: "ares_unavailable",
    });
  });
});
