/**
 * Unit tests for Online-Board flight-list sort per TZ §4.1.13.2.
 */
import { describe, expect, it } from "vitest";
import { sortFlights, compareFlightsForMode } from "./sortFlights.js";
import type { ISimpleFlight } from "./types.js";

// ---------------------------------------------------------------------------
// Minimal flight factory helpers
// ---------------------------------------------------------------------------

function makeDirectFlight(
  id: string,
  depIso: string | null,
  arrIso: string | null,
): ISimpleFlight {
  return {
    id,
    routeType: "Direct",
    flightId: { carrier: "SU", date: "20260421", flightNumber: "0001", suffix: "" },
    flyingTime: "PT2H",
    operatingBy: {},
    status: "Scheduled",
    leg: {
      index: 0,
      dayChange: 0,
      status: "Scheduled",
      updated: "",
      flyingTime: "PT2H",
      operatingBy: {},
      flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
      departure: {
        checkingStatus: "",
        scheduled: { airport: "A", airportCode: "AAA", city: "A", cityCode: "AAA", countryCode: "RU" },
        times: {
          scheduledDeparture: depIso
            ? { dayChange: { value: 0, title: "" }, local: depIso, localTime: "", tzOffset: 0, utc: "" }
            : (undefined as never),
        },
      },
      arrival: {
        scheduled: { airport: "B", airportCode: "BBB", city: "B", cityCode: "BBB", countryCode: "RU" },
        times: {
          scheduledArrival: arrIso
            ? { dayChange: { value: 0, title: "" }, local: arrIso, localTime: "", tzOffset: 0, utc: "" }
            : (undefined as never),
        },
      },
      equipment: {},
    },
  } as unknown as ISimpleFlight;
}

