import { describe, it, expect, vi } from "vitest";
import { ApiClient } from "@/shared/api/client";
import {
  searchFlights,
  getFlightDetails,
  getCalendarDays,
} from "./api";
import type {
  SearchFlightsParams,
  FlightDetailsParams,
  CalendarParams,
} from "./api";
import type { IBoardResponse, IDaysResponse } from "./types";

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

/**
 * Create an ApiClient with a mock fetch that captures the request URL
 * and returns a canned response.
 */
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]);
}

/** Minimal valid IBoardResponse for testing */
const BOARD_RESPONSE: IBoardResponse = {
  data: {
    partners: ["SU"],
    routes: [],
    daysOfFlight: ["2025-01-15"],
  },
};

const DAYS_RESPONSE: IDaysResponse = {
  days: "2025-01-15,2025-01-16,2025-01-17",
};

// ---------------------------------------------------------------------------
// searchFlights
// ---------------------------------------------------------------------------

describe("searchFlights", () => {
  it("sends dateFrom and dateTo as query params", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    const params: SearchFlightsParams = {
      dateFrom: "20250115",
      dateTo: "20250116",
    };

    await searchFlights(client, params);

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1.1/ru/board");
    expect(url.searchParams.get("dateFrom")).toBe("20250115");
    expect(url.searchParams.get("dateTo")).toBe("20250116");
  });

  it("includes flightNumber when provided", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    await searchFlights(client, {
      flightNumber: "SU100",
      dateFrom: "20250115",
      dateTo: "20250116",
    });

    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("flightNumber")).toBe("SU100");
  });

  it("includes departure and arrival when provided", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    await searchFlights(client, {
      dateFrom: "20250115",
      dateTo: "20250116",
      departure: "SVO",
      arrival: "JFK",
    });

    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("departure")).toBe("SVO");
    expect(url.searchParams.get("arrival")).toBe("JFK");
  });

  // TIRREDESIGN-11: the backend ignores the raw 4-digit HHMM and only
  // honours HH:MM:SS. Convert at the API layer (Angular parity —
  // ApiFormatterService.formatTime). '2400' collapses to '23:59:59'.
  it("normalises timeFrom/timeTo to HH:MM:SS before sending", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    await searchFlights(client, {
      dateFrom: "20250115",
      dateTo: "20250116",
      timeFrom: "0800",
      timeTo: "2000",
    });

    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("timeFrom")).toBe("08:00:00");
    expect(url.searchParams.get("timeTo")).toBe("20:00:00");
  });

  it("collapses timeTo='2400' to '23:59:59'", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    await searchFlights(client, {
      dateFrom: "20250115",
      dateTo: "20250116",
      timeFrom: "0000",
      timeTo: "2400",
    });

    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("timeFrom")).toBe("00:00:00");
    expect(url.searchParams.get("timeTo")).toBe("23:59:59");
  });

  it("omits optional params when not provided", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    await searchFlights(client, {
      dateFrom: "20250115",
      dateTo: "20250116",
    });

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

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

    const result = await searchFlights(client, {
      dateFrom: "20250115",
      dateTo: "20250116",
    });

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

  it("throws ApiHttpError on 404", async () => {
    const { client } = createMockClient({ error: "not found" }, 404);

    await expect(
      searchFlights(client, { dateFrom: "20250115", dateTo: "20250116" }),
    ).rejects.toThrow("HTTP 404");
  });
});

// ---------------------------------------------------------------------------
// getFlightDetails
// ---------------------------------------------------------------------------

describe("getFlightDetails", () => {
  it("sends flights and dates as query params", async () => {
    const { client, mockFetch } = createMockClient(BOARD_RESPONSE);

    const params: FlightDetailsParams = {
      flights: "SU100",
      dates: "2025-01-15",
    };

    await getFlightDetails(client, params);

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1.1/ru/onlineboard/details");
    expect(url.searchParams.get("flights")).toBe("SU100");
    expect(url.searchParams.get("dates")).toBe("2025-01-15");
  });

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

    const result = await getFlightDetails(client, {
      flights: "SU100",
      dates: "2025-01-15",
    });

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

  it("throws ApiHttpError on 404", async () => {
    const { client } = createMockClient({ error: "not found" }, 404);

    await expect(
      getFlightDetails(client, { flights: "SU999", dates: "2025-01-15" }),
    ).rejects.toThrow("HTTP 404");
  });
});

// ---------------------------------------------------------------------------
// getCalendarDays
// ---------------------------------------------------------------------------

describe("getCalendarDays", () => {
  it("builds correct path for flight type", async () => {
    const { client, mockFetch } = createMockClient(DAYS_RESPONSE);

    const params: CalendarParams = {
      date: "2025-01-15",
      searchType: "flight",
      flightNumber: "SU100",
    };

    await getCalendarDays(client, params);

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1/ru/days/2025-01-15/31/flight/SU100/board/");
  });

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

    await getCalendarDays(client, {
      date: "2025-01-15",
      searchType: "departure",
      departure: "SVO",
    });

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

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

    await getCalendarDays(client, {
      date: "2025-01-15",
      searchType: "arrival",
      arrival: "JFK",
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1/ru/days/2025-01-15/31/arrival/JFK/board/");
  });

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

    await getCalendarDays(client, {
      date: "2025-01-15",
      searchType: "route",
      departure: "SVO",
      arrival: "JFK",
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe(
      "/flights/v1/ru/days/2025-01-15/31/route/SVO-JFK/board/",
    );
  });

  it("parses comma-separated days string into array", async () => {
    const { client } = createMockClient(DAYS_RESPONSE);

    const result = await getCalendarDays(client, {
      date: "2025-01-15",
      searchType: "flight",
      flightNumber: "SU100",
    });

    expect(result).toEqual(["2025-01-15", "2025-01-16", "2025-01-17"]);
  });

  it("parses bitmask days from the requested base date", async () => {
    const { client } = createMockClient({ days: "0101" });

    const result = await getCalendarDays(client, {
      date: "2025-01-15",
      searchType: "route",
      departure: "SVO",
      arrival: "JFK",
    });

    expect(result).toEqual(["20250116", "20250118"]);
  });

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

    const result = await getCalendarDays(client, {
      date: "2025-01-15",
      searchType: "flight",
      flightNumber: "SU100",
    });

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

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

    await expect(
      getCalendarDays(client, {
        date: "2025-01-15",
        searchType: "flight",
        flightNumber: "SU100",
      }),
    ).rejects.toThrow("HTTP 500");
  });
});
