import { closeSync, fstatSync, openSync, readSync } from 'fs'
import { getPublicAppConfig } from './config-loader.ts'
import { getLogFilePath } from './logger.ts'
import { getPerfSnapshot } from './perf-metrics.ts'
import { getRuntimeInputDiagnostics } from './runtime-input-diagnostics.ts'
import { getRuntimeStatus } from './runtime-status.ts'
import { getBundledOpencodeVersion } from './runtime-opencode-cli.ts'
import { sanitizeForExport } from './log-sanitizer.ts'
import { maskEffectiveSettingsCredentials } from './settings.ts'
import { getEffectiveSettings } from './settings.ts'

// Number of log tail lines included in the bundle. Enough to cover a
// normal session's error window without bloating the text beyond what's
// reasonable to paste into a bug report.
const LOG_TAIL_LINES = 200
// Hard cap on the log tail byte count — defense-in-depth against a
// pathological log file. 512 KB is comfortably larger than
// LOG_TAIL_LINES * typical-line-length but still copy-paste friendly.
const LOG_TAIL_MAX_BYTES = 512 * 1024

export function tailLogFile(path: string, lines: number, maxBytes: number): string {
  try {
    if (!path) return '(no log file configured)'
    const fd = openSync(path, 'r')
    try {
      const stat = fstatSync(fd)
      if (!stat.isFile()) return '(log path is not a file)'
      const readBytes = Math.min(stat.size, maxBytes)
      // Read the tail without loading the whole file into memory.
      const buffer = Buffer.alloc(readBytes)
      readSync(fd, buffer, 0, readBytes, Math.max(0, stat.size - readBytes))
      const text = buffer.toString('utf-8')
      const split = text.split('\n')
      // Drop the first partial line if we started mid-line.
      const usable = stat.size > maxBytes ? split.slice(1) : split
      return usable.slice(-lines).join('\n')
    } finally {
      closeSync(fd)
    }
  } catch (err) {
    return `(could not read log: ${(err as Error).message})`
  }
}

function section(title: string, body: string) {
  return `=== ${sanitizeForExport(title)} ===\n${body.trimEnd()}\n`
}

function jsonForDiagnostics(value: unknown) {
  return sanitizeForExport(JSON.stringify(value, null, 2))
}

export function buildDiagnosticsBundle(): string {
  const now = new Date().toISOString()
  const config = getPublicAppConfig()
  const runtimeInputs = getRuntimeInputDiagnostics()
  const runtimeStatus = getRuntimeStatus()
  const perf = getPerfSnapshot()
  const settings = maskEffectiveSettingsCredentials(getEffectiveSettings())
  // Log tail gets the export-grade sanitizer on top of the in-line
  // sanitizer that already ran when each line was written. Strips
  // /Users/<name> and /home/<name> paths so bundles shared publicly
  // don't leak filesystem layout or usernames.
  const logTail = sanitizeForExport(tailLogFile(getLogFilePath(), LOG_TAIL_LINES, LOG_TAIL_MAX_BYTES))

  const header = [
    `${config.branding.name} diagnostics`,
    `Generated: ${now}`,
    `Branding: ${config.branding.name} (${config.branding.appId})`,
    `Platform: ${process.platform} ${process.arch} node ${process.version}`,
    `Electron: ${process.versions.electron || 'n/a'}`,
    `OpenCode CLI: ${getBundledOpencodeVersion() || 'n/a'}`,
    `Runtime ready: ${runtimeStatus.ready}${runtimeStatus.error ? ` (error: ${sanitizeForExport(runtimeStatus.error)})` : ''}`,
  ].join('\n')

  const parts = [
    section('Header', header),
    section('Effective Config (public, credentials redacted)', jsonForDiagnostics(config)),
    section('Effective Settings (credentials masked)', jsonForDiagnostics(settings)),
    section('Runtime Inputs', jsonForDiagnostics(runtimeInputs)),
    section('Runtime Doctor JSON', jsonForDiagnostics(runtimeStatus)),
    section('Perf Snapshot', jsonForDiagnostics(perf)),
    section(`Log Tail (last ${LOG_TAIL_LINES} lines, ${getLogFilePath()})`, logTail),
  ]

  return parts.join('\n')
}
