import { describe, it, expect } from 'vitest'
import {
  rangesOverlap,
  rangeIncludesDay,
  aggregateDayStatus,
  findFreeConsultantIdsForRange,
  buildDayCells,
  parseISODate,
  formatISODate,
  addDays,
  startOfWeekUTC,
  validateAvailabilityInput,
  STATUS_PRIORITY,
  TIMELINE_DAYS,
  type ConsultantAvailability,
} from '@/lib/intake/availability'

const ENTRY = (
  id: string,
  consultantId: string,
  start: string,
  end: string,
  status: ConsultantAvailability['status'],
): ConsultantAvailability => ({
  id,
  consultant_id: consultantId,
  start_date: start,
  end_date: end,
  status,
  note: null,
})

describe('availability — range overlap', () => {
  it('detects overlap when ranges touch', () => {
    expect(rangesOverlap('2026-06-01', '2026-06-05', '2026-06-05', '2026-06-10')).toBe(true)
  })
  it('detects no overlap when separated', () => {
    expect(rangesOverlap('2026-06-01', '2026-06-05', '2026-06-06', '2026-06-10')).toBe(false)
  })
  it('detects overlap when one contains the other', () => {
    expect(rangesOverlap('2026-06-01', '2026-06-30', '2026-06-10', '2026-06-12')).toBe(true)
  })
})

describe('availability — day inclusion', () => {
  it('detects day inside range', () => {
    expect(rangeIncludesDay('2026-06-01', '2026-06-05', '2026-06-03')).toBe(true)
  })
  it('detects day outside range', () => {
    expect(rangeIncludesDay('2026-06-01', '2026-06-05', '2026-05-31')).toBe(false)
    expect(rangeIncludesDay('2026-06-01', '2026-06-05', '2026-06-06')).toBe(false)
  })
})

describe('availability — day status aggregation (highest priority wins)', () => {
  it('returns null if no entries match', () => {
    expect(aggregateDayStatus([], 'c1', '2026-06-03')).toBeNull()
    expect(
      aggregateDayStatus(
        [ENTRY('a', 'c1', '2026-06-01', '2026-06-02', 'free')],
        'c1',
        '2026-06-03',
      ),
    ).toBeNull()
  })

  it('returns free when only free entry matches', () => {
    expect(
      aggregateDayStatus([ENTRY('a', 'c1', '2026-06-01', '2026-06-10', 'free')], 'c1', '2026-06-05'),
    ).toBe('free')
  })

  it('returns highest-priority status when several overlap', () => {
    const entries = [
      ENTRY('a', 'c1', '2026-06-01', '2026-06-10', 'partial'),
      ENTRY('b', 'c1', '2026-06-03', '2026-06-05', 'full'),
    ]
    expect(aggregateDayStatus(entries, 'c1', '2026-06-04')).toBe('full')
    expect(aggregateDayStatus(entries, 'c1', '2026-06-08')).toBe('partial')
  })

  it('prefers blocked over vacation, vacation over full', () => {
    expect(STATUS_PRIORITY.blocked).toBeGreaterThan(STATUS_PRIORITY.vacation)
    expect(STATUS_PRIORITY.vacation).toBeGreaterThan(STATUS_PRIORITY.full)
  })

  it('ignores entries from other consultants', () => {
    const entries = [
      ENTRY('a', 'c1', '2026-06-01', '2026-06-10', 'free'),
      ENTRY('b', 'c2', '2026-06-01', '2026-06-10', 'full'),
    ]
    expect(aggregateDayStatus(entries, 'c1', '2026-06-05')).toBe('free')
    expect(aggregateDayStatus(entries, 'c2', '2026-06-05')).toBe('full')
  })
})

