/**
 * 执行周期 ↔ 5 字段 Cron 互转（对齐宝塔 aaPanel crontab.py GetCrondCycle）
 * 存储仍用 cron_expr；「执行周期」= 重复方式 + 开始执行时刻（时:分），不含秒。
 * cron 时/分/日/星期按北京时间（Asia/Shanghai）解释，与 schedule_runner 一致。
 */

import { beijingWallParts, parseApiDateTime } from '@/utils/datetime'

export type ScheduleCycleType =
  | 'day'
  | 'day-n'
  | 'hour'
  | 'hour-n'
  | 'minute-n'
  | 'week'
  | 'month'

export interface ScheduleCycleConfig {
  type: ScheduleCycleType | 'custom'
  minute: number
  hour: number
  /** day-n / hour-n / minute-n 的 N */
  interval: number
  /** Cron 周字段 0=周日 … 6=周六 */
  week: number
  /** 每月几号 1–31 */
  monthDay: number
  customCron: string
}

export const CYCLE_TYPE_OPTIONS: { value: ScheduleCycleType; label: string }[] = [
  { value: 'day', label: '当天' },
  { value: 'day-n', label: '每 N 天' },
  { value: 'hour', label: '每小时' },
  { value: 'hour-n', label: '每 N 小时' },
  { value: 'minute-n', label: '每 N 分钟' },
  { value: 'week', label: '每周' },
  { value: 'month', label: '每月' },
]

export const WEEKDAY_OPTIONS = [
  { value: 0, label: '周日' },
  { value: 1, label: '周一' },
  { value: 2, label: '周二' },
  { value: 3, label: '周三' },
  { value: 4, label: '周四' },
  { value: 5, label: '周五' },
  { value: 6, label: '周六' },
]

export function defaultCycleConfig(): ScheduleCycleConfig {
  return {
    type: 'day',
    minute: 0,
    hour: 2,
    interval: 2,
    week: 1,
    monthDay: 1,
    customCron: '',
  }
}

function clampInt(n: number, min: number, max: number): number {
  const v = Math.floor(Number(n))
  if (Number.isNaN(v)) return min
  return Math.min(max, Math.max(min, v))
}

/** 调度 runner 最小粒度 60 秒 */
export const SCHEDULE_MIN_INTERVAL_MINUTES = 1

export function buildCronFromCycle(config: ScheduleCycleConfig): string {
  if (config.type === 'custom') {
    const raw = config.customCron.trim()
    if (!raw) throw new Error('自定义 Cron 不能为空')
    if (!validateCronFiveField(raw)) throw new Error('Cron 须为 5 字段')
    return raw
  }

  const minute = clampInt(config.minute, 0, 59)
  const hour = clampInt(config.hour, 0, 23)

  switch (config.type) {
    case 'day':
      return `${minute} ${hour} * * *`
    case 'day-n': {
      const n = clampInt(config.interval, 1, 365)
      return `${minute} ${hour} */${n} * *`
    }
    case 'hour':
      return `${minute} * * * *`
    case 'hour-n': {
      const n = clampInt(config.interval, 1, 23)
      return `${minute} */${n} * * *`
    }
    case 'minute-n': {
      const n = clampInt(config.interval, SCHEDULE_MIN_INTERVAL_MINUTES, 59)
      return `*/${n} * * * *`
    }
    case 'week': {
      const w = clampInt(config.week, 0, 6)
      return `${minute} ${hour} * * ${w}`
    }
    case 'month': {
      const dom = clampInt(config.monthDay, 1, 31)
      return `${minute} ${hour} ${dom} * *`
    }
    default:
      throw new Error('未知执行周期类型')
  }
}

export function validateCronFiveField(expr: string): boolean {
  const parts = expr.trim().split(/\s+/)
  if (parts.length !== 5) return false
  return parts.every((p) => /^[\d*/,\-]+$/.test(p))
}

function isStar(s: string): boolean {
  return s === '*'
}

function parseStepOrValue(field: string): { kind: 'star' | 'step' | 'fixed'; value?: number; step?: number } {
  if (field === '*') return { kind: 'star' }
  if (field.startsWith('*/')) {
    const step = parseInt(field.slice(2), 10)
    if (!Number.isNaN(step) && step > 0) return { kind: 'step', step }
  }
  const fixed = parseInt(field, 10)
  if (!Number.isNaN(fixed)) return { kind: 'fixed', value: fixed }
  return { kind: 'star' }
}

