// @vitest-environment jsdom
/**
 * Tests for ScheduleFlightBody – TZ §4.1.14.4 compliance assertions.
 *
 * Coverage:
 * - Direct flight: leg number, flight-id, operator logo, dep/arr times, duration
 * - Transfer box between legs (multi-leg + connecting): label + duration + station info
 * - Buy button visibility gate: >2h ahead AND <330 days ahead (TZ §4.1.14.4.4)
 * - Status button visibility gate: same-day departure only (TZ §4.1.14.4.5)
 * - Share button always present
 * - Actions area rendered at bottom
 */

import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import type { ISimpleFlight, IFlightLeg } from "@/features/online-board/types.js";

// ── i18n stub ─────────────────────────────────────────────────────────────────
vi.mock("@/i18n/provider.js", () => ({
  useTranslation: () => ({ t: (k: string) => k }),
}));
vi.mock("@/i18n/useLocale.js", () => ({
  useLocale: () => ({ language: "ru", locale: "ru-ru" }),
}));

// ── UI stubs ──────────────────────────────────────────────────────────────────
vi.mock("@/ui/flights/TimeGroup.js", () => ({
  TimeGroup: ({ scheduled }: { scheduled: string }) => (
    <span data-testid="time-group">{scheduled}</span>
  ),
}));
vi.mock("@/ui/flights/StationDisplay.js", () => ({
  StationDisplay: ({ airportCode }: { airportCode: string }) => (
    <span data-testid="station-display">{airportCode}</span>
  ),
}));
vi.mock("@/ui/flights/OperatorLogo.js", () => ({
  OperatorLogo: ({ carrier }: { carrier: string }) => (
    <span data-testid="operator-logo">{carrier}</span>
  ),
}));
vi.mock(
  "@/features/online-board/components/BoardDetailsHeader/FlightActions.js",
  () => ({
    FlightActions: () => <div data-testid="flight-actions" />,
  }),
);

import { ScheduleFlightBody } from "./ScheduleFlightBody.js";

// ── Helpers ───────────────────────────────────────────────────────────────────

function makeTimesSet(utc: string, local: string) {
  return {
    scheduledDeparture: {
      utc,
      local,
      localTime: local,
      tzOffset: 0,
      dayChange: { value: 0, title: "" },
    },
  };
}

function makeArrivalTimesSet(utc: string, local: string) {
  return {
    scheduledArrival: {
      utc,
      local,
      localTime: local,
      tzOffset: 0,
      dayChange: { value: 0, title: "" },
    },
  };
}

function makeStation(code: string, city: string, airport: string): {
  scheduled: { airportCode: string; city: string; airport: string; cityCode: string; countryCode: string };
  terminal: string;
} {
  return {
    scheduled: {
      airportCode: code,
      city,
      airport,
      cityCode: code,
      countryCode: "RU",
    },
    terminal: "",
  };
}

function makeLeg(depUtc: string, depLocal: string, arrUtc: string, arrLocal: string, depCode = "SVO", arrCode = "LED"): IFlightLeg {
  return {
    departure: {
      ...makeStation(depCode, "Moscow", "Sheremetyevo"),
      times: makeTimesSet(depUtc, depLocal),
      checkingStatus: "",
    },
    arrival: {
      ...makeStation(arrCode, "Saint Petersburg", "Pulkovo"),
      times: makeArrivalTimesSet(arrUtc, arrLocal),
    },
    flyingTime: "02:00:00",
    equipment: {},
    operatingBy: {},
    status: "Scheduled",
    flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
    dayChange: 0,
    index: 0,
    updated: "",
  } as unknown as IFlightLeg;
}

function makeDirectFlight(depUtc: string, depLocal = "10:00:00"): ISimpleFlight {
  const arrUtc = new Date(new Date(depUtc).getTime() + 2 * 3600 * 1000).toISOString();
  return {
    routeType: "Direct",
    id: "test-direct",
    flyingTime: "02:00:00",
    status: "Scheduled",
    flightId: { carrier: "SU", flightNumber: "1234", date: depUtc.slice(0, 10) },
    operatingBy: {},
    leg: makeLeg(depUtc, depLocal, arrUtc, "12:00:00"),
  } as unknown as ISimpleFlight;
}

