/**
 * Agent Schedule Helper
 * Computes the next run date for an agent based on its frequency, scheduled time, and timezone.
 */

interface ScheduleParams {
  frequency: string   // manual, hourly, daily, weekly, monthly
  scheduledTime?: string | null  // "HH:mm" in user's local time
  scheduledDay?: number | null   // 0-6 (Mon-Sun) for weekly, 1-31 for monthly
  timezone?: string | null       // IANA timezone, e.g. "Europe/Paris"
}

/**
 * Parse "HH:mm" into hours and minutes.
 */
function parseTime(time: string): { hours: number; minutes: number } {
  const [h, m] = time.split(':').map(Number)
  return { hours: h, minutes: m || 0 }
}

/**
 * Calculate the next run date for an agent.
 * Returns a UTC Date, or null for manual frequency.
 */
export function calculateNextRun(params: ScheduleParams): Date | null {
  const { frequency, scheduledTime, scheduledDay, timezone } = params

  if (frequency === 'manual') return null

  const tz = timezone || 'UTC'
  const time = scheduledTime || '08:00'
  const { hours, minutes } = parseTime(time)
  const now = new Date()

  if (frequency === 'hourly') {
    return new Date(now.getTime() + 60 * 60 * 1000)
  }

  // For daily/weekly/monthly, build the target in the user's timezone then convert to UTC
  const fmt = (d: Date) =>
    new Intl.DateTimeFormat('en-US', {
      year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit', second: '2-digit',
      hour12: false, timeZone: tz,
    }).format(d)

  // Helper: create a Date in the user's timezone for a given local date/time
  const makeDateInTZ = (year: number, month: number, day: number, h: number, m: number): Date => {
    // Build a string like "2026-04-26 09:00" in the user's timezone
    const localStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:00`
    // Parse as if it's in the user's timezone by using Intl to find the UTC offset
    // We use a trick: format the date in the TZ, then find the offset
    const fakeUtc = new Date(localStr + 'Z')
    // Get the UTC offset for this time in the timezone
    const offsetMs = getTimezoneOffset(tz, fakeUtc)
    return new Date(fakeUtc.getTime() - offsetMs)
  }

  if (frequency === 'daily') {
    // Try today first, if past → tomorrow
    const todayStr = fmt(now)
    const todayParts = todayStr.match(/(\d{2})\/(\d{2})\/(\d{4}), (\d{2}):(\d{2}):(\d{2})/)
    if (!todayParts) return new Date(now.getTime() + 24 * 60 * 60 * 1000)

    const todayMonth = parseInt(todayParts[1]) - 1
    const todayDay = parseInt(todayParts[2])
    const todayYear = parseInt(todayParts[3])

    const target = makeDateInTZ(todayYear, todayMonth, todayDay, hours, minutes)
    if (target > now) return target

    // Tomorrow
    const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000)
    const tmStr = fmt(tomorrow)
    const tmParts = tmStr.match(/(\d{2})\/(\d{2})\/(\d{4}),/)
    if (!tmParts) return new Date(now.getTime() + 24 * 60 * 60 * 1000)
    return makeDateInTZ(parseInt(tmParts[3]), parseInt(tmParts[1]) - 1, parseInt(tmParts[2]), hours, minutes)
  }

  if (frequency === 'weekly') {
    const targetDay = scheduledDay ?? 0 // 0=Monday
    // JS getDay(): 0=Sun, 1=Mon, ... 6=Sat
    // Our convention: 0=Mon, 1=Tue, ... 6=Sun
    const jsDay = targetDay === 6 ? 0 : targetDay + 1

    // Start from today, scan forward up to 7 days
    for (let i = 0; i <= 7; i++) {
      const candidate = new Date(now.getTime() + i * 24 * 60 * 60 * 1000)
      const candidateStr = fmt(candidate)
      const parts = candidateStr.match(/(\d{2})\/(\d{2})\/(\d{4}),/)
      if (!parts) continue

      const candidateMonth = parseInt(parts[1]) - 1
      const candidateDay = parseInt(parts[2])
      const candidateYear = parseInt(parts[3])

      // Get day of week in the user's timezone
      const dayOfWeek = new Intl.DateTimeFormat('en-US', { weekday: 'short', timeZone: tz }).format(candidate)

      // Map to our convention
      const dayMap: Record<string, number> = { Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6 }
      const thisDayIndex = dayMap[dayOfWeek]

      if (thisDayIndex === targetDay) {
        const target = makeDateInTZ(candidateYear, candidateMonth, candidateDay, hours, minutes)
        if (target > now) return target
      }
    }

    // Fallback: next week same day
    return new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
  }

  if (frequency === 'monthly') {
    const targetDayOfMonth = scheduledDay ?? 1

    // Try this month, then next month, then the month after
    for (let offset = 0; offset <= 2; offset++) {
      const base = new Date(now)
      base.setMonth(base.getMonth() + offset)

      const year = base.getFullYear()
      const month = base.getMonth()
      // Clamp to max days in month
      const maxDays = new Date(year, month + 1, 0).getDate()
      const day = Math.min(targetDayOfMonth, maxDays)

      const target = makeDateInTZ(year, month, day, hours, minutes)
      if (target > now) return target
    }

    // Fallback
    return new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
  }

  return null
}

/**
 * Get the timezone offset in ms for a given timezone at a specific UTC time.
 * Positive offset means the timezone is ahead of UTC.
 */
function getTimezoneOffset(tz: string, utcDate: Date): number {
  // Get local time in the target timezone
  const localStr = new Intl.DateTimeFormat('en-US', {
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
    hour12: false, timeZone: tz,
  }).format(utcDate)

  // Parse the local time
  const parts = localStr.match(/(\d{2})\/(\d{2})\/(\d{4}), (\d{2}):(\d{2}):(\d{2})/)
  if (!parts) return 0

  const localDate = new Date(
    parseInt(parts[3]), parseInt(parts[1]) - 1, parseInt(parts[2]),
    parseInt(parts[4]) === 24 ? 0 : parseInt(parts[4]), parseInt(parts[5]), parseInt(parts[6])
  )

  // Offset = local - UTC (in ms)
  return localDate.getTime() - utcDate.getTime()
}
