import { appendFileSync } from 'node:fs'
import { createEnvReader, readProcessEnv } from '../env'
import {
  getPerfSpanOverlapSummary,
  getPerfSpanSnapshot,
  getPerfSpanStats,
  isPerfEnabled,
  perfLog,
} from './perf-log'

let timer: ReturnType<typeof setInterval> | null = null
let lastMem: {
  rss: number
  heapUsed: number
  heapTotal: number
  external: number
  arrayBuffers: number
} | null = null

function formatClockTime(ms: number): string {
  const d = new Date(ms)
  const hh = String(d.getHours()).padStart(2, '0')
  const mm = String(d.getMinutes()).padStart(2, '0')
  const ss = String(d.getSeconds()).padStart(2, '0')
  const mmm = String(d.getMilliseconds()).padStart(3, '0')
  return `${hh}:${mm}:${ss}.${mmm}`
}

function getMem(): {
  rss: number
  heapUsed: number
  heapTotal: number
  external: number
  arrayBuffers: number
} | null {
  const p = (globalThis as any)?.process
  if (!p || typeof p?.memoryUsage !== 'function')
    return null
  try {
    const mu = p.memoryUsage()
    return {
      rss: Number(mu?.rss ?? 0),
      heapUsed: Number(mu?.heapUsed ?? 0),
      heapTotal: Number(mu?.heapTotal ?? 0),
      external: Number(mu?.external ?? 0),
      arrayBuffers: Number(mu?.arrayBuffers ?? 0),
    }
  }
  catch {
    return null
  }
}

function fmtMb(n: number): string {
  const v = Number(n || 0) / (1024 * 1024)
  return Number.isFinite(v) ? v.toFixed(1) : '0.0'
}

export function startEventLoopLagLogger(
  opts?: Readonly<{
    path?: string
    intervalMs?: number
    warnMs?: number
  }>,
): void {
  if (timer)
    return

  const env = createEnvReader(readProcessEnv())
  const path = String(
    (opts?.path ?? env.prefixedStr('EVENT_LOOP_LAG_LOG_PATH'))
    || '/tmp/dimcode-event-loop-lag.log',
  )
  const intervalMsRaw = Number(
    opts?.intervalMs ?? env.prefixedInt('EVENT_LOOP_LAG_INTERVAL_MS') ?? 50,
  )
  const warnMsRaw = Number(
    opts?.warnMs ?? env.prefixedInt('EVENT_LOOP_LAG_WARN_MS') ?? 120,
  )

  const intervalMs = Number.isFinite(intervalMsRaw)
    ? Math.max(10, Math.floor(intervalMsRaw))
    : 50
  const warnMs = Number.isFinite(warnMsRaw)
    ? Math.max(10, Math.floor(warnMsRaw))
    : 120

  let last = Date.now()
  timer = setInterval(() => {
    const now = Date.now()
    const expected = last + intervalMs
    const lag = now - expected
    last = now
    if (lag < warnMs)
      return
    const mem = getMem()
    const memText = mem
      ? (() => {
          const prev = lastMem
          lastMem = mem
          const delta = prev
            ? ` rssΔMB=${fmtMb(mem.rss - prev.rss)} heapUsedΔMB=${fmtMb(mem.heapUsed - prev.heapUsed)}`
            : ''
          return ` rssMB=${fmtMb(mem.rss)} heapUsedMB=${fmtMb(mem.heapUsed)} heapTotalMB=${fmtMb(mem.heapTotal)} extMB=${fmtMb(mem.external)} abMB=${fmtMb(mem.arrayBuffers)}${delta}`
        })()
      : ''
    try {
      appendFileSync(
        path,
        `[${formatClockTime(now)}] lag=${lag}ms interval=${intervalMs}ms${memText ? ` ${memText.trim()}` : ''}\n`,
      )
    }
    catch {
      // ignore
    }
    if (isPerfEnabled()) {
      perfLog(
        `[LAG] lag=${lag}ms interval=${intervalMs}ms warnMs=${warnMs}${memText ? ` ${memText.trim()}` : ''}${(() => {
          const s = getPerfSpanSnapshot()
          const stats = getPerfSpanStats()
          const statsText = ` spanActiveDepth=${stats.activeDepth} spanHistorySize=${stats.historySize}`
          if (!s)
            return `${statsText}${getPerfSpanOverlapSummary(expected, now)}`
          return `${statsText} span=${s.path} spanDepth=${s.depth} spanTopAgeMs=${s.topAgeMs}${getPerfSpanOverlapSummary(expected, now)}`
        })()}`,
      )
    }
  }, intervalMs)
  ;(timer as any).unref?.()
}

export function stopEventLoopLagLogger(): void {
  if (!timer)
    return
  clearInterval(timer)
  timer = null
}
