/**
 * Tests for OnlineBoardStartPage component.
 *
 * Verifies page layout rendering with filter and info sections,
 * query param building from popular requests, and navigation on click.
 *
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi, beforeEach, afterEach, afterAll } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { OnlineBoardStartPage, buildOnlineBoardPrefillState } from "./OnlineBoardStartPage.js";
import type { PopularRequest } from "@/features/popular-requests/types.js";
import {
  resetCrossSectionStore,
  setScheduleFilter,
  setBoardFilter,
  type ScheduleFilterSnapshot,
} from "@/shared/state/crossSectionNavigation.js";
import { sessionStore } from "@/shared/storage.js";

// ---------------------------------------------------------------------------
// Hook mocks for geo + viewport — controlled per test
// ---------------------------------------------------------------------------

let geoMockEnabled = false;

vi.mock("@/shared/hooks/useGeoCityDefault.js", () => ({
  useGeoCityDefault: (opts: { shouldApply: () => boolean; onCity: (code: string) => void }) => {
    // If the test enabled geo, fire it synchronously so we don't need async.
    if (geoMockEnabled && opts.shouldApply()) {
      opts.onCity("MOW");
    }
  },
}));

let isMobileMockValue = false;
vi.mock("@/shared/hooks/useIsMobileViewport.js", () => ({
  useIsMobileViewport: () => isMobileMockValue,
}));

const mockNavigate = vi.fn();

vi.mock("@modern-js/runtime/router", () => ({
  useNavigate: () => mockNavigate,
  useParams: () => ({ lang: "ru-ru" }),
  Link: ({ children, to, ...props }: { children: React.ReactNode; to: string; className?: string; [k: string]: unknown }) => (
    <a href={to} {...props}>{children}</a>
  ),
}));

vi.mock("@/i18n/provider.js", () => ({
  useTranslation: () => ({
    t: (key: string) => key,
  }),
}));

vi.mock("@/features/popular-requests/components/PopularRequestsPanel.js", () => ({
  PopularRequestsPanel: ({ onRequestClick }: { onRequestClick?: (r: PopularRequest) => void }) => (
    <div data-testid="popular-requests">
      <button
        data-testid="popular-click"
        onClick={() => onRequestClick?.({ mode: "FlightNumber", carrier: "SU", flightNumber: "0654", type: "Onlineboard" })}
      >
        Popular
      </button>
      <button
        data-testid="popular-click-schedule-route"
        onClick={() => onRequestClick?.({ mode: "Route", departure: "MOW", arrival: "MMK", type: "Schedule" })}
      >
        Schedule Route
      </button>
    </div>
  ),
}));

vi.mock("@/shared/hooks/useCitySearch.js", () => ({
  useCitySearch: () => ({ suggestions: [], search: vi.fn() }),
}));

vi.mock("@/shared/hooks/useSearchHistory.js", () => ({
  useSearchHistory: () => ({ items: [], add: vi.fn(), clear: vi.fn() }),
}));

vi.mock("@/shared/dictionaries/index.js", () => ({
  useDictionaries: () => ({ dictionaries: null, loading: false, error: null }),
  getCityCodeByAirportCode: () => undefined,
}));

vi.mock("../hooks/useCalendarDays.js", () => ({
  useCalendarDays: () => ({ days: [], loading: false }),
}));

vi.mock("@/ui/city-autocomplete/index.js", () => ({
  CityAutocomplete: (props: Record<string, unknown>) => (
    <input
      data-testid={`${(props["testIdPrefix"] as string) ?? "city-autocomplete"}-input`}
      placeholder={props["placeholder"] as string}
      defaultValue={(props["value"] as string) ?? ""}
    />
  ),
  SwapCityButton: (props: { onClick: () => void; testId?: string }) => (
    <button type="button" data-testid={props.testId} onClick={props.onClick} />
  ),
}));

// ---------------------------------------------------------------------------
// Pure function: buildOnlineBoardPrefillState
// ---------------------------------------------------------------------------

describe("buildOnlineBoardPrefillState", () => {
  // Freeze clock so todayYyyymmdd() returns a deterministic value.
  beforeEach(() => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date(2026, 4, 15, 12, 0, 0)); // 2026-05-15
  });
  afterAll(() => {
    vi.useRealTimers();
  });

  it("4.1.5-B1: FlightNumber click includes date=today, no time field", () => {
    const request: PopularRequest = {
      mode: "FlightNumber",
      carrier: "SU",
      flightNumber: "0654",
      type: "Onlineboard",
    };
    expect(buildOnlineBoardPrefillState(request)).toEqual({
      tab: "flight",
      flightNumber: "SU0654",
      date: "20260515",
    });
  });

  it("4.1.5-B2: Departure click includes date=today", () => {
    expect(
      buildOnlineBoardPrefillState({
        mode: "Departure",
        departure: "LED",
        type: "Onlineboard",
      }),
    ).toEqual({ tab: "route", departure: "LED", date: "20260515" });
  });

  it("4.1.5-B3: Arrival click includes date=today", () => {
    expect(
      buildOnlineBoardPrefillState({
        mode: "Arrival",
        arrival: "VKO",
        type: "Onlineboard",
      }),
    ).toEqual({ tab: "route", arrival: "VKO", date: "20260515" });
  });

  it("4.1.5-B4: Route click includes date=today", () => {
    expect(
      buildOnlineBoardPrefillState({
        mode: "Route",
        departure: "LED",
        arrival: "KRR",
        type: "Onlineboard",
      }),
    ).toEqual({ tab: "route", departure: "LED", arrival: "KRR", date: "20260515" });
  });
});

// ---------------------------------------------------------------------------
// Component tests
// ---------------------------------------------------------------------------

describe("OnlineBoardStartPage", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    sessionStore.clear();
  });

  it("renders start page with page layout structure", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByTestId("online-board-start")).toBeTruthy();
  });

  it("renders the page title as h1", () => {
    render(<OnlineBoardStartPage />);
    const heading = screen.getAllByText("BOARD.TITLE");
    const h1 = heading.find((el) => el.tagName === "H1");
    expect(h1).toBeTruthy();
  });

  it("renders the info section heading", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByText("BOARD.BOARD-START")).toBeTruthy();
  });

  it("renders 4 info tiles", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByText("BOARD.BOARD-START-TITLE1")).toBeTruthy();
    expect(screen.getByText("BOARD.BOARD-START-TITLE2")).toBeTruthy();
    expect(screen.getByText("BOARD.BOARD-START-TITLE3")).toBeTruthy();
    expect(screen.getByText("BOARD.BOARD-START-TITLE4")).toBeTruthy();
  });

  // ---------------------------------------------------------------------------
  // TZ §4.1.6: start-page info section per TZ Table 8
  // ---------------------------------------------------------------------------

  describe("4.1.6: start-page info section per TZ Table 8", () => {
    it("4.1.6-R-info-heading: renders 'Что такое Онлайн-табло...' heading", () => {
      render(<OnlineBoardStartPage />);
      expect(screen.getByText("BOARD.BOARD-START")).toBeTruthy();
    });

    it("4.1.6-R-info-block-1: renders 'Актуальная информация' block with description", () => {
      render(<OnlineBoardStartPage />);
      expect(screen.getByText("BOARD.BOARD-START-TITLE1")).toBeTruthy();
      expect(screen.getByText("BOARD.BOARD-START-TITLE1-DESCRIPTION")).toBeTruthy();
    });

    it("4.1.6-R-info-block-2: renders 'Информация об услугах' block with description", () => {
      render(<OnlineBoardStartPage />);
      expect(screen.getByText("BOARD.BOARD-START-TITLE2")).toBeTruthy();
      expect(screen.getByText("BOARD.BOARD-START-TITLE2-DESCRIPTION")).toBeTruthy();
    });

    it("4.1.6-R-info-block-3: renders 'Купить билет' block with description", () => {
      render(<OnlineBoardStartPage />);
      expect(screen.getByText("BOARD.BOARD-START-TITLE3")).toBeTruthy();
      expect(screen.getByText("BOARD.BOARD-START-TITLE3-DESCRIPTION")).toBeTruthy();
    });

    it("4.1.6-R-info-block-4: renders 'Расписание' info block with description", () => {
      render(<OnlineBoardStartPage />);
      expect(screen.getByText("BOARD.BOARD-START-TITLE4")).toBeTruthy();
      expect(screen.getByText("BOARD.BOARD-START-TITLE4-DESCRIPTION")).toBeTruthy();
    });

    it("4.1.6-R-popular-heading: renders 'Популярные разделы' via PopularRequestsPanel", () => {
      render(<OnlineBoardStartPage />);
      expect(screen.getByTestId("popular-requests")).toBeTruthy();
    });
  });

  it("renders the filter component", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByTestId("flight-filter")).toBeTruthy();
  });

  it("renders page tabs", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByTestId("onlineboard-tab")).toBeTruthy();
    expect(screen.getByTestId("schedule-tab")).toBeTruthy();
  });

  it("renders the popular requests panel", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByTestId("popular-requests")).toBeTruthy();
  });

  it("renders breadcrumbs", () => {
    render(<OnlineBoardStartPage />);
    expect(screen.getByTestId("breadcrumbs")).toBeTruthy();
  });

  it.skip("renders feedback button", () => {
    // Feedback button not yet implemented — stub kept for future Gap B.4 work
    render(<OnlineBoardStartPage />);
    expect(screen.getByTestId("feedback-button")).toBeTruthy();
  });

  it("does not navigate on same-page popular click (Onlineboard type)", () => {
    render(<OnlineBoardStartPage />);
    const btn = screen.getByTestId("popular-click");
    fireEvent.click(btn);
    // Same-page click — the filter remounts via key bump, no nav.
    expect(mockNavigate).not.toHaveBeenCalled();
  });

  it("TIRREDESIGN-9: schedule Route popular click opens Schedule with prefilled data", () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date(2026, 4, 15, 12, 0, 0));
    try {
      render(<OnlineBoardStartPage />);
      fireEvent.click(screen.getByTestId("popular-click-schedule-route"));
      expect(mockNavigate).toHaveBeenCalledWith("/ru-ru/schedule");
      expect(JSON.parse(sessionStore.getRaw("afl-prefill:schedule") ?? "{}")).toEqual({
        departure: "MOW",
        arrival: "MMK",
        withReturn: false,
        dateFrom: "20260514",
        dateTo: "20260517",
      });
    } finally {
      vi.useRealTimers();
    }
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.8: cross-section hydration tests (Board ↔ Schedule)
// ---------------------------------------------------------------------------

describe("4.1.8: Online-Board hydrates from cross-section store on mount", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    resetCrossSectionStore();
  });

  it("4.1.8: hydrates departure + arrival from Schedule filter (projected) when no own state", () => {
    const schedSnap: ScheduleFilterSnapshot = {
      mode: "route", departure: "MOW", arrival: "LED",
      dateFrom: "20260511", dateTo: "20260517",
      timeFrom: "0000", timeTo: "2400",
      onlyDirect: false, showReturn: false, searchExecuted: true,
    };
    setScheduleFilter(schedSnap);
    render(<OnlineBoardStartPage />);
    // departure input should be seeded with "MOW"
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("MOW");
    const arrInput = screen.getByTestId("route-arrival-input") as HTMLInputElement;
    expect(arrInput.defaultValue).toBe("LED");
  });

  it("4.1.8: uses own board snapshot (not schedule) when board state is set", () => {
    // Set board first (own state)
    setBoardFilter({
      mode: "route", departure: "KZN", arrival: "AER",
      date: "20260515", timeFrom: "0000", timeTo: "2400",
      searchExecuted: false,
    });
    // Also set schedule — it should be ignored since board is present
    setScheduleFilter({
      mode: "route", departure: "SVO", arrival: "VVO",
      dateFrom: "20260511", dateTo: "20260517",
      timeFrom: "0000", timeTo: "2400",
      onlyDirect: false, showReturn: false, searchExecuted: true,
    });
    render(<OnlineBoardStartPage />);
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("KZN");
  });

  it("4.1.8: renders empty filter when both board and schedule store are empty", () => {
    // No cross-section state set
    render(<OnlineBoardStartPage />);
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.1-R1/R3: first-entry geolocation auto-fill
// ---------------------------------------------------------------------------

describe("4.1.1-R1/R3: first-entry geolocation + defaults", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    resetCrossSectionStore();
    geoMockEnabled = false;
    isMobileMockValue = false;
  });

  afterEach(() => {
    geoMockEnabled = false;
    isMobileMockValue = false;
  });

  it("4.1.1-R1: populates departure with geolocated city when no stored filter", () => {
    geoMockEnabled = true; // mock will call onCity("MOW") synchronously on shouldApply()
    render(<OnlineBoardStartPage />);
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("MOW");
  });

  it("4.1.1-R3: does not populate departure when geolocation is denied (geoMockEnabled=false)", () => {
    // geoMockEnabled stays false → useGeoCityDefault mock never fires onCity
    render(<OnlineBoardStartPage />);
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("");
  });

  it("4.1.1-R1: does not override departure when cross-section store already has board filter", () => {
    setBoardFilter({
      mode: "route",
      departure: "LED",
      arrival: "KGD",
      date: "20260515",
      timeFrom: "0000",
      timeTo: "2400",
      searchExecuted: false,
    });
    // shouldApply() checks getBoardFilter() — returns LED snapshot → returns false → onCity not called
    geoMockEnabled = true;
    render(<OnlineBoardStartPage />);
    // Departure should be LED from the board snapshot, NOT overridden by geo.
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("LED");
  });

  it("4.1.1-R1: does not override departure when prefill already has departure set", () => {
    // Schedule filter sets a departure so prefill.departure = "SVO"
    setScheduleFilter({
      mode: "route",
      departure: "SVO",
      arrival: "",
      dateFrom: "20260515",
      dateTo: "20260521",
      timeFrom: "0000",
      timeTo: "2400",
      onlyDirect: false,
      showReturn: false,
      searchExecuted: true,
    });
    // shouldApply checks !getBoardFilter() (true) && !prefill.departure (false → SVO) → returns false
    geoMockEnabled = true;
    render(<OnlineBoardStartPage />);
    const depInput = screen.getByTestId("route-departure-input") as HTMLInputElement;
    expect(depInput.defaultValue).toBe("SVO");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.1-R4: Online-Board Маршрут time default on desktop = 00:00-24:00
// ---------------------------------------------------------------------------

describe("4.1.1-R4: Online-Board time default desktop = 00:00-24:00", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    resetCrossSectionStore();
    geoMockEnabled = false;
    isMobileMockValue = false; // desktop
  });

  it("desktop viewport passes 00:00-24:00 as initial time range to filter", () => {
    render(<OnlineBoardStartPage />);
    // The time-selector shows "HH:MM — HH:MM" text; default is 00:00 — 24:00.
    const timeSelector = screen.getByTestId("time-selector");
    expect(timeSelector.textContent).toContain("00:00");
    expect(timeSelector.textContent).toContain("24:00");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.9: Today/Tomorrow label substitution on Board calendar input
// Wiring is verified via the dateLabels.ts unit tests; here we verify that
// the filter renders the calendar and imports the helper without errors,
// and that the formatDateWithTodayTomorrow function itself behaves correctly.
// (Deep PrimeReact Calendar DOM-value mutation is covered in dateLabels.test.ts)
// ---------------------------------------------------------------------------

describe("4.1.9-R: Board calendar Today/Tomorrow label integration", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    resetCrossSectionStore();
    geoMockEnabled = false;
    isMobileMockValue = false;
    vi.useFakeTimers();
    vi.setSystemTime(new Date(2026, 4, 15, 12, 0, 0)); // 2026-05-15
  });

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

  it("4.1.9-R: filter renders without error when today's date is pre-populated", () => {
    setBoardFilter({
      mode: "route",
      departure: "SVO",
      arrival: "LED",
      date: "20260515", // today (frozen clock)
      timeFrom: "0000",
      timeTo: "2400",
      searchExecuted: false,
    });
    expect(() => render(<OnlineBoardStartPage />)).not.toThrow();
    // The Calendar input is present in the DOM
    expect(document.getElementById("route-date-input")).toBeTruthy();
  });

  it("4.1.9-R: filter renders without error when tomorrow's date is pre-populated", () => {
    setBoardFilter({
      mode: "route",
      departure: "SVO",
      arrival: "",
      date: "20260516", // tomorrow (frozen clock at 2026-05-15)
      timeFrom: "0000",
      timeTo: "2400",
      searchExecuted: false,
    });
    expect(() => render(<OnlineBoardStartPage />)).not.toThrow();
    expect(document.getElementById("route-date-input")).toBeTruthy();
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.1-R5: Online-Board Маршрут time default on mobile = -1h/+3h
// ---------------------------------------------------------------------------

describe("4.1.1-R5: Online-Board time default mobile = -1h/+3h", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    resetCrossSectionStore();
    geoMockEnabled = false;
    isMobileMockValue = true; // mobile
  });

  afterEach(() => {
    vi.useRealTimers();
    isMobileMockValue = false;
  });

  it("mobile viewport at 10:00 uses 09:00-13:00 time range", () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date(2026, 4, 15, 10, 0, 0));
    render(<OnlineBoardStartPage />);
    const timeSelector = screen.getByTestId("time-selector");
    // -1h from 10:00 = 09:00, +3h from 10:00 = 13:00
    expect(timeSelector.textContent).toContain("09:00");
    expect(timeSelector.textContent).toContain("13:00");
  });

  it("mobile viewport at 00:30 clamps from-time to 00:00 (not negative)", () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date(2026, 4, 15, 0, 30, 0));
    render(<OnlineBoardStartPage />);
    const timeSelector = screen.getByTestId("time-selector");
    // -1h from 00:30 would be -00:30 → clamps to 00:00; +3h = 03:30
    expect(timeSelector.textContent).toContain("00:00");
    expect(timeSelector.textContent).toContain("03:30");
  });
});
