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

interface LogOptions {
  date?: string
  follow?: boolean
  grep?: string
  since?: string
}

const formatDate = (date: Date) =>
  date.toISOString().slice(0, 10)

const matchesGrep = (text: string, pattern: string) =>
  text.toLowerCase().includes(pattern.toLowerCase())

const parseDuration = (duration: string): number | null => {
  const match = duration.match(/^(\d+)([hdwm])$/)
  if (!match) return null

  const value = parseInt(match[1]!, 10)
  const unit = match[2]!

  const ms = {
    h: 60 * 60 * 1000,
    d: 24 * 60 * 60 * 1000,
    w: 7 * 24 * 60 * 60 * 1000,
    m: 30 * 24 * 60 * 60 * 1000,
  }

  return value * ms[unit as keyof typeof ms]
}

const printDiskLog = (line: string, grep?: string) => {
  if (grep && !matchesGrep(line, grep)) return
  console.log(line)
}

export const printLog = (line: LogLine, grep?: string) => {
  if (grep && !matchesGrep(line.text, grep)) return
  console.log(`${new Date(line.time).toLocaleTimeString()} ${line.text}`)
}

export async function logApp(arg: string | undefined, options: LogOptions) {
  const name = resolveAppName(arg)
  if (!name) return

  if (options.follow) {
    await tailLogs(name, options.grep)
    return
  }

  // Handle --since option
  if (options.since) {
    const ms = parseDuration(options.since)
    if (!ms) {
      console.error('Invalid duration. Use format like: 1h, 2d, 1w, 1m')
      return
    }

    const dates = await get<string[]>(`/api/apps/${name}/logs/dates`)
    if (!dates) {
      console.error(`App not found: ${name}`)
      return
    }

    const cutoff = new Date(Date.now() - ms)
    const cutoffDate = formatDate(cutoff)

    // Filter dates that are >= cutoff
    const relevantDates = dates.filter(d => d >= cutoffDate).reverse()
    if (relevantDates.length === 0) {
      console.log('No logs in the specified time range')
      return
    }

    for (const date of relevantDates) {
      const lines = await get<string[]>(`/api/apps/${name}/logs?date=${date}`)
      if (!lines) continue
      for (const line of lines) {
        printDiskLog(line, options.grep)
      }
    }
    return
  }

  // Handle --date option
  if (options.date) {
    const lines = await get<string[]>(`/api/apps/${name}/logs?date=${options.date}`)
    if (!lines) {
      console.error(`App not found: ${name}`)
      return
    }
    if (lines.length === 0) {
      console.log('No logs for this date')
      return
    }
    for (const line of lines) {
      printDiskLog(line, options.grep)
    }
    return
  }

  // Default: show today's in-memory logs
  const logs: LogLine[] | undefined = await get(`/api/apps/${name}/logs`)
  if (!logs) {
    console.error(`App not found: ${name}`)
    return
  }
  if (logs.length === 0) {
    console.log('No logs yet')
    return
  }
  for (const line of logs) {
    printLog(line, options.grep)
  }
}

export async function tailLogs(name: string, grep?: string) {
  try {
    const url = makeUrl(`/api/apps/${name}/logs/stream`)
    const res = await fetch(url, { signal: getSignal() })
    if (!res.ok) {
      console.error(`App not found: ${name}`)
      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
          printLog(data, grep)
        }
      }
    }
  } catch (error) {
    handleError(error)
  }
}
