import { appendFileSync } from 'node:fs'
import { createEnvReader, readProcessEnv } from '../env'

type RuntimeGlobals = typeof globalThis & {
  process?: Pick<NodeJS.Process, 'versions'>
  __DIMCODE_PERF_SPAN__?: PerfSpanState
  __DIMCODE_PERF_SPAN_HISTORY__?: PerfHistoryState
}

function runtimeGlobals(): RuntimeGlobals {
  return globalThis as RuntimeGlobals
}

function isNodeLike(): boolean {
  const p = runtimeGlobals().process
  const v = p?.versions ?? {}
  return Boolean(
    p && (typeof v?.node === 'string' || typeof v?.bun === 'string'),
  )
}

function env() {
  return createEnvReader(readProcessEnv())
}

function parseEnabled(v: unknown): boolean {
  const s = String(v ?? '')
    .trim()
    .toLowerCase()
  return s === '1' || s === 'true' || s === 'yes' || s === 'on'
}

export function isPerfEnabled(): boolean {
  if (!isNodeLike())
    return false
  const e = env()
  return (
    e.prefixedStr('PROFILE_1SHOT') === '1' || e.prefixedStr('PERF_LOG') === '1'
  )
}

export function isOneShotEnabled(): boolean {
  if (!isNodeLike())
    return false
  return env().prefixedStr('PROFILE_1SHOT') === '1'
}

function shouldLogSlowSpans(): boolean {
  const e = env()
  if (isOneShotEnabled())
    return true
  return parseEnabled(e.prefixedStr('PERF_SPAN_LOG'))
}