function makeMultiLegFlight(
  id: string,
  firstDepIso: string | null,
  lastArrIso: string | null,
): ISimpleFlight {
  return {
    id,
    routeType: "MultiLeg",
    flightId: { carrier: "SU", date: "20260421", flightNumber: "0001", suffix: "" },
    flyingTime: "PT6H",
    operatingBy: {},
    status: "Scheduled",
    legs: [
      {
        index: 0,
        dayChange: 0,
        status: "Scheduled",
        updated: "",
        flyingTime: "PT3H",
        operatingBy: {},
        flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
        departure: {
          checkingStatus: "",
          scheduled: { airport: "A", airportCode: "AAA", city: "A", cityCode: "AAA", countryCode: "RU" },
          times: {
            scheduledDeparture: firstDepIso
              ? { dayChange: { value: 0, title: "" }, local: firstDepIso, localTime: "", tzOffset: 0, utc: "" }
              : (undefined as never),
          },
        },
        arrival: {
          scheduled: { airport: "C", airportCode: "CCC", city: "C", cityCode: "CCC", countryCode: "RU" },
          times: {
            scheduledArrival: { dayChange: { value: 0, title: "" }, local: "2026-04-21T15:00:00", localTime: "", tzOffset: 0, utc: "" },
          },
        },
        equipment: {},
      },
      {
        index: 1,
        dayChange: 0,
        status: "Scheduled",
        updated: "",
        flyingTime: "PT3H",
        operatingBy: {},
        flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
        departure: {
          checkingStatus: "",
          scheduled: { airport: "C", airportCode: "CCC", city: "C", cityCode: "CCC", countryCode: "RU" },
          times: {
            scheduledDeparture: { dayChange: { value: 0, title: "" }, local: "2026-04-21T16:00:00", localTime: "", tzOffset: 0, utc: "" },
          },
        },
        arrival: {
          scheduled: { airport: "B", airportCode: "BBB", city: "B", cityCode: "BBB", countryCode: "RU" },
          times: {
            scheduledArrival: lastArrIso
              ? { dayChange: { value: 0, title: "" }, local: lastArrIso, localTime: "", tzOffset: 0, utc: "" }
              : (undefined as never),
          },
        },
        equipment: {},
      },
    ],
  } as unknown as ISimpleFlight;
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("4.1.13.2-R: sortFlights — departure modes", () => {
  const modes = ["flight-number", "route", "departure"] as const;

  for (const mode of modes) {
    it(`${mode} mode: sorts by departure time ascending`, () => {
      const f1 = makeDirectFlight("f1", "2026-04-21T14:00:00", "2026-04-21T16:00:00");
      const f2 = makeDirectFlight("f2", "2026-04-21T08:00:00", "2026-04-21T10:00:00");
      const f3 = makeDirectFlight("f3", "2026-04-21T11:00:00", "2026-04-21T13:00:00");
      const sorted = sortFlights([f1, f2, f3], mode);
      expect(sorted.map((f) => f.id)).toEqual(["f2", "f3", "f1"]);
    });

    it(`${mode} mode: already-sorted input stays sorted`, () => {
      const f1 = makeDirectFlight("f1", "2026-04-21T06:00:00", null);
      const f2 = makeDirectFlight("f2", "2026-04-21T12:00:00", null);
      const sorted = sortFlights([f1, f2], mode);
      expect(sorted.map((f) => f.id)).toEqual(["f1", "f2"]);
    });
  }
});

describe("4.1.13.2-R: sortFlights — arrival mode", () => {
  it("sorts by arrival time ascending", () => {
    const f1 = makeDirectFlight("f1", "2026-04-21T06:00:00", "2026-04-21T18:00:00");
    const f2 = makeDirectFlight("f2", "2026-04-21T08:00:00", "2026-04-21T10:00:00");
    const f3 = makeDirectFlight("f3", "2026-04-21T07:00:00", "2026-04-21T14:00:00");
    const sorted = sortFlights([f1, f2, f3], "arrival");
    expect(sorted.map((f) => f.id)).toEqual(["f2", "f3", "f1"]);
  });

  it("uses last leg arrival for MultiLeg flights", () => {
    // f1: last leg arrives 20:00; f2: last leg arrives 09:00
    const f1 = makeMultiLegFlight("f1", "2026-04-21T06:00:00", "2026-04-21T20:00:00");
    const f2 = makeMultiLegFlight("f2", "2026-04-21T01:00:00", "2026-04-21T09:00:00");
    const sorted = sortFlights([f1, f2], "arrival");
    expect(sorted.map((f) => f.id)).toEqual(["f2", "f1"]);
  });
});

describe("4.1.13.2-R: sortFlights — day ordering", () => {
  it("yesterday < today < tomorrow (emerges from absolute timestamps)", () => {
    const yesterday = makeDirectFlight("yesterday", "2026-04-20T22:00:00", null);
    const today = makeDirectFlight("today", "2026-04-21T06:00:00", null);
    const tomorrow = makeDirectFlight("tomorrow", "2026-04-22T02:00:00", null);
    // Supply in scrambled order
    const sorted = sortFlights([tomorrow, today, yesterday], "departure");
    expect(sorted.map((f) => f.id)).toEqual(["yesterday", "today", "tomorrow"]);
  });

  it("same-day flights with different times sort by time", () => {
    const f1 = makeDirectFlight("early", "2026-04-21T05:30:00", null);
    const f2 = makeDirectFlight("late", "2026-04-21T23:45:00", null);
    const sorted = sortFlights([f2, f1], "departure");
    expect(sorted.map((f) => f.id)).toEqual(["early", "late"]);
  });
});

describe("4.1.13.2-R: sortFlights — MultiLeg uses first leg departure", () => {
  it("uses first leg departure for non-arrival modes", () => {
    // f1 first leg departs 14:00; f2 first leg departs 08:00
    const f1 = makeMultiLegFlight("f1", "2026-04-21T14:00:00", "2026-04-21T22:00:00");
    const f2 = makeMultiLegFlight("f2", "2026-04-21T08:00:00", "2026-04-21T16:00:00");
    const sorted = sortFlights([f1, f2], "route");
    expect(sorted.map((f) => f.id)).toEqual(["f2", "f1"]);
  });
});

describe("4.1.13.2-R: sortFlights — missing timestamps", () => {
  it("pushes flights with null departure time to the end", () => {
    const good = makeDirectFlight("good", "2026-04-21T10:00:00", null);
    const missing = makeDirectFlight("missing", null, null);
    const sorted = sortFlights([missing, good], "departure");
    expect(sorted.map((f) => f.id)).toEqual(["good", "missing"]);
  });

  it("two flights with null times keep relative order stable (both pushed to end)", () => {
    const m1 = makeDirectFlight("m1", null, null);
    const m2 = makeDirectFlight("m2", null, null);
    const good = makeDirectFlight("good", "2026-04-21T10:00:00", null);
    const sorted = sortFlights([m1, m2, good], "departure");
    // good must be first; m1 and m2 after
    expect(sorted[0]?.id).toBe("good");
    expect(new Set([sorted[1]?.id, sorted[2]?.id])).toEqual(new Set(["m1", "m2"]));
  });

  it("pushes flights with null arrival time to the end in arrival mode", () => {
    const good = makeDirectFlight("good", null, "2026-04-21T12:00:00");
    const missing = makeDirectFlight("missing", null, null);
    const sorted = sortFlights([missing, good], "arrival");
    expect(sorted.map((f) => f.id)).toEqual(["good", "missing"]);
  });
});

describe("4.1.13.2-R: sortFlights — edge cases", () => {
  it("returns empty array when given empty array", () => {
    expect(sortFlights([], "departure")).toEqual([]);
  });

  it("single flight is unchanged", () => {
    const f = makeDirectFlight("only", "2026-04-21T12:00:00", null);
    expect(sortFlights([f], "departure").map((x) => x.id)).toEqual(["only"]);
  });

  it("does not mutate the input array", () => {
    const f1 = makeDirectFlight("f1", "2026-04-21T14:00:00", null);
    const f2 = makeDirectFlight("f2", "2026-04-21T08:00:00", null);
    const input = [f1, f2];
    sortFlights(input, "departure");
    expect(input[0]?.id).toBe("f1"); // original untouched
  });

  it("compareFlightsForMode returns 0 for two flights with same timestamp", () => {
    const f1 = makeDirectFlight("f1", "2026-04-21T10:00:00", null);
    const f2 = makeDirectFlight("f2", "2026-04-21T10:00:00", null);
    expect(compareFlightsForMode("departure")(f1, f2)).toBe(0);
  });
});
