import { describe, it, expect } from 'vitest';
import { detectEvents } from './dates';
import type { Segment } from '../types';

const seg = (start: number, text: string, id?: string): Segment => ({
  id,
  start,
  end: start + 1,
  text,
});

// Anchor = 2026-06-13 (Saturday) 00:00 UTC. All assertions derive from this.
const ANCHOR = Date.UTC(2026, 5, 13, 0, 0, 0, 0);

const utc = (y: number, mo: number, d: number, h = 0, mi = 0) =>
  Date.UTC(y, mo, d, h, mi, 0, 0);

describe('detectEvents', () => {
  it('returns [] for empty input', () => {
    expect(detectEvents([], ANCHOR)).toEqual([]);
  });

  it('returns [] for non-finite anchor', () => {
    expect(detectEvents([seg(0, 'exam tomorrow')], Number.NaN)).toEqual([]);
  });

  it('resolves ISO YYYY-MM-DD to UTC midnight', () => {
    const out = detectEvents([seg(0, 'The exam is on 2026-06-20.')], ANCHOR);
    expect(out).toHaveLength(1);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 20));
    expect(out[0]!.allDay).toBe(true);
    expect(out[0]!.confidence).toBeGreaterThanOrEqual(0.9);
  });

  it('resolves "March 3" using the anchor year (no roll within 6mo)', () => {
    const out = detectEvents([seg(0, 'deadline on March 3')], ANCHOR);
    expect(out).toHaveLength(1);
    // March 3 2026 is ~3mo before June 13 2026 -> stays in the anchor year.
    expect(out[0]!.dateMs).toBe(utc(2026, 2, 3));
  });

  it('rolls a date more than 6 months before the anchor to next year', () => {
    // Anchor late in the year: "January 5" resolves ~10mo back -> roll to next.
    const novAnchor = Date.UTC(2026, 10, 1); // 2026-11-01
    const out = detectEvents([seg(0, 'deadline on January 5')], novAnchor);
    expect(out[0]!.dateMs).toBe(utc(2027, 0, 5));
  });

  it('keeps an explicit year even if far past', () => {
    const out = detectEvents([seg(0, 'paper from March 3, 2020')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2020, 2, 3));
  });

  it('resolves day-first "3 March"', () => {
    const out = detectEvents([seg(0, 'submission due 20 July')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 6, 20));
  });

  it('does NOT roll a date within the next 6 months', () => {
    const out = detectEvents([seg(0, 'exam on June 20')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 20));
  });

  it('resolves "on the 14th" within the anchor month', () => {
    const out = detectEvents([seg(0, 'assignment due on the 14th')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 14));
  });

  it('resolves relative "tomorrow" from the anchor', () => {
    const out = detectEvents([seg(0, 'quiz tomorrow')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 14));
    expect(out[0]!.confidence).toBeLessThan(0.9); // relative
  });

  it('resolves "today" to the anchor day', () => {
    const out = detectEvents([seg(0, 'homework due today')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 13));
  });

  it('resolves "next week" as +7 days', () => {
    const out = detectEvents([seg(0, 'the exam is next week')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 20));
  });

  it('resolves "in 3 days" and "in 2 weeks"', () => {
    const a = detectEvents([seg(0, 'due in 3 days')], ANCHOR);
    expect(a[0]!.dateMs).toBe(utc(2026, 5, 16));
    const b = detectEvents([seg(0, 'due in 2 weeks')], ANCHOR);
    expect(b[0]!.dateMs).toBe(utc(2026, 5, 27));
  });

  it('resolves "next Monday" to the Monday of the following week', () => {
    // Anchor Sat 2026-06-13. Next Monday = 2026-06-22 (skip the 15th).
    const out = detectEvents([seg(0, 'exam next Monday')], ANCHOR);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 22));
  });

  it('captures a clock time and sets allDay=false', () => {
    const out = detectEvents([seg(0, 'exam on June 20 at 3pm')], ANCHOR);
    expect(out[0]!.allDay).toBe(false);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 20, 15, 0));
  });

  it('parses 24h time "14:00"', () => {
    const out = detectEvents([seg(0, 'meeting 2026-06-20 14:00')], ANCHOR);
    expect(out[0]!.allDay).toBe(false);
    expect(out[0]!.dateMs).toBe(utc(2026, 5, 20, 14, 0));
  });

  it('uses the surrounding clause as the title', () => {
    const out = detectEvents(
      [seg(0, 'Welcome everyone. The final exam is on June 20. See you.')],
      ANCHOR,
    );
    expect(out[0]!.title.toLowerCase()).toContain('final exam');
  });

  it('boosts confidence when a deadline keyword is present', () => {
    const withKw = detectEvents([seg(0, 'the exam is on June 20')], ANCHOR)[0]!;
    const without = detectEvents([seg(0, 'we met on June 20')], ANCHOR)[0]!;
    expect(withKw.confidence).toBeGreaterThan(without.confidence);
  });

  it('tags sourceSegmentId and start', () => {
    const out = detectEvents([seg(42, 'exam on June 20', 'segX')], ANCHOR);
    expect(out[0]!.sourceSegmentId).toBe('segX');
    expect(out[0]!.start).toBe(42);
  });

  it('dedupes identical (dateMs + title)', () => {
    const out = detectEvents(
      [seg(0, 'exam on June 20'), seg(1, 'exam on June 20')],
      ANCHOR,
    );
    expect(out).toHaveLength(1);
  });

  it('does not read the wall clock (output depends only on the anchor)', () => {
    const a = detectEvents([seg(0, 'quiz tomorrow')], ANCHOR);
    const other = Date.UTC(2030, 0, 1);
    const b = detectEvents([seg(0, 'quiz tomorrow')], other);
    expect(a[0]!.dateMs).toBe(utc(2026, 5, 14));
    expect(b[0]!.dateMs).toBe(Date.UTC(2030, 0, 2));
  });
});
