import { describe, it, expect } from 'vitest'
import {
  intervalHitsToday,
  isScheduledToday,
  getDueTimeToday,
  isPastTime,
  isExtendedToday,
  formatDueTimeLabel,
  msUntilExpiry,
  toLocalISODate,
} from '../common/scheduleUtils'
import type { ChoreSchedule } from '../common/models'

// ---------------------------------------------------------------------------
// toLocalISODate
// ---------------------------------------------------------------------------
describe('toLocalISODate', () => {
  it('formats a date as YYYY-MM-DD', () => {
    const d = new Date(2025, 0, 5) // Jan 5, 2025 local
    expect(toLocalISODate(d)).toBe('2025-01-05')
  })

  it('pads month and day', () => {
    const d = new Date(2025, 2, 9) // Mar 9
    expect(toLocalISODate(d)).toBe('2025-03-09')
  })
})

// ---------------------------------------------------------------------------
// intervalHitsToday
// ---------------------------------------------------------------------------
describe('intervalHitsToday', () => {
  it('anchor day hits on itself', () => {
    const wednesday = new Date(2025, 0, 8) // Jan 8, 2025 is a Wednesday
    expect(intervalHitsToday('2025-01-08', 2, wednesday)).toBe(true)
  })

  it('anchor=Wednesday(2025-01-08), interval=2, Thursday does NOT hit', () => {
    const thursday = new Date(2025, 0, 9) // 1 day after anchor, 1 % 2 !== 0
    expect(intervalHitsToday('2025-01-08', 2, thursday)).toBe(false)
  })

  it('anchor=Wednesday(2025-01-08), interval=2, Friday hits (2 days after anchor)', () => {
    const friday = new Date(2025, 0, 10) // 2 days after anchor, 2 % 2 === 0
    expect(intervalHitsToday('2025-01-08', 2, friday)).toBe(true)
  })

  it('interval=1 hits every day', () => {
    const monday = new Date(2025, 0, 6) // anchor is that same Monday
    expect(intervalHitsToday('2025-01-06', 1, monday)).toBe(true)
    const tuesday = new Date(2025, 0, 7)
    expect(intervalHitsToday('2025-01-06', 1, tuesday)).toBe(true)
  })

  it('returns false for a date before the anchor', () => {
    const beforeAnchor = new Date(2025, 0, 7) // Jan 7 is before Jan 8 anchor
    expect(intervalHitsToday('2025-01-08', 2, beforeAnchor)).toBe(false)
  })

  it('empty anchor string treats localDate as the anchor (always hits)', () => {
    const today = new Date(2025, 0, 8)
    expect(intervalHitsToday('', 2, today)).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// isScheduledToday
// ---------------------------------------------------------------------------
describe('isScheduledToday', () => {
  const daysSchedule: ChoreSchedule = {
    child_id: 'c1',
    task_id: 't1',
    mode: 'days',
    day_configs: [
      { day: 1, hour: 8, minute: 0 }, // Monday
      { day: 3, hour: 9, minute: 30 }, // Wednesday
    ],
    interval_days: 2,
    anchor_date: '',
    interval_has_deadline: true,
    interval_hour: 0,
    interval_minute: 0,
  }

  it('returns true on a scheduled weekday', () => {
    const monday = new Date(2025, 0, 6) // Jan 6, 2025 = Monday
    expect(isScheduledToday(daysSchedule, monday)).toBe(true)
  })

  it('returns false on an unscheduled weekday', () => {
    const tuesday = new Date(2025, 0, 7) // Tuesday
    expect(isScheduledToday(daysSchedule, tuesday)).toBe(false)
  })

  it('interval mode delegates to intervalHitsToday', () => {
    const intervalSchedule: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2025-01-08', // Wednesday
      interval_has_deadline: true,
      interval_hour: 8,
      interval_minute: 0,
    }
    const wednesday = new Date(2025, 0, 8)
    expect(isScheduledToday(intervalSchedule, wednesday)).toBe(true)
    const thursday = new Date(2025, 0, 9)
    expect(isScheduledToday(intervalSchedule, thursday)).toBe(false)
  })

  it('returns true when enabled is false on a scheduled day (days mode) — paused = always shown', () => {
    const pausedSchedule: ChoreSchedule = {
      ...daysSchedule,
      enabled: false,
    }
    const monday = new Date(2025, 0, 6) // Monday — scheduled day
    expect(isScheduledToday(pausedSchedule, monday)).toBe(true)
  })

  it('returns true when enabled is false on an unscheduled day (days mode) — paused = always shown', () => {
    const pausedSchedule: ChoreSchedule = {
      ...daysSchedule,
      enabled: false,
    }
    const tuesday = new Date(2025, 0, 7) // Tuesday — NOT a scheduled day normally
    expect(isScheduledToday(pausedSchedule, tuesday)).toBe(true)
  })

  it('returns true when enabled is false on a hit day (interval mode) — paused = always shown', () => {
    const pausedInterval: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2025-01-08',
      interval_has_deadline: true,
      interval_hour: 8,
      interval_minute: 0,
      enabled: false,
    }
    const wednesday = new Date(2025, 0, 8) // would normally hit
    expect(isScheduledToday(pausedInterval, wednesday)).toBe(true)
  })

  it('returns true when enabled is false on a non-hit day (interval mode) — paused = always shown', () => {
    const pausedInterval: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2025-01-08',
      interval_has_deadline: true,
      interval_hour: 8,
      interval_minute: 0,
      enabled: false,
    }
    const thursday = new Date(2025, 0, 9) // would NOT normally hit
    expect(isScheduledToday(pausedInterval, thursday)).toBe(true)
  })

  it('treats enabled=undefined as enabled (backward compat)', () => {
    // daysSchedule has no enabled field — should behave as enabled
    const monday = new Date(2025, 0, 6)
    expect(isScheduledToday(daysSchedule, monday)).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// getDueTimeToday
// ---------------------------------------------------------------------------
describe('getDueTimeToday', () => {
  const daysSchedule: ChoreSchedule = {
    child_id: 'c1',
    task_id: 't1',
    mode: 'days',
    day_configs: [{ day: 1, hour: 8, minute: 30 }],
    interval_days: 2,
    anchor_date: '',
    interval_has_deadline: true,
    interval_hour: 0,
    interval_minute: 0,
  }

  it('returns correct time for a scheduled day', () => {
    const monday = new Date(2025, 0, 6)
    expect(getDueTimeToday(daysSchedule, monday)).toEqual({ hour: 8, minute: 30 })
  })

  it('returns null for an unscheduled day', () => {
    const tuesday = new Date(2025, 0, 7)
    expect(getDueTimeToday(daysSchedule, tuesday)).toBeNull()
  })

  it('interval mode returns interval time on hit days', () => {
    const intervalSchedule: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2025-01-08',
      interval_has_deadline: true,
      interval_hour: 14,
      interval_minute: 45,
    }
    const wednesday = new Date(2025, 0, 8)
    expect(getDueTimeToday(intervalSchedule, wednesday)).toEqual({ hour: 14, minute: 45 })
  })

  it('interval mode returns null on non-hit days', () => {
    const intervalSchedule: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2025-01-08',
      interval_has_deadline: true,
      interval_hour: 14,
      interval_minute: 45,
    }
    const thursday = new Date(2025, 0, 9)
    expect(getDueTimeToday(intervalSchedule, thursday)).toBeNull()
  })

  it('interval mode with interval_has_deadline=false returns null (Anytime)', () => {
    const intervalSchedule: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2025-01-08',
      interval_has_deadline: false,
      interval_hour: 14,
      interval_minute: 45,
    }
    const wednesday = new Date(2025, 0, 8)
    // Even on a hit day, Anytime means no deadline → null
    expect(getDueTimeToday(intervalSchedule, wednesday)).toBeNull()
  })

  it('returns null when schedule is paused (enabled=false) — paused = no deadline', () => {
    const pausedSchedule: ChoreSchedule = {
      child_id: 'c1',
      task_id: 't1',
      mode: 'days',
      day_configs: [
        { day: 1, has_deadline: true, hour: 8, minute: 30 }, // Monday with deadline
      ],
      interval_days: null,
      anchor_date: null,
      interval_has_deadline: false,
      interval_hour: null,
      interval_minute: null,
      enabled: false,
    }
    const monday = new Date(2025, 0, 6) // Monday — would normally return { hour: 8, minute: 30 }
    expect(getDueTimeToday(pausedSchedule, monday)).toBeNull()
  })
})

// ---------------------------------------------------------------------------
// isPastTime
// ---------------------------------------------------------------------------
describe('isPastTime', () => {
  it('returns true when current time equals due time', () => {
    const now = new Date(2025, 0, 6, 8, 30, 0)
    expect(isPastTime(8, 30, now)).toBe(true)
  })

  it('returns true when current time is past due time', () => {
    const now = new Date(2025, 0, 6, 9, 0, 0)
    expect(isPastTime(8, 30, now)).toBe(true)
  })

  it('returns false when current time is before due time', () => {
    const now = new Date(2025, 0, 6, 8, 0, 0)
    expect(isPastTime(8, 30, now)).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// isExtendedToday
// ---------------------------------------------------------------------------
describe('isExtendedToday', () => {
  it('returns true when extension date matches today', () => {
    const today = new Date(2025, 0, 15)
    expect(isExtendedToday('2025-01-15', today)).toBe(true)
  })

  it('returns false when extension date does not match today', () => {
    const today = new Date(2025, 0, 15)
    expect(isExtendedToday('2025-01-14', today)).toBe(false)
  })

  it('returns false for null extension date', () => {
    const today = new Date(2025, 0, 15)
    expect(isExtendedToday(null, today)).toBe(false)
  })

  it('returns false for undefined extension date', () => {
    const today = new Date(2025, 0, 15)
    expect(isExtendedToday(undefined, today)).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// formatDueTimeLabel
// ---------------------------------------------------------------------------
describe('formatDueTimeLabel', () => {
  it('formats midnight as 12:00 AM', () => {
    expect(formatDueTimeLabel(0, 0)).toBe('12:00 AM')
  })

  it('formats noon as 12:00 PM', () => {
    expect(formatDueTimeLabel(12, 0)).toBe('12:00 PM')
  })

  it('formats 8:30 AM', () => {
    expect(formatDueTimeLabel(8, 30)).toBe('8:30 AM')
  })

  it('formats 14:45 as 2:45 PM', () => {
    expect(formatDueTimeLabel(14, 45)).toBe('2:45 PM')
  })

  it('pads single-digit minutes', () => {
    expect(formatDueTimeLabel(9, 5)).toBe('9:05 AM')
  })
})

// ---------------------------------------------------------------------------
// msUntilExpiry
// ---------------------------------------------------------------------------
describe('msUntilExpiry', () => {
  it('returns positive ms when due time is in the future', () => {
    const now = new Date(2025, 0, 6, 8, 0, 0, 0)
    const ms = msUntilExpiry(8, 30, now)
    expect(ms).toBe(30 * 60 * 1000) // 30 minutes
  })

  it('returns 0 when due time is in the past', () => {
    const now = new Date(2025, 0, 6, 9, 0, 0, 0)
    expect(msUntilExpiry(8, 30, now)).toBe(0)
  })

  it('returns 0 when due time equals current time', () => {
    const now = new Date(2025, 0, 6, 8, 30, 0, 0)
    expect(msUntilExpiry(8, 30, now)).toBe(0)
  })

  it('correctly handles seconds and milliseconds offset', () => {
    // 8:29:30.500 → due at 8:30:00.000 = 29500ms remaining
    const now = new Date(2025, 0, 6, 8, 29, 30, 500)
    expect(msUntilExpiry(8, 30, now)).toBe(29500)
  })
})