/** 从 cron 反解析为周期配置；无法识别则 type=custom */
export function parseCronToCycle(cron: string): ScheduleCycleConfig {
  const base = defaultCycleConfig()
  const expr = (cron || '').trim()
  if (!expr) return base

  const parts = expr.split(/\s+/)
  if (parts.length !== 5) {
    return { ...base, type: 'custom', customCron: expr }
  }

  const [minF, hourF, domF, monF, dowF] = parts
  if (!isStar(monF)) {
    return { ...base, type: 'custom', customCron: expr }
  }

  const minP = parseStepOrValue(minF)
  const hourP = parseStepOrValue(hourF)
  const domP = parseStepOrValue(domF)
  const dowP = parseStepOrValue(dowF)

  // */N * * * *
  if (minP.kind === 'step' && hourP.kind === 'star' && domP.kind === 'star' && dowP.kind === 'star') {
    return { ...base, type: 'minute-n', interval: minP.step! }
  }

  // M * * * *
  if (minP.kind === 'fixed' && hourP.kind === 'star' && domP.kind === 'star' && dowP.kind === 'star') {
    return { ...base, type: 'hour', minute: minP.value! }
  }

  // M */H * * *
  if (minP.kind === 'fixed' && hourP.kind === 'step' && domP.kind === 'star' && dowP.kind === 'star') {
    return { ...base, type: 'hour-n', minute: minP.value!, interval: hourP.step! }
  }

  // M H * * W
  if (
    minP.kind === 'fixed' &&
    hourP.kind === 'fixed' &&
    domP.kind === 'star' &&
    (dowP.kind === 'fixed' || dowF === '7')
  ) {
    const w = dowF === '7' ? 0 : dowP.value!
    return { ...base, type: 'week', minute: minP.value!, hour: hourP.value!, week: w }
  }

  // M H D * *
  if (minP.kind === 'fixed' && hourP.kind === 'fixed' && domP.kind === 'fixed' && dowP.kind === 'star') {
    return {
      ...base,
      type: 'month',
      minute: minP.value!,
      hour: hourP.value!,
      monthDay: domP.value!,
    }
  }

  // M H */D * *
  if (minP.kind === 'fixed' && hourP.kind === 'fixed' && domP.kind === 'step' && dowP.kind === 'star') {
    return {
      ...base,
      type: 'day-n',
      minute: minP.value!,
      hour: hourP.value!,
      interval: domP.step!,
    }
  }

  // M H * * *
  if (minP.kind === 'fixed' && hourP.kind === 'fixed' && domP.kind === 'star' && dowP.kind === 'star') {
    return { ...base, type: 'day', minute: minP.value!, hour: hourP.value! }
  }

  return { ...base, type: 'custom', customCron: expr }
}

function pad2(n: number): string {
  return String(n).padStart(2, '0')
}

export function formatCycleLabel(input: ScheduleCycleConfig | string): string {
  const config = typeof input === 'string' ? parseCronToCycle(input) : input
  if (config.type === 'custom') {
    return config.customCron ? `自定义: ${config.customCron}` : '—'
  }

  const time = `${pad2(config.hour)}:${pad2(config.minute)}`

  switch (config.type) {
    case 'day':
      return `当天 ${time}`
    case 'day-n':
      return `每 ${config.interval} 天 ${time}`
    case 'hour':
      return `每小时 第 ${config.minute} 分`
    case 'hour-n':
      return `每 ${config.interval} 小时 第 ${config.minute} 分`
    case 'minute-n':
      return `每 ${config.interval} 分钟`
    case 'week': {
      const wd = WEEKDAY_OPTIONS.find((o) => o.value === config.week)?.label || `周${config.week}`
      return `每周${wd} ${time}`
    }
    case 'month':
      return `每月 ${config.monthDay} 日 ${time}`
    default:
      return '—'
  }
}

export function validateCycleConfig(config: ScheduleCycleConfig): string | null {
  if (config.type === 'custom') {
    if (!config.customCron.trim()) return '请填写 Cron 表达式'
    if (!validateCronFiveField(config.customCron.trim())) return 'Cron 须为 5 字段'
    return null
  }
  try {
    buildCronFromCycle(config)
    return null
  } catch (e: unknown) {
    return e instanceof Error ? e.message : '执行周期无效'
  }
}

function cronFieldMatch(expr: string, value: number): boolean {
  for (const part of expr.split(',')) {
    if (part === '*') return true
    if (part.startsWith('*/')) {
      const step = parseInt(part.slice(2), 10)
      if (step > 0 && value % step === 0) return true
      continue
    }
    if (part.includes('-')) {
      const [low, high] = part.split('-', 2)
      const lo = parseInt(low, 10)
      const hi = parseInt(high, 10)
      if (!Number.isNaN(lo) && !Number.isNaN(hi) && lo <= value && value <= hi) return true
      continue
    }
    const fixed = parseInt(part, 10)
    if (!Number.isNaN(fixed) && (value === fixed || (fixed === 7 && value === 0))) return true
  }
  return false
}

function cronMatch(cronExpr: string, at: Date): boolean {
  const parts = cronExpr.trim().split(/\s+/)
  if (parts.length !== 5) return false
  const w = beijingWallParts(at)
  const fields = [w.minute, w.hour, w.day, w.month, w.dow]
  return parts.every((expr, i) => cronFieldMatch(expr, fields[i]!))
}

/** 计算 strictly after *after* 的下一匹配时刻（单次执行 fire_at 用） */
export function computeNextCronRun(cronExpr: string, after: Date = new Date()): Date | null {
  const candidate = new Date(after)
  candidate.setSeconds(0, 0)
  candidate.setMinutes(candidate.getMinutes() + 1)
  const limit = new Date(after.getTime() + 366 * 24 * 60 * 60 * 1000)
  while (candidate <= limit) {
    if (cronMatch(cronExpr, candidate)) return new Date(candidate)
    candidate.setMinutes(candidate.getMinutes() + 1)
  }
  return null
}

/** 从调度记录还原周期（兼容旧 once 仅 fire_at） */
export function cycleFromSchedule(cronExpr?: string | null, fireAt?: string | null): ScheduleCycleConfig {
  if (cronExpr?.trim()) return parseCronToCycle(cronExpr)
  if (fireAt) {
    const d = parseApiDateTime(fireAt)
    if (d) {
      const w = beijingWallParts(d)
      return { ...defaultCycleConfig(), type: 'day', hour: w.hour, minute: w.minute }
    }
  }
  return defaultCycleConfig()
}

export function formatScheduleCycleDisplay(
  runMode: string | undefined,
  cronExpr?: string | null,
  fireAt?: string | null,
): string {
  const label = formatCycleLabel(cycleFromSchedule(cronExpr, fireAt))
  if (runMode === 'once') return `单次 · ${label}`
  return label
}