function slowSpanThresholdMs(): number {
  const e = env()
  const raw = Number(e.prefixedStr('PERF_SPAN_SLOW_MS'))
  if (Number.isFinite(raw) && raw > 0)
    return Math.max(1, Math.floor(raw))
  return isOneShotEnabled() ? 5 : 50
}

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}`
}

type PerfSpan = Readonly<{ name: string, startedAt: number, detail?: string }>
interface PerfSpanState {
  stack: PerfSpan[]
}

type PerfSpanRecord = Readonly<{
  name: string
  detail?: string
  startedAt: number
  endedAt: number
  durationMs: number
}>

interface PerfHistoryState {
  records: PerfSpanRecord[]
  maxRecords: number
}

function writePerfLine(text: string): void {
  if (!isPerfEnabled())
    return
  const line = String(text ?? '').trim()
  if (!line)
    return
  try {
    appendFileSync(
      '/tmp/dimcode-perf.log',
      `[${formatClockTime(Date.now())}] ${line}\n`,
    )
  }
  catch {
    // ignore
  }
}

function getSpanState(): PerfSpanState {
  const g = runtimeGlobals()
  if (!g.__DIMCODE_PERF_SPAN__)
    g.__DIMCODE_PERF_SPAN__ = { stack: [] } satisfies PerfSpanState
  return g.__DIMCODE_PERF_SPAN__
}

function getHistoryState(): PerfHistoryState {
  const g = runtimeGlobals()
  if (!g.__DIMCODE_PERF_SPAN_HISTORY__) {
    const e = env()
    const rawMax = Number(e.prefixedStr('PERF_SPAN_HISTORY_MAX'))
    const computedMax
      = Number.isFinite(rawMax) && rawMax > 0
        ? Math.max(1000, Math.floor(rawMax))
        : isOneShotEnabled()
          ? 20000
          : 2000
    g.__DIMCODE_PERF_SPAN_HISTORY__ = {
      records: [],
      maxRecords: computedMax,
    } satisfies PerfHistoryState
  }
  return g.__DIMCODE_PERF_SPAN_HISTORY__
}

function recordSpan(span: PerfSpan, endedAt: number): void {
  const start = Number(span.startedAt || 0)
  const end = Number(endedAt || 0)
  const durationMs = Math.max(0, end - start)
  const rec: PerfSpanRecord = {
    name: span.name,
    ...(span.detail ? { detail: span.detail } : {}),
    startedAt: start,
    endedAt: end,
    durationMs,
  }

  const h = getHistoryState()
  h.records.push(rec)
  if (h.records.length > h.maxRecords)
    h.records.splice(0, h.records.length - h.maxRecords)

  if (shouldLogSlowSpans() && durationMs >= slowSpanThresholdMs()) {
    const detail = rec.detail ? ` detail=${rec.detail}` : ''
    writePerfLine(`[SPAN] ${rec.name} ${rec.durationMs}ms${detail}`)
  }
}

export function pushPerfSpan(name: string, detail?: string): void {
  const n = String(name ?? '').trim()
  if (!n)
    return
  const d = detail !== undefined ? String(detail ?? '').trim() : undefined
  const state = getSpanState()
  state.stack.push({
    name: n,
    startedAt: Date.now(),
    ...(d ? { detail: d } : {}),
  })
  // Avoid unbounded growth if someone forgets to pop.
  if (state.stack.length > 64)
    state.stack.splice(0, state.stack.length - 64)
}

export function popPerfSpan(name?: string): void {
  const state = getSpanState()
  if (state.stack.length === 0)
    return
  const endedAt = Date.now()
  if (!name) {
    const span = state.stack.pop()
    if (span)
      recordSpan(span, endedAt)
    return
  }
  const want = String(name ?? '').trim()
  // Pop until we find the matching span name (best-effort).
  for (let i = state.stack.length - 1; i >= 0; i--) {
    const cur = state.stack[i]
    state.stack.pop()
    if (cur)
      recordSpan(cur, endedAt)
    if (cur?.name === want)
      break
  }
}

export function withPerfSpan<T>(
  name: string,
  detail: string | undefined,
  fn: () => T,
): T {
  if (!isPerfEnabled())
    return fn()
  pushPerfSpan(name, detail)
  try {
    return fn()
  }
  finally {
    popPerfSpan(name)
  }
}

export async function withPerfSpanAsync<T>(
  name: string,
  detail: string | undefined,
  fn: () => Promise<T>,
): Promise<T> {
  if (!isPerfEnabled())
    return await fn()
  pushPerfSpan(name, detail)
  try {
    return await fn()
  }
  finally {
    popPerfSpan(name)
  }
}

export function getPerfSpanSnapshot(): {
  path: string
  depth: number
  topAgeMs: number
} | null {
  const state = getSpanState()
  if (!state.stack.length)
    return null
  const now = Date.now()
  const parts = state.stack.map((s) => {
    const d = s.detail ? `(${s.detail})` : ''
    return `${s.name}${d}`
  })
  const top = state.stack[state.stack.length - 1]!
  const age = Math.max(0, now - Number(top.startedAt || 0))
  return { path: parts.join('>'), depth: state.stack.length, topAgeMs: age }
}

export function getPerfSpanOverlapSummary(
  windowStartMs: number,
  windowEndMs: number,
): string {
  const start = Number(windowStartMs || 0)
  const end = Number(windowEndMs || 0)
  if (
    !Number.isFinite(start)
    || !Number.isFinite(end)
    || end <= 0
    || end < start
  ) {
    return ''
  }

  const overlaps: Array<{ rec: PerfSpanRecord, overlapMs: number }> = []

  // Completed spans
  const history = getHistoryState().records
  for (let i = history.length - 1; i >= 0; i--) {
    const r = history[i]!
    if (r.endedAt < start)
      break // records are in chronological order
    if (r.startedAt > end)
      continue
    const oStart = Math.max(start, r.startedAt)
    const oEnd = Math.min(end, r.endedAt)
    const overlapMs = Math.max(0, oEnd - oStart)
    if (overlapMs > 0)
      overlaps.push({ rec: r, overlapMs })
  }

  // Active spans (no end time yet): treat as ending at windowEnd.
  const active = getSpanState().stack
  for (const s of active) {
    const r: PerfSpanRecord = {
      name: s.name,
      ...(s.detail ? { detail: s.detail } : {}),
      startedAt: s.startedAt,
      endedAt: end,
      durationMs: Math.max(0, end - Number(s.startedAt || 0)),
    }
    if (r.startedAt > end)
      continue
    const oStart = Math.max(start, r.startedAt)
    const oEnd = end
    const overlapMs = Math.max(0, oEnd - oStart)
    if (overlapMs > 0)
      overlaps.push({ rec: r, overlapMs })
  }

  if (overlaps.length === 0)
    return ''

  overlaps.sort((a, b) => b.overlapMs - a.overlapMs)
  const top = overlaps.slice(0, 4).map(({ rec, overlapMs }) => {
    const d = rec.detail ? `(${rec.detail})` : ''
    return `${rec.name}${d}:${overlapMs}ms`
  })
  return ` spanOverlaps=${top.join(',')}`
}

export function getPerfSpanStats(): {
  activeDepth: number
  historySize: number
} {
  const activeDepth = getSpanState().stack.length
  const historySize = getHistoryState().records.length
  return { activeDepth, historySize }
}

export function perfLog(line: string): void {
  writePerfLine(line)
}
