/**
 * Schedule math — scheduled-reports (wave-15).
 *
 * Timezone-aware computation of report periods and next-run timestamps.
 * Uses native Intl / Date arithmetic — no date-fns dependency.
 */
import type { ReportScheduleRow } from '@zync/db/queries'

export interface ReportPeriod {
  from: string  // YYYY-MM-DD
  to: string    // YYYY-MM-DD
  label: string
}

// ── Timezone helpers ──────────────────────────────────────────────────────────

/**
 * Return a Date that represents `instant` broken down in `tz`.
 * We use Intl.DateTimeFormat to parse the local wall-clock fields.
 */
function toZoned(instant: Date, tz: string): {
  year: number; month: number; day: number
  hours: number; minutes: number; seconds: number
  dayOfWeek: number
} {
  const fmt = new Intl.DateTimeFormat('en-CA', {
    timeZone: tz,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
    hour12: false,
    weekday: 'short',
  })
  const parts = fmt.formatToParts(instant)
  const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '0'
  const year = parseInt(get('year'), 10)
  const month = parseInt(get('month'), 10) // 1-based
  const day = parseInt(get('day'), 10)
  const hours = parseInt(get('hour'), 10)
  const minutes = parseInt(get('minute'), 10)
  const seconds = parseInt(get('second'), 10)
  // weekday: Mon, Tue, Wed, Thu, Fri, Sat, Sun → 0=Sun…6=Sat
  const WEEKDAY_MAP: Record<string, number> = {
    Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6,
  }
  const dayOfWeek = WEEKDAY_MAP[get('weekday')] ?? 0
  return { year, month, day, hours, minutes, seconds, dayOfWeek }
}

/**
 * Convert a local date/time in `tz` back to a UTC Date.
 * Approach: build an ISO string as if it were UTC, create a Date, then
 * measure the offset, and correct.
 */
function fromZoned(year: number, month: number, day: number, hours: number, minutes: number, tz: string): Date {
  // Create a UTC guess
  const guessUtc = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0, 0))
  // Find what local time that UTC corresponds to in tz
  const local = toZoned(guessUtc, tz)
  // Compute the offset in ms
  const guessLocalMs = Date.UTC(local.year, local.month - 1, local.day, local.hours, local.minutes)
  const targetMs = Date.UTC(year, month - 1, day, hours, minutes)
  const offsetMs = targetMs - guessLocalMs
  return new Date(guessUtc.getTime() + offsetMs)
}

function fmtDate(year: number, month: number, day: number): string {
  return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}

// Quarter helpers
function quarterOfMonth(month: number): number { return Math.floor((month - 1) / 3) }
function quarterStart(year: number, q: number): { year: number; month: number } {
  const m = q * 3 + 1
  if (m > 12) return { year: year + 1, month: m - 12 }
  return { year, month: m }
}
function quarterEnd(year: number, q: number): { year: number; month: number; day: number } {
  const endMonth = q * 3 + 3
  if (endMonth > 12) return lastDayOf(year + 1, endMonth - 12)
  return lastDayOf(year, endMonth)
}
function lastDayOf(year: number, month: number): { year: number; month: number; day: number } {
  return { year, month, day: new Date(year, month, 0).getDate() }
}

// Add months helper
function addMonths(year: number, month: number, n: number): { year: number; month: number } {
  let m = month + n
  let y = year
  while (m > 12) { m -= 12; y++ }
  while (m < 1) { m += 12; y-- }
  return { year: y, month: m }
}

function parseTime(timeOfDay: string): { hours: number; minutes: number } {
  const parts = timeOfDay.split(':')
  return { hours: parseInt(parts[0] ?? '8', 10), minutes: parseInt(parts[1] ?? '0', 10) }
}

/**
 * Compute the report period based on schedule frequency + period_type.
 */
