import { describe, it, expect, vi } from "vitest";
import { ApiClient } from "@/shared/api/client";
import { searchDestinations, getFlightsMapCalendar } from "./api";
import type {
  FlightsMapSearchParams,
  IDestinationsResponse,
  IFlightsMapDaysResponse,
} from "./types";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function createMockClient(responseBody: unknown, status = 200) {
  const mockFetch = vi.fn<typeof fetch>().mockResolvedValue(
    new Response(JSON.stringify(responseBody), {
      status,
      headers: { "Content-Type": "application/json" },
    }),
  );

  const client = new ApiClient({
    baseUrl: "https://api.test.com",
    locale: "ru",
    fetchImpl: mockFetch,
    retry: { maxRetries: 0 },
  });

  return { client, mockFetch };
}

function extractUrl(mockFetch: ReturnType<typeof vi.fn>): URL {
  const call = mockFetch.mock.calls[0] as [string, ...unknown[]];
  return new URL(call[0]);
}

const DESTINATIONS_RESPONSE: IDestinationsResponse = {
  data: {
    routes: [
      { route: ["SVO", "LED"], isDirect: true },
      { route: ["SVO", "DME", "LED"], isDirect: false },
    ],
  },
};

const DAYS_RESPONSE: IFlightsMapDaysResponse = {
  days: "01101",
};

// ---------------------------------------------------------------------------
// searchDestinations
// ---------------------------------------------------------------------------

describe("searchDestinations", () => {
  it("sends GET to destinations/1 with query params", async () => {
    const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);

    const params: FlightsMapSearchParams = {
      departure: "SVO",
      arrival: "LED",
      dateFrom: "20250601",
      dateTo: "20251201",
    };

    await searchDestinations(client, params);

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/1/ru/destinations");
    expect(url.searchParams.get("departure")).toBe("SVO");
    expect(url.searchParams.get("arrival")).toBe("LED");
    expect(url.searchParams.get("dateFrom")).toBe("2025-06-01");
    expect(url.searchParams.get("dateTo")).toBe("2025-12-01");
    expect(url.searchParams.get("connections")).toBe("0");
  });

  it("omits arrival param when not provided", async () => {
    const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);

    await searchDestinations(client, {
      departure: "SVO",
      dateFrom: "20250601",
      dateTo: "20251201",
    });

    const url = extractUrl(mockFetch);
    expect(url.searchParams.has("arrival")).toBe(false);
  });

  it("sends connections param when specified", async () => {
    const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);

    await searchDestinations(client, {
      departure: "SVO",
      arrival: "LED",
      dateFrom: "20250601",
      dateTo: "20251201",
      connections: 1,
    });

    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("connections")).toBe("1");
  });

  it("returns the deserialized response", async () => {
    const { client } = createMockClient(DESTINATIONS_RESPONSE);

    const result = await searchDestinations(client, {
      departure: "SVO",
      arrival: "LED",
      dateFrom: "20250601",
      dateTo: "20251201",
    });

    expect(result).toEqual(DESTINATIONS_RESPONSE);
  });

  it("throws on server error", async () => {
    const { client } = createMockClient({ error: "internal" }, 500);

    await expect(
      searchDestinations(client, {
        departure: "SVO",
        dateFrom: "20250601",
        dateTo: "20251201",
      }),
    ).rejects.toThrow("HTTP 500");
  });
});

// ---------------------------------------------------------------------------
// getFlightsMapCalendar
// ---------------------------------------------------------------------------