describe('availability — find free consultants for intake range', () => {
  const consultantIds = ['c1', 'c2', 'c3']

  it('returns all when no blocking entries exist', () => {
    expect(findFreeConsultantIdsForRange(consultantIds, [], '2026-06-01', '2026-06-05')).toEqual([
      'c1',
      'c2',
      'c3',
    ])
  })

  it('excludes consultants with full/vacation/blocked overlap', () => {
    const entries = [
      ENTRY('a', 'c1', '2026-06-03', '2026-06-04', 'vacation'),
      ENTRY('b', 'c2', '2026-06-10', '2026-06-15', 'full'),
    ]
    expect(findFreeConsultantIdsForRange(consultantIds, entries, '2026-06-01', '2026-06-05')).toEqual([
      'c2',
      'c3',
    ])
  })

  it('does NOT exclude consultants with partial overlap (still bookable)', () => {
    const entries = [ENTRY('a', 'c1', '2026-06-03', '2026-06-04', 'partial')]
    expect(findFreeConsultantIdsForRange(consultantIds, entries, '2026-06-01', '2026-06-05')).toContain(
      'c1',
    )
  })

  it('excludes consultants with blocked status', () => {
    const entries = [ENTRY('a', 'c2', '2026-06-04', '2026-06-04', 'blocked')]
    expect(findFreeConsultantIdsForRange(consultantIds, entries, '2026-06-01', '2026-06-05')).not.toContain(
      'c2',
    )
  })
})

describe('availability — day cell building', () => {
  it('produces TIMELINE_DAYS cells with correct weekIndex', () => {
    const start = parseISODate('2026-06-01')
    const cells = buildDayCells(start, TIMELINE_DAYS)
    expect(cells).toHaveLength(TIMELINE_DAYS)
    expect(cells[0].weekIndex).toBe(0)
    expect(cells[6].weekIndex).toBe(0)
    expect(cells[7].weekIndex).toBe(1)
  })

  it('weekday 0 = Monday for ISO-week start', () => {
    const monday = parseISODate('2026-06-01')
    const cells = buildDayCells(monday, 7)
    expect(cells[0].weekday).toBe(0)
    expect(cells[5].weekday).toBe(5)
    expect(cells[6].weekday).toBe(6)
  })
})

describe('availability — ISO date helpers', () => {
  it('addDays rolls month/year correctly', () => {
    const d = parseISODate('2026-12-30')
    expect(formatISODate(addDays(d, 5))).toBe('2027-01-04')
  })

  it('startOfWeekUTC snaps to Monday', () => {
    const wed = parseISODate('2026-06-03')
    expect(formatISODate(startOfWeekUTC(wed))).toBe('2026-06-01')

    const sun = parseISODate('2026-06-07')
    expect(formatISODate(startOfWeekUTC(sun))).toBe('2026-06-01')

    const mon = parseISODate('2026-06-08')
    expect(formatISODate(startOfWeekUTC(mon))).toBe('2026-06-08')
  })
})

describe('availability — input validation', () => {
  it('accepts a complete vacation entry', () => {
    expect(
      validateAvailabilityInput({
        startDate: '2026-06-01',
        endDate: '2026-06-05',
        status: 'vacation',
        note: 'Urlaub',
      }).ok,
    ).toBe(true)
  })

  it('rejects missing dates', () => {
    const r1 = validateAvailabilityInput({ startDate: '', endDate: '2026-06-05', status: 'free', note: null })
    expect(r1.ok).toBe(false)
    if (!r1.ok) expect(r1.error).toBe('start_required')

    const r2 = validateAvailabilityInput({ startDate: '2026-06-01', endDate: '', status: 'free', note: null })
    expect(r2.ok).toBe(false)
    if (!r2.ok) expect(r2.error).toBe('end_required')
  })

  it('rejects end before start', () => {
    const r = validateAvailabilityInput({ startDate: '2026-06-10', endDate: '2026-06-05', status: 'free', note: null })
    expect(r.ok).toBe(false)
    if (!r.ok) expect(r.error).toBe('end_before_start')
  })

  it('rejects unknown status', () => {
    const r = validateAvailabilityInput({
      startDate: '2026-06-01',
      endDate: '2026-06-05',
      status: 'something' as never,
      note: null,
    })
    expect(r.ok).toBe(false)
    if (!r.ok) expect(r.error).toBe('invalid_status')
  })
})