export function computeReportPeriod(
  schedule: Pick<ReportScheduleRow, 'frequency' | 'periodType'>,
  now: Date,
  tz: string,
): ReportPeriod {
  const z = toZoned(now, tz)
  const { frequency, periodType } = schedule

  if (periodType === 'ytd') {
    const from = fmtDate(z.year, 1, 1)
    // yesterday
    const yest = new Date(Date.UTC(z.year, z.month - 1, z.day - 1))
    const yz = toZoned(yest, tz)
    const to = fmtDate(yz.year, yz.month, yz.day)
    return { from, to, label: `YTD ${z.year}` }
  }

  if (periodType === 'current') {
    switch (frequency) {
      case 'daily': {
        const d = fmtDate(z.year, z.month, z.day)
        return { from: d, to: d, label: d }
      }
      case 'weekly': {
        // Start of week (Sun=0)
        const startDay = z.day - z.dayOfWeek
        const startD = new Date(Date.UTC(z.year, z.month - 1, startDay))
        const endD   = new Date(Date.UTC(z.year, z.month - 1, startDay + 6))
        const s = toZoned(startD, tz)
        const e = toZoned(endD, tz)
        const from = fmtDate(s.year, s.month, s.day)
        const to   = fmtDate(e.year, e.month, e.day)
        return { from, to, label: `Week of ${from}` }
      }
      case 'monthly': {
        const lastDay = new Date(z.year, z.month, 0).getDate()
        return {
          from: fmtDate(z.year, z.month, 1),
          to: fmtDate(z.year, z.month, lastDay),
          label: new Date(z.year, z.month - 1, 1).toLocaleString('en', { month: 'long', year: 'numeric' }),
        }
      }
      case 'quarterly': {
        const q = quarterOfMonth(z.month)
        const qs = quarterStart(z.year, q)
        const qe = quarterEnd(z.year, q)
        return {
          from: fmtDate(qs.year, qs.month, 1),
          to: fmtDate(qe.year, qe.month, qe.day),
          label: `Q${q + 1} ${z.year}`,
        }
      }
    }
  }

  // previous period
  switch (frequency) {
    case 'daily': {
      const prev = new Date(Date.UTC(z.year, z.month - 1, z.day - 1))
      const p = toZoned(prev, tz)
      const d = fmtDate(p.year, p.month, p.day)
      return { from: d, to: d, label: d }
    }
    case 'weekly': {
      const prevStart = new Date(Date.UTC(z.year, z.month - 1, z.day - z.dayOfWeek - 7))
      const prevEnd   = new Date(Date.UTC(z.year, z.month - 1, z.day - z.dayOfWeek - 1))
      const s = toZoned(prevStart, tz)
      const e = toZoned(prevEnd, tz)
      const from = fmtDate(s.year, s.month, s.day)
      const to   = fmtDate(e.year, e.month, e.day)
      return { from, to, label: `Week of ${from}` }
    }
    case 'monthly': {
      const prev = addMonths(z.year, z.month, -1)
      const lastDay = new Date(prev.year, prev.month, 0).getDate()
      return {
        from: fmtDate(prev.year, prev.month, 1),
        to: fmtDate(prev.year, prev.month, lastDay),
        label: new Date(prev.year, prev.month - 1, 1).toLocaleString('en', { month: 'long', year: 'numeric' }),
      }
    }
    case 'quarterly': {
      const q = quarterOfMonth(z.month)
      const prevQ = q === 0 ? 3 : q - 1
      const prevYear = q === 0 ? z.year - 1 : z.year
      const qs = quarterStart(prevYear, prevQ)
      const qe = quarterEnd(prevYear, prevQ)
      return {
        from: fmtDate(qs.year, qs.month, 1),
        to: fmtDate(qe.year, qe.month, qe.day),
        label: `Q${prevQ + 1} ${prevYear}`,
      }
    }
    default: {
      const prev = addMonths(z.year, z.month, -1)
      const lastDay = new Date(prev.year, prev.month, 0).getDate()
      return {
        from: fmtDate(prev.year, prev.month, 1),
        to: fmtDate(prev.year, prev.month, lastDay),
        label: new Date(prev.year, prev.month - 1, 1).toLocaleString('en', { month: 'long', year: 'numeric' }),
      }
    }
  }
}

/**
 * Compute the next run timestamp for a schedule starting from `fromInstant`.
 */
export function computeNextRun(
  schedule: Pick<ReportScheduleRow, 'frequency' | 'dayOfWeek' | 'dayOfMonth' | 'timeOfDay'>,
  fromInstant: Date,
  tz: string,
): Date {
  const { hours, minutes } = parseTime(schedule.timeOfDay ?? '08:00')
  const z = toZoned(fromInstant, tz)

  switch (schedule.frequency) {
    case 'daily': {
      // Next day at timeOfDay
      return fromZoned(z.year, z.month, z.day + 1, hours, minutes, tz)
    }
    case 'weekly': {
      const targetDow = schedule.dayOfWeek ?? 1 // default Monday
      let daysUntil = targetDow - z.dayOfWeek
      if (daysUntil <= 0) daysUntil += 7
      return fromZoned(z.year, z.month, z.day + daysUntil, hours, minutes, tz)
    }
    case 'monthly': {
      const targetDom = schedule.dayOfMonth ?? 1
      // Try this month, if already passed try next month
      let candidate = fromZoned(z.year, z.month, targetDom, hours, minutes, tz)
      if (candidate <= fromInstant) {
        const next = addMonths(z.year, z.month, 1)
        candidate = fromZoned(next.year, next.month, targetDom, hours, minutes, tz)
      }
      return candidate
    }
    case 'quarterly': {
      // First day of next quarter
      const q = quarterOfMonth(z.month)
      const nextQIdx = (q + 1) % 4
      const nextQYear = q === 3 ? z.year + 1 : z.year
      const qs = quarterStart(nextQYear, nextQIdx)
      return fromZoned(qs.year, qs.month, 1, hours, minutes, tz)
    }
    default:
      return fromZoned(z.year, z.month, z.day + 1, hours, minutes, tz)
  }
}
