/**
 * Unit tests for date-utils. Ported from webapp/js/dateUtils.test.js.
 */

import { describe, it, expect } from "vitest";
import {
  localDateString,
  dutyOverlapsLocalDay,
  dutyOverlapsLocalRange,
  getMonday,
  formatHHMM,
  firstDayOfMonth,
  lastDayOfMonth,
  formatDateKey,
  dateKeyToDDMM,
} from "./date-utils";

describe("localDateString", () => {
  it("formats date as YYYY-MM-DD", () => {
    const d = new Date(2025, 0, 15);
    expect(localDateString(d)).toBe("2025-01-15");
  });

  it("pads month and day with zero", () => {
    expect(localDateString(new Date(2025, 0, 5))).toBe("2025-01-05");
    expect(localDateString(new Date(2025, 8, 9))).toBe("2025-09-09");
  });

  it("handles December and year boundary", () => {
    expect(localDateString(new Date(2024, 11, 31))).toBe("2024-12-31");
  });
});

describe("dutyOverlapsLocalDay", () => {
  it("returns true when duty spans the whole day", () => {
    const d = {
      start_at: "2025-02-25T00:00:00Z",
      end_at: "2025-02-25T23:59:59Z",
    };
    expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(true);
  });

  it("returns true when duty overlaps part of the day", () => {
    const d = {
      start_at: "2025-02-25T09:00:00Z",
      end_at: "2025-02-25T14:00:00Z",
    };
    expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(true);
  });

  it("returns true when duty continues from previous day", () => {
    const d = {
      start_at: "2025-02-24T22:00:00Z",
      end_at: "2025-02-25T06:00:00Z",
    };
    expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(true);
  });

  it("returns false when duty ends before the day", () => {
    const d = {
      start_at: "2025-02-24T09:00:00Z",
      end_at: "2025-02-24T18:00:00Z",
    };
    expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(false);
  });

  it("returns false when duty starts after the day", () => {
    const d = {
      start_at: "2025-02-26T09:00:00Z",
      end_at: "2025-02-26T18:00:00Z",
    };
    expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(false);
  });
});

describe("dutyOverlapsLocalRange", () => {
  it("returns true when duty overlaps the range", () => {
    const d = {
      start_at: "2025-02-24T12:00:00Z",
      end_at: "2025-02-26T12:00:00Z",
    };
    expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(true);
  });

  it("returns true when duty is entirely inside the range", () => {
    const d = {
      start_at: "2025-02-26T09:00:00Z",
      end_at: "2025-02-26T18:00:00Z",
    };
    expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(true);
  });

  it("returns false when duty ends before range start", () => {
    const d = {
      start_at: "2025-02-20T09:00:00Z",
      end_at: "2025-02-22T18:00:00Z",
    };
    expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(false);
  });

  it("returns false when duty starts after range end", () => {
    const d = {
      start_at: "2025-03-01T09:00:00Z",
      end_at: "2025-03-01T18:00:00Z",
    };
    expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(false);
  });
});

describe("getMonday", () => {
  it("returns same day when date is Monday", () => {
    const monday = new Date(2025, 0, 6); // 6 Jan 2025 is Monday
    const result = getMonday(monday);
    expect(result.getFullYear()).toBe(2025);
    expect(result.getMonth()).toBe(0);
    expect(result.getDate()).toBe(6);
    expect(result.getDay()).toBe(1);
  });

  it("returns previous Monday for Wednesday", () => {
    const wed = new Date(2025, 0, 8);
    const result = getMonday(wed);
    expect(result.getDay()).toBe(1);
    expect(result.getDate()).toBe(6);
  });

  it("returns Monday of same week for Sunday", () => {
    const sun = new Date(2025, 0, 12);
    const result = getMonday(sun);
    expect(result.getDay()).toBe(1);
    expect(result.getDate()).toBe(6);
  });
});

describe("formatHHMM", () => {
  it("formats ISO string as HH:MM in local time", () => {
    const s = "2025-02-25T14:30:00Z";
    const result = formatHHMM(s);
    expect(result).toMatch(/^\d{2}:\d{2}$/);
    const d = new Date(s);
    const expected =
      (d.getHours() < 10 ? "0" : "") +
      d.getHours() +
      ":" +
      (d.getMinutes() < 10 ? "0" : "") +
      d.getMinutes();
    expect(result).toBe(expected);
  });

  it("returns empty string for null", () => {
    expect(formatHHMM(null as unknown as string)).toBe("");
  });

  it("returns empty string for undefined", () => {
    expect(formatHHMM(undefined as unknown as string)).toBe("");
  });

  it("returns empty string for empty string", () => {
    expect(formatHHMM("")).toBe("");
  });

  it("pads hours and minutes with zero", () => {
    const s = "2025-02-25T09:05:00Z";
    const result = formatHHMM(s);
    expect(result).toMatch(/^\d{2}:\d{2}$/);
  });
});

describe("firstDayOfMonth", () => {
  it("returns first day of month", () => {
    const d = new Date(2025, 5, 15);
    const result = firstDayOfMonth(d);
    expect(result.getFullYear()).toBe(2025);
    expect(result.getMonth()).toBe(5);
    expect(result.getDate()).toBe(1);
  });

  it("handles January", () => {
    const d = new Date(2025, 0, 31);
    const result = firstDayOfMonth(d);
    expect(result.getDate()).toBe(1);
    expect(result.getMonth()).toBe(0);
  });
});

describe("lastDayOfMonth", () => {
  it("returns last day of month", () => {
    const d = new Date(2025, 0, 15);
    const result = lastDayOfMonth(d);
    expect(result.getFullYear()).toBe(2025);
    expect(result.getMonth()).toBe(0);
    expect(result.getDate()).toBe(31);
  });

  it("returns 28 for non-leap February", () => {
    const d = new Date(2023, 1, 1);
    const result = lastDayOfMonth(d);
    expect(result.getDate()).toBe(28);
    expect(result.getMonth()).toBe(1);
  });

  it("returns 29 for leap February", () => {
    const d = new Date(2024, 1, 1);
    const result = lastDayOfMonth(d);
    expect(result.getDate()).toBe(29);
  });
});

describe("formatDateKey", () => {
  it("formats ISO date string as DD.MM (local time)", () => {
    const result = formatDateKey("2025-02-25T00:00:00Z");
    expect(result).toMatch(/^\d{2}\.\d{2}$/);
    const [day, month] = result.split(".");
    expect(Number(day)).toBeGreaterThanOrEqual(1);
    expect(Number(day)).toBeLessThanOrEqual(31);
    expect(Number(month)).toBeGreaterThanOrEqual(1);
    expect(Number(month)).toBeLessThanOrEqual(12);
  });

  it("returns DD.MM format with zero-padding", () => {
    const result = formatDateKey("2025-01-05T12:00:00Z");
    expect(result).toMatch(/^\d{2}\.\d{2}$/);
  });
});

describe("dateKeyToDDMM", () => {
  it("converts YYYY-MM-DD to DD.MM", () => {
    expect(dateKeyToDDMM("2025-02-25")).toBe("25.02");
  });

  it("handles single-digit day and month", () => {
    expect(dateKeyToDDMM("2025-01-09")).toBe("09.01");
  });

  it("returns key unchanged when format is not YYYY-MM-DD", () => {
    expect(dateKeyToDDMM("short")).toBe("short");
    expect(dateKeyToDDMM("2025/02/25")).toBe("2025/02/25");
    expect(dateKeyToDDMM("20250225")).toBe("20250225");
  });
});
