/**
 * Unit tests for TZ §4.1.15.7 timeline time-calculation algorithm.
 *
 * @vitest-environment node
 */

import { describe, it, expect } from "vitest";
import { computeTimelineCalc, formatTimelineDuration } from "./timelineTime.js";
import type { TimelineInputs } from "./timelineTime.js";

// ---------------------------------------------------------------------------
// formatTimelineDuration — R98
// ---------------------------------------------------------------------------

describe("formatTimelineDuration (R98)", () => {
  it("45 min → only minutes part (no leading hours)", () => {
    expect(formatTimelineDuration(45, "ru")).toBe("45мин.");
  });

  it("0 min → 0мин.", () => {
    expect(formatTimelineDuration(0, "ru")).toBe("0мин.");
  });

  it("60 min → 1ч. 0мин.", () => {
    expect(formatTimelineDuration(60, "ru")).toBe("1ч. 0мин.");
  });

  it("90 min → 1ч. 30мин.", () => {
    expect(formatTimelineDuration(90, "ru")).toBe("1ч. 30мин.");
  });

  it("1440 min (1 day) → 1д. 0ч. 0мин.", () => {
    expect(formatTimelineDuration(1440, "ru")).toBe("1д. 0ч. 0мин.");
  });

  it("1505 min (1d 1h 5m) → 1д. 1ч. 5мин.", () => {
    expect(formatTimelineDuration(1505, "ru")).toBe("1д. 1ч. 5мин.");
  });

  it("1445 min (1d 0h 5m) → 1д. 0ч. 5мин. (hours shown when days present)", () => {
    expect(formatTimelineDuration(1445, "ru")).toBe("1д. 0ч. 5мин.");
  });

  it("English locale uses en units", () => {
    expect(formatTimelineDuration(90, "en")).toBe("1h 30m");
  });

  it("English with days", () => {
    expect(formatTimelineDuration(1505, "en")).toBe("1d 1h 5m");
  });

  it("negative input treated as 0", () => {
    expect(formatTimelineDuration(-10, "ru")).toBe("0мин.");
  });

  it("fractional minutes are floored", () => {
    expect(formatTimelineDuration(90.9, "ru")).toBe("1ч. 30мин.");
  });
});

// ---------------------------------------------------------------------------
// computeTimelineCalc — R94–R97
// ---------------------------------------------------------------------------

// Fixed reference timestamps for deterministic tests
const DEP_UTC = "2026-04-21T10:00:00Z"; // 10:00 UTC departure (actual)
const ARR_SCHED_UTC = "2026-04-21T12:00:00Z"; // 12:00 UTC scheduled arrival  (120 min total)
const ARR_EST_UTC = "2026-04-21T12:30:00Z"; // 12:30 UTC estimated arrival (150 min total)
const ARR_ACTUAL_UTC = "2026-04-21T11:50:00Z"; // 11:50 UTC actual arrival  (110 min total)

// Now = 11:00 UTC → 60 min elapsed, 60/120 = 50% (vs scheduled)
const NOW_INFLIGHT = Date.parse("2026-04-21T11:00:00Z");
// Now = 12:30 → already past scheduled arrival
const NOW_PAST = Date.parse("2026-04-21T12:30:00Z");

describe("computeTimelineCalc — no actual departure", () => {
  const inputs: TimelineInputs = {
    depActualUtc: null,
    arrScheduledUtc: ARR_SCHED_UTC,
  };

  it("returns zeros when depActualUtc is missing", () => {
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result).toEqual({
      totalMinutes: 0,
      elapsedMinutes: 0,
      remainingMinutes: 0,
      positionPercent: 0,
    });
  });
});

describe("computeTimelineCalc — R94 arrival time priority", () => {
  it("uses actual arrival over estimated when both present", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
      arrEstimatedUtc: ARR_EST_UTC,
      arrActualUtc: ARR_ACTUAL_UTC,
    };
    // actual arrival = 11:50 → total = 110 min
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result.totalMinutes).toBe(110);
  });

  it("uses estimated arrival when no actual arrival", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
      arrEstimatedUtc: ARR_EST_UTC,
      arrActualUtc: null,
    };
    // estimated = 12:30 → total = 150 min
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result.totalMinutes).toBe(150);
  });

  it("falls back to scheduled arrival when no actual or estimated", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    // scheduled = 12:00 → total = 120 min
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result.totalMinutes).toBe(120);
  });
});

describe("computeTimelineCalc — R95 elapsed (В пути)", () => {
  it("returns 60 elapsed minutes when now is 60 min after departure", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result.elapsedMinutes).toBe(60);
  });

  it("clamps elapsed to totalMinutes when now is past arrival", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_PAST);
    expect(result.elapsedMinutes).toBe(120); // clamped to total
  });
});

describe("computeTimelineCalc — R96 remaining (До прилета)", () => {
  it("returns 60 remaining minutes when now is 60 min before arrival", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result.remainingMinutes).toBe(60);
  });

  it("returns 0 when now is past arrival", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_PAST);
    expect(result.remainingMinutes).toBe(0);
  });
});

describe("computeTimelineCalc — R97 position percent", () => {
  it("returns 50% when halfway through 120-min flight", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result.positionPercent).toBe(50);
  });

  it("returns 100% when now is past arrival", () => {
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_PAST);
    expect(result.positionPercent).toBe(100);
  });

  it("returns 0% before departure (elapsed clamped to 0)", () => {
    const beforeDep = Date.parse("2026-04-21T09:30:00Z");
    const inputs: TimelineInputs = {
      depActualUtc: DEP_UTC,
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, beforeDep);
    expect(result.positionPercent).toBe(0);
    expect(result.elapsedMinutes).toBe(0);
  });
});

describe("computeTimelineCalc — invalid timestamps", () => {
  it("returns zeros when depActualUtc is not a valid date", () => {
    const inputs: TimelineInputs = {
      depActualUtc: "not-a-date",
      arrScheduledUtc: ARR_SCHED_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result).toEqual({
      totalMinutes: 0,
      elapsedMinutes: 0,
      remainingMinutes: 0,
      positionPercent: 0,
    });
  });

  it("returns zeros when arr is before dep (invalid range)", () => {
    const inputs: TimelineInputs = {
      depActualUtc: ARR_SCHED_UTC, // swap: dep after arr
      arrScheduledUtc: DEP_UTC,
    };
    const result = computeTimelineCalc(inputs, NOW_INFLIGHT);
    expect(result).toEqual({
      totalMinutes: 0,
      elapsedMinutes: 0,
      remainingMinutes: 0,
      positionPercent: 0,
    });
  });
});