function makeMultiLegFlight(depUtc: string): ISimpleFlight {
  const mid = new Date(new Date(depUtc).getTime() + 2 * 3600 * 1000).toISOString();
  const midNext = new Date(new Date(mid).getTime() + 1.5 * 3600 * 1000).toISOString();
  const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
  return {
    routeType: "MultiLeg",
    id: "test-multileg",
    flyingTime: "06:30:00",
    status: "Scheduled",
    flightId: { carrier: "SU", flightNumber: "5678", date: depUtc.slice(0, 10) },
    operatingBy: {},
    legs: [
      makeLeg(depUtc, "10:00", mid, "12:00", "SVO", "KJA"),
      makeLeg(midNext, "13:30", arr, "16:30", "KJA", "LED"),
    ],
  } as unknown as ISimpleFlight;
}

function makeConnectingFlight(depUtc: string): ISimpleFlight {
  const mid = new Date(new Date(depUtc).getTime() + 2 * 3600 * 1000).toISOString();
  const midNext = new Date(new Date(mid).getTime() + 2 * 3600 * 1000).toISOString();
  const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
  return {
    routeType: "MultiLeg",
    id: "su6188+su6233",
    flyingTime: "07:00:00",
    status: "Scheduled",
    flightId: { carrier: "SU", flightNumber: "6188", date: depUtc.slice(0, 10) },
    operatingBy: {},
    legs: [
      makeLeg(depUtc, "10:00", mid, "12:00", "SVO", "KUF"),
      makeLeg(midNext, "14:00", arr, "17:00", "KUF", "LED"),
    ],
    _childFlightIds: [
      { carrier: "SU", flightNumber: "6188" },
      { carrier: "SU", flightNumber: "6233" },
    ],
  } as unknown as ISimpleFlight;
}

// ── Buy-gate helpers ──────────────────────────────────────────────────────────

/** 10 days from now — used by the structure tests below to exercise a
 *  future-dated flight against the leg-body renderer. */
const FUTURE_10D = new Date(Date.now() + 10 * 24 * 3600 * 1000).toISOString();

// ── Tests ─────────────────────────────────────────────────────────────────────

