import type { LogLine } from '@types'
import color from 'kleur'
import { get, getSignal, handleError, makeUrl, post } from '../http'
import { resolveAppName } from '../name'

interface CronJobSummary {
  app: string
  name: string
  schedule: string
  state: string
  status: string
  lastRun?: number
  lastDuration?: number
  lastExitCode?: number
  nextRun?: number
}

interface CronJobDetail extends CronJobSummary {
  lastError?: string
  lastOutput?: string
}

function formatRelative(ts?: number): string {
  if (!ts) return '-'
  const diff = Date.now() - ts
  if (diff < 0) {
    const mins = Math.round(-diff / 60000)
    if (mins < 60) return `in ${mins}m`
    const hours = Math.round(mins / 60)
    if (hours < 24) return `in ${hours}h`
    return `in ${Math.round(hours / 24)}d`
  }
  const mins = Math.round(diff / 60000)
  if (mins < 60) return `${mins}m ago`
  const hours = Math.round(mins / 60)
  if (hours < 24) return `${hours}h ago`
  return `${Math.round(hours / 24)}d ago`
}

function formatDuration(ms?: number): string {
  if (!ms) return '-'
  if (ms < 1000) return `${ms}ms`
  if (ms < 60000) return `${Math.round(ms / 1000)}s`
  return `${Math.round(ms / 60000)}m`
}

function pad(str: string, len: number, right = false): string {
  if (right) return str.padStart(len)
  return str.padEnd(len)
}

function statusColor(status: string): (s: string) => string {
  if (status === 'running') return color.green
  if (status === 'ok') return color.green
  if (status === 'idle') return color.gray
  return color.red
}

function parseJobArg(arg: string): { app: string; name: string } | undefined {
  const parts = arg.split(':')
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
    console.error(`Invalid job format: ${arg}`)
    console.error('Use app:name format (e.g., myapp:backup)')
    return undefined
  }
  return { app: parts[0]!, name: parts[1]! }
}

export async function cronList(app?: string) {
  const appName = app ? resolveAppName(app) : undefined
  if (app && !appName) return

  const url = appName
    ? `/api/tools/cron/api/jobs?app=${appName}`
    : '/api/tools/cron/api/jobs'

  const jobs = await get<CronJobSummary[]>(url)
  if (!jobs || jobs.length === 0) {
    console.log('No cron jobs found')
    return
  }

  const jobWidth = Math.max(3, ...jobs.map(j => `${j.app}:${j.name}`.length))
  const schedWidth = Math.max(8, ...jobs.map(j => String(j.schedule).length))
  const statusWidth = Math.max(6, ...jobs.map(j => j.status.length))

  console.log(
    color.gray(
      `${pad('JOB', jobWidth)}  ${pad('SCHEDULE', schedWidth)}  ${pad('STATUS', statusWidth)}  ${pad('LAST RUN', 10)}  ${pad('NEXT RUN', 10)}`
    )
  )

  for (const j of jobs) {
    const id = `${j.app}:${j.name}`
    const colorFn = statusColor(j.status)
    console.log(
      `${pad(id, jobWidth)}  ${pad(String(j.schedule), schedWidth)}  ${colorFn(pad(j.status, statusWidth))}  ${pad(formatRelative(j.lastRun), 10)}  ${pad(formatRelative(j.nextRun), 10)}`
    )
  }
}

export async function cronStatus(arg: string) {
  const parsed = parseJobArg(arg)
  if (!parsed) return

  const job = await get<CronJobDetail>(`/api/tools/cron/api/jobs/${parsed.app}/${parsed.name}`)
  if (!job) return

  const colorFn = statusColor(job.status)

  console.log(`${color.bold(`${job.app}:${job.name}`)}  ${colorFn(job.status)}`)
  console.log()
  console.log(`  Schedule:   ${job.schedule}`)
  console.log(`  State:      ${job.state}`)
  console.log(`  Last run:   ${formatRelative(job.lastRun)}`)
  console.log(`  Duration:   ${formatDuration(job.lastDuration)}`)
  if (job.lastExitCode !== undefined) {
    console.log(`  Exit code:  ${job.lastExitCode === 0 ? color.green('0') : color.red(String(job.lastExitCode))}`)
  }
  console.log(`  Next run:   ${formatRelative(job.nextRun)}`)

  if (job.lastError) {
    console.log()
    console.log(color.red('Error:'))
    console.log(job.lastError)
  }

  if (job.lastOutput) {
    console.log()
    console.log(color.gray('Output:'))
    console.log(job.lastOutput)
  }
}

export async function cronLog(arg?: string, options?: { follow?: boolean }) {
  // No arg: show the cron tool's own logs
  // "myapp": show myapp's logs filtered to [cron entries
  // "myapp:backup": show myapp's logs filtered to [cron:backup]
  const follow = options?.follow ?? false

  if (!arg) {
    // Show cron tool's own logs
    if (follow) {
      await tailCronLogs('cron')
      return
    }
    const logs = await get<LogLine[]>('/api/apps/cron/logs')
    if (!logs || logs.length === 0) {
      console.log('No cron logs yet')
      return
    }
    for (const line of logs) printCronLog(line)
    return
  }

  // Parse arg — could be "myapp" or "myapp:backup"
  const colon = arg.indexOf(':')
  const appName = colon >= 0 ? arg.slice(0, colon) : arg
  const jobName = colon >= 0 ? arg.slice(colon + 1) : undefined
  const grepPrefix = jobName ? `[cron:${jobName}]` : '[cron'

  const resolved = resolveAppName(appName)
  if (!resolved) return

  if (follow) {
    await tailCronLogs(resolved, grepPrefix)
    return
  }

  const logs = await get<LogLine[]>(`/api/apps/${resolved}/logs`)
  if (!logs || logs.length === 0) {
    console.log('No cron logs yet')
    return
  }
  for (const line of logs) {
    if (line.text.includes(grepPrefix)) printCronLog(line)
  }
}

export async function cronRun(arg: string) {
  const parsed = parseJobArg(arg)
  if (!parsed) return

  const result = await post<{ ok: boolean; message: string; error?: string }>(
    `/api/tools/cron/api/jobs/${parsed.app}/${parsed.name}/run`
  )
  if (!result) return

  console.log(color.green(result.message))
}

const printCronLog = (line: LogLine) =>
  console.log(`${new Date(line.time).toLocaleTimeString()} ${line.text}`)

async function tailCronLogs(app: string, grep?: string) {
  try {
    const url = makeUrl(`/api/apps/${app}/logs/stream`)
    const res = await fetch(url, { signal: getSignal() })
    if (!res.ok) {
      console.error(`App not found: ${app}`)
      return
    }
    if (!res.body) return

    const reader = res.body.getReader()
    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n\n')
      buffer = lines.pop() ?? ''

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = JSON.parse(line.slice(6)) as LogLine
          if (!grep || data.text.includes(grep)) printCronLog(data)
        }
      }
    }
  } catch (error) {
    handleError(error)
  }
}
