/**
 * BUG A regression — write-path timezone serialization.
 *
 * Verifies that timed events are serialized to an unambiguous UTC instant
 * (so the operator's wall-clock time round-trips correctly regardless of the
 * API container's timezone), while all-day events keep their DATE strings.
 *
 * The PWA vitest harness runs with TZ=UTC, so the assertions are computed
 * relative to the local zone (whatever it is) rather than hard-coding an offset.
 * The core guarantee under test: the serialized timed value is a UTC instant
 * (ends in 'Z') derived from the LOCAL wall clock — never the naive wall-clock
 * string passed through verbatim, and never an instant that loses the local hour.
 */

import { describe, it, expect } from 'vitest';
import {
  serializeEventDateTime,
  localWallClockToUtcIso,
  computeNewTimedEnd,
  computeNewAllDayEnd,
} from './eventDateTime.js';

describe('serializeEventDateTime (BUG A — write-path TZ)', () => {
  it('serializes a timed start to a UTC instant (ends in Z)', () => {
    const { start } = serializeEventDateTime(false, '2026-06-07', '09:00', '2026-06-07', '10:00');
    expect(start.endsWith('Z')).toBe(true);
    // It must NOT be the naive wall-clock string (the original bug shape).
    expect(start).not.toBe('2026-06-07T09:00:00');
  });

  it('the serialized instant round-trips back to the SAME local wall clock', () => {
    // This is the heart of BUG A: 09:00 in → 09:00 back out in the operator's zone.
    const { start, end } = serializeEventDateTime(
      false,
      '2026-06-07',
      '09:00',
      '2026-06-07',
      '10:30',
    );
    const startBack = new Date(start);
    expect(startBack.getHours()).toBe(9);
    expect(startBack.getMinutes()).toBe(0);
    const endBack = new Date(end);
    expect(endBack.getHours()).toBe(10);
    expect(endBack.getMinutes()).toBe(30);
  });

  it('equals the instant new Date(local parts) produces — not a passthrough', () => {
    const { start } = serializeEventDateTime(false, '2026-06-07', '09:00', '2026-06-07', '10:00');
    expect(start).toBe(new Date('2026-06-07T09:00:00').toISOString());
  });

  it('leaves all-day events as DATE strings (no time, no Z) — D-13 contract', () => {
    const { start, end } = serializeEventDateTime(
      true,
      '2026-06-07',
      '09:00',
      '2026-06-09',
      '10:00',
    );
    expect(start).toBe('2026-06-07');
    expect(end).toBe('2026-06-09');
  });

  it('localWallClockToUtcIso round-trips a local wall clock to a UTC instant', () => {
    const iso = localWallClockToUtcIso('2026-06-07', '09:00');
    expect(iso.endsWith('Z')).toBe(true);
    expect(new Date(iso).getHours()).toBe(9);
  });
});

describe('computeNewTimedEnd (D-04 — end-tracking)', () => {
  it('preserves a 1-hour timed delta when start moves forward', () => {
    // Old: 2026-06-11 09:00 → 10:00 (1h duration)
    // New start: 2026-06-11 11:00 → new end should be 12:00
    const result = computeNewTimedEnd(
      '2026-06-11',
      '11:00',
      '2026-06-11',
      '09:00',
      '2026-06-11',
      '10:00',
    );
    expect(result.endDate).toBe('2026-06-11');
    expect(result.endTime).toBe('12:00');
  });

  it('preserves a multi-day timed delta (26h) when start moves', () => {
    // Old: 2026-06-11 08:00 → 2026-06-12 10:00 (26h duration)
    // New start: 2026-06-13 08:00 → new end should be 2026-06-14 10:00
    const result = computeNewTimedEnd(
      '2026-06-13',
      '08:00',
      '2026-06-11',
      '08:00',
      '2026-06-12',
      '10:00',
    );
    expect(result.endDate).toBe('2026-06-14');
    expect(result.endTime).toBe('10:00');
  });

  it('floors to 1h when old end was already behind old start', () => {
    // Old end (10:00) <= old start (11:00) — stale/invalid state
    // New start: 2026-06-11 14:00 → new end should snap to +1h = 15:00
    const result = computeNewTimedEnd(
      '2026-06-11',
      '14:00',
      '2026-06-11',
      '11:00',
      '2026-06-11',
      '10:00',
    );
    expect(result.endDate).toBe('2026-06-11');
    expect(result.endTime).toBe('15:00');
  });
});

describe('computeNewAllDayEnd (D-04 — all-day end-tracking)', () => {
  it('preserves a 0-day span (single-day all-day event)', () => {
    // Old: 2026-06-11 → 2026-06-11 (0-day span, single day)
    // New start: 2026-06-15 → new end should be 2026-06-15
    const result = computeNewAllDayEnd('2026-06-15', '2026-06-11', '2026-06-11');
    expect(result).toBe('2026-06-15');
  });

  it('preserves a 3-day span when start moves', () => {
    // Old: 2026-06-11 → 2026-06-14 (3-day span)
    // New start: 2026-06-20 → new end should be 2026-06-23
    const result = computeNewAllDayEnd('2026-06-20', '2026-06-11', '2026-06-14');
    expect(result).toBe('2026-06-23');
  });

  it('floors to same day when old end was already behind old start', () => {
    // Old end (2026-06-10) < old start (2026-06-11) — stale/invalid state
    // New start: 2026-06-15 → new end should snap to same day 2026-06-15
    const result = computeNewAllDayEnd('2026-06-15', '2026-06-11', '2026-06-10');
    expect(result).toBe('2026-06-15');
  });
});