describe("getFlightsMapCalendar", () => {
  it("builds correct path for direct route", async () => {
    const { client, mockFetch } = createMockClient(DAYS_RESPONSE);

    await getFlightsMapCalendar(client, {
      date: "20250601",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1/ru/days/2025-06-01/200/route/SVO-LED/flights-map/");
  });

  it("builds correct path for connecting route", async () => {
    const { client, mockFetch } = createMockClient(DAYS_RESPONSE);

    await getFlightsMapCalendar(client, {
      date: "20250601",
      departure: "SVO",
      arrival: "LED",
      connections: true,
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe(
      "/flights/v1/ru/days/2025-06-01/200/connections/SVO-LED-1/flights-map/",
    );
  });

  it("builds correct path for departure-only (spider mode)", async () => {
    const { client, mockFetch } = createMockClient(DAYS_RESPONSE);

    await getFlightsMapCalendar(client, {
      date: "20250601",
      departure: "SVO",
      connections: false,
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1/ru/days/2025-06-01/200/departure/SVO/flights-map/");
  });

  it("parses binary days string into available date strings", async () => {
    const { client } = createMockClient({ days: "10110" });

    const result = await getFlightsMapCalendar(client, {
      date: "20250601",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

    expect(result).toEqual(["20250601", "20250603", "20250604"]);
  });

  it("returns empty array for empty days string", async () => {
    const { client } = createMockClient({ days: "" });

    const result = await getFlightsMapCalendar(client, {
      date: "20250601",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

    expect(result).toEqual([]);
  });

  it("throws on server error", async () => {
    const { client } = createMockClient({ error: "internal" }, 500);

    await expect(
      getFlightsMapCalendar(client, {
        date: "20250601",
        departure: "SVO",
        arrival: "LED",
        connections: false,
      }),
    ).rejects.toThrow("HTTP 500");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.24.5 R44/R45: API endpoint contract assertions
// ---------------------------------------------------------------------------

describe("§4.1.24.5 R44: searchDestinations uses reusable destinations endpoint", () => {
  it("4.1.24-R44: endpoint path is flights/1/{locale}/destinations", async () => {
    const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);
    await searchDestinations(client, {
      departure: "MOW",
      dateFrom: "20260421",
      dateTo: "20261021",
    });
    const url = extractUrl(mockFetch);
    expect(url.pathname).toMatch(/\/flights\/1\/ru\/destinations$/);
  });

  it("4.1.24-R44: dateFrom/dateTo params are formatted as yyyy-MM-dd with dashes", async () => {
    const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);
    await searchDestinations(client, {
      departure: "MOW",
      dateFrom: "20260421",
      dateTo: "20261021",
    });
    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("dateFrom")).toBe("2026-04-21");
    expect(url.searchParams.get("dateTo")).toBe("2026-10-21");
  });

  it("4.1.24-R44: connections param present in query string", async () => {
    const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);
    await searchDestinations(client, {
      departure: "MOW",
      arrival: "LED",
      dateFrom: "20260421",
      dateTo: "20261021",
      connections: 1,
    });
    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("connections")).toBe("1");
  });

  it("4.1.24-R44: response data.routes array is returned", async () => {
    const { client } = createMockClient(DESTINATIONS_RESPONSE);
    const result = await searchDestinations(client, {
      departure: "MOW",
      dateFrom: "20260421",
      dateTo: "20261021",
    });
    expect(Array.isArray(result.data.routes)).toBe(true);
    expect(result.data.routes[0]).toHaveProperty("isDirect");
    expect(result.data.routes[0]).toHaveProperty("route");
  });
});

describe("§4.1.24.5 R45: getFlightsMapCalendar uses days endpoint with bit-string response", () => {
  it("4.1.24-R45: endpoint path is flights/v1/{locale}/days/...", async () => {
    const { client, mockFetch } = createMockClient(DAYS_RESPONSE);
    await getFlightsMapCalendar(client, {
      date: "20260421",
      departure: "MOW",
      connections: false,
    });
    const url = extractUrl(mockFetch);
    expect(url.pathname).toMatch(/\/flights\/v1\/ru\/days\//);
  });

  it("4.1.24-R45: response parsed as bit-string — \"1\" chars become available dates", async () => {
    const { client } = createMockClient({ days: "10100" });
    const result = await getFlightsMapCalendar(client, {
      date: "20260421",
      departure: "MOW",
      connections: false,
    });
    // days[0]=1 → 20260421, days[1]=0 → skip, days[2]=1 → 20260423
    expect(result).toContain("20260421");
    expect(result).toContain("20260423");
    expect(result).not.toContain("20260422");
  });

  it("4.1.24-R45: route with connections=true uses connections/ path segment", async () => {
    const { client, mockFetch } = createMockClient(DAYS_RESPONSE);
    await getFlightsMapCalendar(client, {
      date: "20260421",
      departure: "SVO",
      arrival: "LED",
      connections: true,
    });
    const url = extractUrl(mockFetch);
    expect(url.pathname).toContain("/connections/");
  });
});
