import { describe, it, expect } from 'vitest'
import {
  parseTime,
  formatTime,
  addMinutes,
  recomputeDayItems,
  computeDayWarnings,
  enumerateDates,
  daysBetween,
} from '../time-engine'
import type { AgendaItem } from '../types'

function makeItem(partial: Partial<AgendaItem> & { id: string; sort_order: number; duration_minutes: number }): AgendaItem {
  return {
    agenda_day_id: 'day-1',
    start_time: null,
    end_time: null,
    title: 'Item',
    description: null,
    responsible: null,
    participant_group: null,
    location: null,
    item_type: 'workshop',
    comments: null,
    required_information: null,
    is_time_fixed: false,
    color: null,
    ...partial,
  }
}

describe('parseTime / formatTime', () => {
  it('parses HH:MM to minutes since midnight', () => {
    expect(parseTime('08:30')).toBe(510)
    expect(parseTime('00:00')).toBe(0)
    expect(parseTime('13:45')).toBe(825)
  })

  it('round-trips through formatTime', () => {
    expect(formatTime(510)).toBe('08:30')
    expect(formatTime(0)).toBe('00:00')
    expect(formatTime(825)).toBe('13:45')
  })

  it('formats overflow past midnight visibly (no silent wrap)', () => {
    expect(formatTime(25 * 60 + 30)).toBe('25:30')
  })

  it('addMinutes adds correctly', () => {
    expect(addMinutes('08:30', 90)).toBe('10:00')
  })
})

describe('recomputeDayItems — sequential chaining', () => {
  it('first item starts at day start, each following starts at previous end', () => {
    const items = [
      makeItem({ id: 'a', sort_order: 0, duration_minutes: 60 }),
      makeItem({ id: 'b', sort_order: 1, duration_minutes: 30 }),
      makeItem({ id: 'c', sort_order: 2, duration_minutes: 90 }),
    ]
    const out = recomputeDayItems(items, '08:30')
    expect(out.map((i) => [i.start_time, i.end_time])).toEqual([
      ['08:30', '09:30'],
      ['09:30', '10:00'],
      ['10:00', '11:30'],
    ])
  })

  it('does not mutate the input array or items', () => {
    const items = [makeItem({ id: 'a', sort_order: 0, duration_minutes: 60 })]
    recomputeDayItems(items, '08:30')
    expect(items[0].start_time).toBeNull()
  })
})

describe('recomputeDayItems — fixed start anchors', () => {
  it('honours a fixed start time and continues the chain after it', () => {
    const items = [
      makeItem({ id: 'a', sort_order: 0, duration_minutes: 60 }),
      makeItem({ id: 'b', sort_order: 1, duration_minutes: 60, is_time_fixed: true, start_time: '13:00' }),
      makeItem({ id: 'c', sort_order: 2, duration_minutes: 30 }),
    ]
    const out = recomputeDayItems(items, '08:30')
    expect(out.map((i) => [i.start_time, i.end_time])).toEqual([
      ['08:30', '09:30'],
      ['13:00', '14:00'],
      ['14:00', '14:30'],
    ])
  })
})

describe('recomputeDayItems — duration guards', () => {
  it('treats zero/negative duration as zero-length for the chain', () => {
    const items = [
      makeItem({ id: 'a', sort_order: 0, duration_minutes: 0 }),
      makeItem({ id: 'b', sort_order: 1, duration_minutes: -15 }),
      makeItem({ id: 'c', sort_order: 2, duration_minutes: 30 }),
    ]
    const out = recomputeDayItems(items, '09:00')
    expect(out.map((i) => [i.start_time, i.end_time])).toEqual([
      ['09:00', '09:00'],
      ['09:00', '09:00'],
      ['09:00', '09:30'],
    ])
  })
})

describe('computeDayWarnings', () => {
  it('flags non-positive durations', () => {
    const items = [makeItem({ id: 'a', sort_order: 0, duration_minutes: 0 })]
    const recomputed = recomputeDayItems(items, '09:00')
    const warnings = computeDayWarnings('day-1', recomputed, '17:00')
    expect(warnings.some((w) => w.code === 'duration_non_positive' && w.itemId === 'a')).toBe(true)
  })

  it('flags missing title', () => {
    const items = [makeItem({ id: 'a', sort_order: 0, duration_minutes: 30, title: '' })]
    const recomputed = recomputeDayItems(items, '09:00')
    const warnings = computeDayWarnings('day-1', recomputed, '17:00')
    expect(warnings.some((w) => w.code === 'missing_title')).toBe(true)
  })

  it('flags exceeding the day end', () => {
    const items = [makeItem({ id: 'a', sort_order: 0, duration_minutes: 600 })]
    const recomputed = recomputeDayItems(items, '09:00') // ends 19:00
    const warnings = computeDayWarnings('day-1', recomputed, '17:00')
    expect(warnings.some((w) => w.code === 'exceeds_day_end')).toBe(true)
  })

  it('flags an overlap when a fixed start is earlier than the previous end', () => {
    const items = [
      makeItem({ id: 'a', sort_order: 0, duration_minutes: 120 }), // 09:00-11:00
      makeItem({ id: 'b', sort_order: 1, duration_minutes: 30, is_time_fixed: true, start_time: '10:00' }),
    ]
    const recomputed = recomputeDayItems(items, '09:00')
    const warnings = computeDayWarnings('day-1', recomputed, '17:00')
    expect(warnings.some((w) => w.code === 'overlap' && w.itemId === 'b')).toBe(true)
  })

  it('returns no warnings for a clean day', () => {
    const items = [
      makeItem({ id: 'a', sort_order: 0, duration_minutes: 60 }),
      makeItem({ id: 'b', sort_order: 1, duration_minutes: 60 }),
    ]
    const recomputed = recomputeDayItems(items, '09:00')
    expect(computeDayWarnings('day-1', recomputed, '17:00')).toEqual([])
  })
})

describe('enumerateDates / daysBetween', () => {
  it('enumerates inclusive date range', () => {
    expect(enumerateDates('2026-06-01', '2026-06-03')).toEqual([
      '2026-06-01',
      '2026-06-02',
      '2026-06-03',
    ])
  })

  it('handles a single-day range', () => {
    expect(enumerateDates('2026-06-01', '2026-06-01')).toEqual(['2026-06-01'])
  })

  it('crosses a month boundary correctly', () => {
    expect(enumerateDates('2026-06-29', '2026-07-01')).toEqual([
      '2026-06-29',
      '2026-06-30',
      '2026-07-01',
    ])
  })

  it('daysBetween is inclusive', () => {
    expect(daysBetween('2026-06-01', '2026-06-03')).toBe(3)
    expect(daysBetween('2026-06-01', '2026-06-01')).toBe(1)
  })

  it('returns empty for an inverted range', () => {
    expect(enumerateDates('2026-06-03', '2026-06-01')).toEqual([])
  })
})
