import { describe, it, expect, beforeEach, vi } from "vitest";
import { fetchSpecialMealAvailability, _resetSpecialMealCache } from "./specialMealApi.js";

describe("fetchSpecialMealAvailability §4.1.15.10", () => {
  beforeEach(() => {
    _resetSpecialMealCache();
    vi.restoreAllMocks();
  });

  it("posts to the ws2 meal endpoint with the documented payload shape", async () => {
    const fetchSpy = vi
      .spyOn(globalThis, "fetch")
      .mockResolvedValue(
        new Response(JSON.stringify({ special_meals: ["DBML"] }), { status: 200 }),
      );
    await fetchSpecialMealAvailability({
      origin: "SVO",
      destination: "JFK",
      airline: "SU",
      number: "100",
      flightDatetimeUtc: "2026-04-22 10:00:00",
      cabin: "economy",
    });
    expect(fetchSpy).toHaveBeenCalledTimes(1);
    const call = fetchSpy.mock.calls[0];
    expect(call?.[0]).toBe("https://www.aeroflot.ru/ws2/v.0.0.2/json/meal");
    const body = JSON.parse(String(call?.[1]?.body ?? "{}"));
    expect(body).toMatchObject({
      origin: "SVO",
      destination: "JFK",
      airline: "SU",
      number: "100",
      flight_datetime: "2026-04-22 10:00:00",
      cabin: "economy",
    });
    // The TZ says to IGNORE is_available_now — make sure we don't send it.
    expect(body).not.toHaveProperty("is_available_now");
  });

  it("caches identical requests (2nd call does not hit the network)", async () => {
    const fetchSpy = vi
      .spyOn(globalThis, "fetch")
      .mockResolvedValue(
        new Response(JSON.stringify({ special_meals: [1] }), { status: 200 }),
      );
    const params = {
      origin: "SVO", destination: "JFK", airline: "SU", number: "100",
      flightDatetimeUtc: "2026-04-22 10:00:00", cabin: "economy",
    } as const;
    const a = await fetchSpecialMealAvailability(params);
    const b = await fetchSpecialMealAvailability(params);
    expect(a).toBe(true);
    expect(b).toBe(true);
    expect(fetchSpy).toHaveBeenCalledTimes(1);
  });

  it("treats an empty response as no-special-meal", async () => {
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({ special_meals: [], is_available_now: true }), {
        status: 200,
      }),
    );
    const result = await fetchSpecialMealAvailability({
      origin: "SVO", destination: "JFK", airline: "SU", number: "101",
      flightDatetimeUtc: "2026-04-22 10:00:00", cabin: "economy",
    });
    // is_available_now is deliberately ignored per TZ §4.1.15.10.
    expect(result).toBe(false);
  });

  it("returns null on non-2xx and does not poison the cache", async () => {
    const fetchSpy = vi
      .spyOn(globalThis, "fetch")
      .mockResolvedValueOnce(new Response("err", { status: 500 }))
      .mockResolvedValueOnce(
        new Response(JSON.stringify({ available: true }), { status: 200 }),
      );
    const params = {
      origin: "SVO", destination: "JFK", airline: "SU", number: "200",
      flightDatetimeUtc: "2026-04-22 10:00:00", cabin: "economy",
    } as const;
    const first = await fetchSpecialMealAvailability(params);
    const second = await fetchSpecialMealAvailability(params);
    expect(first).toBeNull();
    expect(second).toBe(true);
    expect(fetchSpy).toHaveBeenCalledTimes(2);
  });

  it("returns null on network error", async () => {
    vi.spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("net"));
    const result = await fetchSpecialMealAvailability({
      origin: "SVO", destination: "JFK", airline: "SU", number: "300",
      flightDatetimeUtc: "2026-04-22 10:00:00", cabin: "economy",
    });
    expect(result).toBeNull();
  });
});
