import { describe, it, expect, vi } from "vitest";
import { ApiClient } from "@/shared/api/client";
import {
  searchSchedule,
  getScheduleDetails,
  getScheduleCalendarDays,
} from "./api";
import type {
  IScheduleSearchRequest,
  IScheduleResponse,
  IScheduleDetailsResponse,
  IScheduleDaysResponse,
} 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]);
}

function _extractBody(mockFetch: ReturnType<typeof vi.fn>): unknown {
  const call = mockFetch.mock.calls[0] as [string, RequestInit];
  return JSON.parse(call[1].body as string);
}

function extractMethod(mockFetch: ReturnType<typeof vi.fn>): string {
  const call = mockFetch.mock.calls[0] as [string, RequestInit];
  return call[1].method ?? "GET";
}

/** Minimal valid schedule response for testing */
const SCHEDULE_RESPONSE: IScheduleResponse = [];

const DETAILS_RESPONSE: IScheduleDetailsResponse = {
  data: {
    partners: ["SU"],
    routes: [],
    daysOfFlight: ["2025-01-15"],
  },
};

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

// ---------------------------------------------------------------------------
// searchSchedule
// ---------------------------------------------------------------------------

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

    const params: IScheduleSearchRequest = {
      departure: "SVO",
      arrival: "LED",
      dateFrom: "2025-01-15",
      dateTo: "2025-01-16",
    };

    await searchSchedule(client, params);

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/1/ru/schedule");
    expect(extractMethod(mockFetch)).toBe("GET");
  });

  it("advances dateTo by 1 day (half-open interval)", async () => {
    const { client, mockFetch } = createMockClient(SCHEDULE_RESPONSE);

    const params: IScheduleSearchRequest = {
      departure: "SVO",
      arrival: "LED",
      dateFrom: "2025-01-15",
      dateTo: "2025-01-16",
      connections: 1,
    };

    await searchSchedule(client, params);

    const url = extractUrl(mockFetch);
    expect(url.searchParams.get("departure")).toBe("SVO");
    expect(url.searchParams.get("arrival")).toBe("LED");
    expect(url.searchParams.get("dateFrom")).toBe("2025-01-15");
    expect(url.searchParams.get("dateTo")).toBe("2025-01-17");
    expect(url.searchParams.get("connections")).toBe("1");
  });

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

    const result = await searchSchedule(client, {
      departure: "SVO",
      arrival: "LED",
      dateFrom: "2025-01-15",
      dateTo: "2025-01-16",
    });

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

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

    await expect(
      searchSchedule(client, {
        departure: "SVO",
        arrival: "LED",
        dateFrom: "2025-01-15",
        dateTo: "2025-01-16",
      }),
    ).rejects.toThrow("HTTP 500");
  });
});

// ---------------------------------------------------------------------------
// getScheduleDetails
// ---------------------------------------------------------------------------

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

    await getScheduleDetails(client, {
      flights: ["SU0012", "SU0013"],
      dates: ["2025-01-15", "2025-01-16"],
      departure: "SVO",
      arrival: "LED",
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1.1/ru/schedule/details");
    expect(url.searchParams.get("flights[0]")).toBe("SU0012");
    expect(url.searchParams.get("flights[1]")).toBe("SU0013");
    expect(url.searchParams.get("dates[0]")).toBe("2025-01-15");
    expect(url.searchParams.get("dates[1]")).toBe("2025-01-16");
    expect(url.searchParams.get("departure")).toBe("SVO");
    expect(url.searchParams.get("arrival")).toBe("LED");
  });

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

    const result = await getScheduleDetails(client, {
      flights: ["SU0012"],
      dates: ["2025-01-15"],
      departure: "SVO",
      arrival: "LED",
    });

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

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

    await expect(
      getScheduleDetails(client, {
        flights: ["SU999"],
        dates: ["2025-01-15"],
        departure: "SVO",
        arrival: "LED",
      }),
    ).rejects.toThrow("HTTP 404");
  });
});

// ---------------------------------------------------------------------------
// getScheduleCalendarDays
// ---------------------------------------------------------------------------

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

    await getScheduleCalendarDays(client, {
      date: "2025-01-15",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

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

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

    await getScheduleCalendarDays(client, {
      date: "2025-01-15",
      departure: "SVO",
      arrival: "LED",
      connections: true,
    });

    const url = extractUrl(mockFetch);
    expect(url.pathname).toBe("/flights/v1/ru/days/2025-01-15/382/connections/SVO-LED-1/schedule/");
  });

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

    const result = await getScheduleCalendarDays(client, {
      date: "2025-01-15",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

    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: "101" });

    const result = await getScheduleCalendarDays(client, {
      date: "2025-01-15",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

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

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

    const result = await getScheduleCalendarDays(client, {
      date: "2025-01-15",
      departure: "SVO",
      arrival: "LED",
      connections: false,
    });

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

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

    await expect(
      getScheduleCalendarDays(client, {
        date: "2025-01-15",
        departure: "SVO",
        arrival: "LED",
        connections: false,
      }),
    ).rejects.toThrow("HTTP 500");
  });
});