describe("ScheduleFlightBody – TZ §4.1.14.4", () => {

  describe("Direct flight structure", () => {
    it("renders schedule-flight-body container", () => {
      render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
      expect(screen.getByTestId("schedule-flight-body")).toBeTruthy();
    });

    it("renders leg number badge '1' for single leg", () => {
      render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
      expect(screen.getByText("1")).toBeTruthy();
    });

    it("renders flight number SU 1234", () => {
      render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
      expect(screen.getByText("SU 1234")).toBeTruthy();
    });

    it("renders operator logo for the leg carrier", () => {
      render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
      expect(screen.getByTestId("operator-logo")).toBeTruthy();
    });

    it("renders departure and arrival station codes", () => {
      render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
      const stations = screen.getAllByTestId("station-display");
      expect(stations.some((s) => s.textContent === "SVO")).toBe(true);
      expect(stations.some((s) => s.textContent === "LED")).toBe(true);
    });

    it("does NOT render the action strip when showActions is omitted", () => {
      // Schedule details page omits `showActions` because its page-level
      // summary header owns share/buy. Mirrors Angular's
      // `flight-schedule-details [share]=false [buy]=false [print]=false`.
      render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
      expect(screen.queryByTestId("schedule-flight-body-actions")).toBeNull();
    });

    it("renders the action strip when showActions is true", () => {
      // Search results page opts in to mirror Angular's
      // `<flight-details-body-actions>` → `<flight-actions>` (defaults all
      // true except print).
      render(
        <ScheduleFlightBody
          flight={makeDirectFlight(FUTURE_10D)}
          showActions
        />,
      );
      expect(screen.getByTestId("schedule-flight-body-actions")).toBeTruthy();
      expect(screen.getByTestId("flight-actions")).toBeTruthy();
    });
  });

  describe("Multi-leg flight structure", () => {
    it("renders 2 leg rows for multi-leg flight", () => {
      render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
      // Both leg numbers should appear
      const nums = screen.getAllByText(/^[12]$/);
      expect(nums.length).toBeGreaterThanOrEqual(2);
    });

    it("renders horizontal timeline for multi-leg (TZ §4.1.14.4 timeline row)", () => {
      render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
      expect(screen.getByTestId("schedule-timeline")).toBeTruthy();
    });

    it("renders transfer box between legs", () => {
      render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
      expect(screen.getByTestId("flight-transfer")).toBeTruthy();
    });

    it("shows SHARED.INTERMEDIATE-LANDING-PLURAL-ONE for multi-leg transfer", () => {
      render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
      expect(screen.getByText("SHARED.INTERMEDIATE-LANDING-PLURAL-ONE")).toBeTruthy();
    });

    it("renders transfer content area in transfer box", () => {
      render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
      // Transfer box rendered; content (icon + label) is present
      const transfer = screen.getByTestId("flight-transfer");
      expect(transfer.textContent).toContain("SHARED.INTERMEDIATE-LANDING-PLURAL-ONE");
    });
  });

  describe("Connecting flight structure – TZ §4.1.16.6", () => {
    it("renders separate flight numbers per leg for connecting flight", () => {
      render(<ScheduleFlightBody flight={makeConnectingFlight(FUTURE_10D)} />);
      expect(screen.getByText("SU 6188")).toBeTruthy();
      expect(screen.getByText("SU 6233")).toBeTruthy();
    });

    it("shows SHARED.FLIGHT-TRANSFER label for connecting transfer box", () => {
      render(<ScheduleFlightBody flight={makeConnectingFlight(FUTURE_10D)} />);
      expect(screen.getByText("SHARED.FLIGHT-TRANSFER")).toBeTruthy();
    });

    it("transfer box shows single station when same airport (no change)", () => {
      // Both legs land/depart at the same airport code and city (KUF)
      render(<ScheduleFlightBody flight={makeConnectingFlight(FUTURE_10D)} />);
      const transfer = screen.getByTestId("flight-transfer");
      // Should NOT show the → arrow; look specifically at the stations div
      const stationsDiv = transfer.querySelector(".schedule-flight-body__transfer-stations");
      expect(stationsDiv).toBeTruthy();
      // No arrow span in the stations area (the svg icon is outside this div)
      const arrowSpans = stationsDiv?.querySelectorAll("span[aria-hidden='true']") ?? [];
      expect(arrowSpans.length).toBe(0);
    });

    it("transfer box shows two stations when airports differ (TZ §4.1.16.6 rule 12)", () => {
      // Flight where leg1 arr = SVO and leg2 dep = VKO (different airports)
      const dep = FUTURE_10D;
      const mid = new Date(new Date(dep).getTime() + 2 * 3600 * 1000).toISOString();
      const midNext = new Date(new Date(mid).getTime() + 2 * 3600 * 1000).toISOString();
      const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
      const stationChangeFlight: ISimpleFlight = {
        routeType: "MultiLeg",
        id: "su6188+su6233-stn-change",
        flyingTime: "07:00:00",
        status: "Scheduled",
        flightId: { carrier: "SU", flightNumber: "6188", date: dep.slice(0, 10) },
        operatingBy: {},
        legs: [
          { ...makeLeg(dep, "10:00", mid, "12:00", "SVO", "VKO"),
            arrival: { ...makeLeg(dep, "10:00", mid, "12:00", "SVO", "VKO").arrival,
              scheduled: { airportCode: "VKO", city: "Moscow", airport: "Vnukovo", cityCode: "MOW", countryCode: "RU" } } },
          { ...makeLeg(midNext, "14:00", arr, "17:00", "SVO", "LED"),
            departure: { ...makeLeg(midNext, "14:00", arr, "17:00", "SVO", "LED").departure,
              scheduled: { airportCode: "SVO", city: "Moscow", airport: "Sheremetyevo", cityCode: "MOW", countryCode: "RU" } } },
        ],
        _childFlightIds: [
          { carrier: "SU", flightNumber: "6188" },
          { carrier: "SU", flightNumber: "6233" },
        ],
      } as unknown as ISimpleFlight;
      render(<ScheduleFlightBody flight={stationChangeFlight} />);
      const transfer = screen.getByTestId("flight-transfer");
      // Should show arrow → between the two different airports
      const arrowSpans = transfer.querySelectorAll("span[aria-hidden='true']");
      expect(arrowSpans.length).toBeGreaterThanOrEqual(1);
      expect(transfer.textContent).toContain("Vnukovo");
      expect(transfer.textContent).toContain("Sheremetyevo");
    });

    it("transfer box shows both terminals when same airport but different terminals (TZ §4.1.16.6 rule 14)", () => {
      // Leg1 arr = SVO terminal D; Leg2 dep = SVO terminal E
      const dep = FUTURE_10D;
      const mid = new Date(new Date(dep).getTime() + 2 * 3600 * 1000).toISOString();
      const midNext = new Date(new Date(mid).getTime() + 1.5 * 3600 * 1000).toISOString();
      const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
      const leg1 = makeLeg(dep, "10:00", mid, "12:00", "SVO", "SVO");
      const leg2 = makeLeg(midNext, "13:30", arr, "16:30", "SVO", "LED");
      // Patch terminals
      (leg1 as unknown as Record<string, unknown>).arrival = {
        ...(leg1 as unknown as { arrival: unknown }).arrival as Record<string, unknown>,
        terminal: "D",
        scheduled: { airportCode: "SVO", city: "Moscow", airport: "Sheremetyevo", cityCode: "MOW", countryCode: "RU" },
      };
      (leg2 as unknown as Record<string, unknown>).departure = {
        ...(leg2 as unknown as { departure: unknown }).departure as Record<string, unknown>,
        terminal: "E",
        scheduled: { airportCode: "SVO", city: "Moscow", airport: "Sheremetyevo", cityCode: "MOW", countryCode: "RU" },
        checkingStatus: "",
      };
      const terminalChangeFlight: ISimpleFlight = {
        routeType: "MultiLeg",
        id: "su-terminal-change",
        flyingTime: "05:30:00",
        status: "Scheduled",
        flightId: { carrier: "SU", flightNumber: "1000", date: dep.slice(0, 10) },
        operatingBy: {},
        legs: [leg1, leg2],
        _childFlightIds: [
          { carrier: "SU", flightNumber: "1000" },
          { carrier: "SU", flightNumber: "1001" },
        ],
      } as unknown as ISimpleFlight;
      render(<ScheduleFlightBody flight={terminalChangeFlight} />);
      const transfer = screen.getByTestId("flight-transfer");
      // Both terminal codes must appear
      expect(transfer.textContent).toContain("D");
      expect(transfer.textContent).toContain("E");
      // Arrow must be present between them
      const arrowSpans = transfer.querySelectorAll("span[aria-hidden='true']");
      expect(arrowSpans.length).toBeGreaterThanOrEqual(1);
    });

    it("transfer box terminal text uses arrTerminal variable (not leg.arrival.terminal directly)", () => {
      // Regression: same airport, same terminal should show single station without arrow
      const dep = FUTURE_10D;
      const mid = new Date(new Date(dep).getTime() + 2 * 3600 * 1000).toISOString();
      const midNext = new Date(new Date(mid).getTime() + 1.5 * 3600 * 1000).toISOString();
      const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
      const leg1 = makeLeg(dep, "10:00", mid, "12:00", "SVO", "SVO");
      const leg2 = makeLeg(midNext, "13:30", arr, "16:30", "SVO", "LED");
      // Same terminal B on both
      (leg1 as unknown as Record<string, unknown>).arrival = {
        ...(leg1 as unknown as { arrival: unknown }).arrival as Record<string, unknown>,
        terminal: "B",
        scheduled: { airportCode: "SVO", city: "Moscow", airport: "Sheremetyevo", cityCode: "MOW", countryCode: "RU" },
      };
      (leg2 as unknown as Record<string, unknown>).departure = {
        ...(leg2 as unknown as { departure: unknown }).departure as Record<string, unknown>,
        terminal: "B",
        scheduled: { airportCode: "SVO", city: "Moscow", airport: "Sheremetyevo", cityCode: "MOW", countryCode: "RU" },
        checkingStatus: "",
      };
      const sametermFlight: ISimpleFlight = {
        routeType: "MultiLeg",
        id: "su-same-terminal",
        flyingTime: "05:30:00",
        status: "Scheduled",
        flightId: { carrier: "SU", flightNumber: "1000", date: dep.slice(0, 10) },
        operatingBy: {},
        legs: [leg1, leg2],
        _childFlightIds: [
          { carrier: "SU", flightNumber: "1000" },
          { carrier: "SU", flightNumber: "1001" },
        ],
      } as unknown as ISimpleFlight;
      render(<ScheduleFlightBody flight={sametermFlight} />);
      const transfer = screen.getByTestId("flight-transfer");
      // No → arrow for same-airport same-terminal
      const arrowSpans = transfer.querySelectorAll("span[aria-hidden='true']");
      expect(arrowSpans.length).toBe(0);
      // Terminal B should appear
      expect(transfer.textContent).toContain("B");
    });
  });

  // Share/Buy/Status button tests were removed: those affordances
  // relocated from the per-leg body to the page-level summary header.
  // Their visibility is exercised by BoardDetailsHeader/FlightActions
  // tests plus the schedule-details summary e2e spec.

  // ────────────────────────────────────────────────────────────────────────────
  // TZ §4.1.16.7 — Intermediate landing vs transfer duration (UTC fix)
  // ────────────────────────────────────────────────────────────────────────────

  describe("Transfer duration – TZ §4.1.16.7 (UTC-based, not local)", () => {
    it("computes ground time from UTC (90 min = 1ч. 30мин.)", () => {
      // leg1 arr UTC: 10:00Z, leg2 dep UTC: 11:30Z → 90 min
      const depUtc = "2026-08-10T08:00:00Z";
      const midUtc = "2026-08-10T10:00:00Z"; // arr leg1
      const midNextUtc = "2026-08-10T11:30:00Z"; // dep leg2
      const arrUtc = "2026-08-10T14:30:00Z";
      const flight: ISimpleFlight = {
        routeType: "MultiLeg",
        id: "duration-test",
        flyingTime: "06:30:00",
        status: "Scheduled",
        flightId: { carrier: "SU", flightNumber: "9999", date: "2026-08-10" },
        operatingBy: {},
        legs: [
          makeLeg(depUtc, "11:00", midUtc, "13:00", "SVO", "KJA"),
          makeLeg(midNextUtc, "14:30", arrUtc, "17:30", "KJA", "LED"),
        ],
      } as unknown as ISimpleFlight;
      render(<ScheduleFlightBody flight={flight} />);
      // 90 min = "1ч. 30мин." (ru locale)
      const transfer = screen.getByTestId("flight-transfer");
      expect(transfer.textContent).toContain("1ч. 30мин.");
    });

    it("label is Промежуточная посадка for same flight-number multi-leg (§4.1.16.7)", () => {
      render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
      expect(screen.getByText("SHARED.INTERMEDIATE-LANDING-PLURAL-ONE")).toBeTruthy();
    });

    it("label is Пересадка for connecting (different flight numbers) (§4.1.16.7)", () => {
      render(<ScheduleFlightBody flight={makeConnectingFlight(FUTURE_10D)} />);
      expect(screen.getByText("SHARED.FLIGHT-TRANSFER")).toBeTruthy();
    });
  });
});
