# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.

## NimPak Structured Logging
##
## Comprehensive logging system for the NimPak package manager.
## Provides structured logging with multiple output formats and levels.
## Task 38: Add comprehensive logging.

import std/[times, json, strformat, strutils, os, terminal, tables, locks]

type
  LogLevel* = enum
    Trace = 0,    ## Detailed tracing (development only)
    Debug = 1,    ## Debug information
    Info = 2,     ## General information
    Warn = 3,     ## Warnings (potential issues)
    Error = 4,    ## Errors (operation failed)
    Fatal = 5,    ## Fatal errors (application cannot continue)
    Audit = 6     ## Audit log (security-relevant events)

  LogOutput* = enum
    Console,      ## Console (stdout/stderr)
    File,         ## Log file
    Json,         ## JSON structured log
    Syslog        ## System log (future)

  LogEntry* = object
    timestamp*: DateTime
    level*: LogLevel
    component*: string
    message*: string
    context*: Table[string, string]
    duration*: float  ## Operation duration in ms (if applicable)

  Logger* = object
    minLevel*: LogLevel
    outputs*: set[LogOutput]
    logFile*: string
    jsonFile*: string
    useColors*: bool
    component*: string
    fileHandle: File
    jsonHandle: File
    lock: Lock

const
  LevelColors: array[LogLevel, ForegroundColor] = [
    fgWhite,   # Trace
    fgCyan,    # Debug
    fgGreen,   # Info
    fgYellow,  # Warn
    fgRed,     # Error
    fgMagenta, # Fatal
    fgBlue     # Audit
  ]
  
  LevelNames: array[LogLevel, string] = [
    "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "AUDIT"
  ]

var
  globalLogger: Logger
  loggerInitialized = false

# ############################################################################
# Logger Initialization
# ############################################################################

proc initLogger*(component: string = "nip",
                 minLevel: LogLevel = Info,
                 outputs: set[LogOutput] = {Console},
                 logFile: string = "",
                 jsonFile: string = "",
                 useColors: bool = true): Logger =
  ## Initialize a new logger
  result = Logger(
    minLevel: minLevel,
    outputs: outputs,
    logFile: logFile,
    jsonFile: jsonFile,
    useColors: useColors and isatty(stdout),
    component: component
  )
  initLock(result.lock)
  
  if logFile.len > 0 and File in outputs:
    createDir(logFile.parentDir)
    result.fileHandle = open(logFile, fmAppend)
  
  if jsonFile.len > 0 and Json in outputs:
    createDir(jsonFile.parentDir)
    result.jsonHandle = open(jsonFile, fmAppend)

proc initGlobalLogger*(component: string = "nip",
                       minLevel: LogLevel = Info,
                       outputs: set[LogOutput] = {Console},
                       logFile: string = "",
                       jsonFile: string = "") =
  ## Initialize the global logger
  globalLogger = initLogger(component, minLevel, outputs, logFile, jsonFile)
  loggerInitialized = true

proc closeLogger*(logger: var Logger) =
  ## Close logger file handles
  if logger.logFile.len > 0 and Console in logger.outputs:
    logger.fileHandle.close()
  if logger.jsonFile.len > 0 and Json in logger.outputs:
    logger.jsonHandle.close()
  deinitLock(logger.lock)

# ############################################################################
# Log Formatting
# ############################################################################

proc formatConsole(entry: LogEntry, useColors: bool): string =
  ## Format log entry for console output
  let timeStr = entry.timestamp.format("HH:mm:ss")
  let levelStr = LevelNames[entry.level]
  
  if useColors:
    let colorCode = ord(LevelColors[entry.level])
    result = fmt"[\e[90m{timeStr}\e[0m] \e[{colorCode}m{levelStr:5}\e[0m [{entry.component}] {entry.message}"
  else:
    result = fmt"[{timeStr}] {levelStr:5} [{entry.component}] {entry.message}"
  
  # Add context if present
  if entry.context.len > 0:
    result.add " {"
    var first = true
    for key, value in entry.context:
      if not first: result.add ", "
      result.add fmt"{key}={value}"
      first = false
    result.add "}"
  
  # Add duration if present
  if entry.duration > 0:
    result.add fmt" ({entry.duration:.2f}ms)"

proc formatJson(entry: LogEntry): string =
  ## Format log entry as JSON
  let obj = %*{
    "timestamp": entry.timestamp.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
    "level": $entry.level,
    "component": entry.component,
    "message": entry.message
  }
  
  if entry.context.len > 0:
    var ctx = newJObject()
    for key, value in entry.context:
      ctx[key] = %value
    obj["context"] = ctx
  
  if entry.duration > 0:
    obj["duration_ms"] = %entry.duration
  
  result = $obj

proc formatFile(entry: LogEntry): string =
  ## Format log entry for file output (plain text with full timestamp)
  let timeStr = entry.timestamp.format("yyyy-MM-dd HH:mm:ss")
  let levelStr = LevelNames[entry.level]
  result = fmt"[{timeStr}] {levelStr:5} [{entry.component}] {entry.message}"
  
  if entry.context.len > 0:
    for key, value in entry.context:
      result.add fmt"\n  {key}: {value}"

# ############################################################################
# Logging Functions
# ############################################################################

proc log*(logger: var Logger, level: LogLevel, message: string,
          context: Table[string, string] = initTable[string, string](),
          duration: float = 0.0) =
  ## Log a message with the given level
  if level < logger.minLevel:
    return
  
  let entry = LogEntry(
    timestamp: now(),
    level: level,
    component: logger.component,
    message: message,
    context: context,
    duration: duration
  )
  
  acquire(logger.lock)
  defer: release(logger.lock)
  
  if Console in logger.outputs:
    let formatted = formatConsole(entry, logger.useColors)
    if level >= Error:
      stderr.writeLine(formatted)
    else:
      stdout.writeLine(formatted)
  
  if File in logger.outputs and logger.logFile.len > 0:
    logger.fileHandle.writeLine(formatFile(entry))
    logger.fileHandle.flushFile()
  
  if Json in logger.outputs and logger.jsonFile.len > 0:
    logger.jsonHandle.writeLine(formatJson(entry))
    logger.jsonHandle.flushFile()

# Convenience functions for global logger
proc log*(level: LogLevel, message: string,
          context: Table[string, string] = initTable[string, string]()) =
  if loggerInitialized:
    globalLogger.log(level, message, context)

proc trace*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Trace, message, context)

proc debug*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Debug, message, context)

proc info*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Info, message, context)

proc warn*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Warn, message, context)

proc error*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Error, message, context)

proc fatal*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Fatal, message, context)

proc audit*(message: string, context: Table[string, string] = initTable[string, string]()) =
  log(Audit, message, context)

# ############################################################################
# Performance Metrics Logging
# ############################################################################

type
  Timer* = object
    startTime: float
    name: string
    logger: ptr Logger

proc startTimer*(logger: var Logger, name: string): Timer =
  ## Start a performance timer
  result = Timer(
    startTime: epochTime(),
    name: name,
    logger: addr logger
  )

proc stop*(timer: Timer): float =
  ## Stop timer and log the duration
  result = (epochTime() - timer.startTime) * 1000.0  # Convert to ms
  var ctx = initTable[string, string]()
  ctx["operation"] = timer.name
  timer.logger[].log(Debug, fmt"Operation '{timer.name}' completed", ctx, result)

template timed*(logger: var Logger, name: string, body: untyped) =
  ## Execute body and log duration
  let timer = logger.startTimer(name)
  body
  discard timer.stop()

# ############################################################################
# Audit Logging
# ############################################################################

proc auditEvent*(logger: var Logger, event: string, user: string = "",
                 resource: string = "", action: string = "", success: bool = true) =
  ## Log an audit event
  var ctx = initTable[string, string]()
  if user.len > 0: ctx["user"] = user
  if resource.len > 0: ctx["resource"] = resource
  if action.len > 0: ctx["action"] = action
  ctx["success"] = $success
  
  logger.log(Audit, event, ctx)

proc auditPackageOp*(logger: var Logger, operation: string, packageName: string,
                     version: string = "", success: bool = true) =
  ## Log a package operation audit event
  var ctx = initTable[string, string]()
  ctx["package"] = packageName
  if version.len > 0: ctx["version"] = version
  ctx["operation"] = operation
  ctx["success"] = $success
  
  logger.log(Audit, fmt"Package {operation}: {packageName}", ctx)

proc auditCasOp*(logger: var Logger, operation: string, hash: string,
                 format: string = "", success: bool = true) =
  ## Log a CAS operation audit event
  var ctx = initTable[string, string]()
  ctx["hash"] = hash[0..min(20, hash.len-1)]
  if format.len > 0: ctx["format"] = format
  ctx["operation"] = operation
  ctx["success"] = $success
  
  logger.log(Audit, fmt"CAS {operation}", ctx)

# ############################################################################
# Log Level Parsing
# ############################################################################

proc parseLogLevel*(s: string): LogLevel =
  ## Parse log level from string
  case s.toLowerAscii():
  of "trace": Trace
  of "debug": Debug
  of "info": Info
  of "warn", "warning": Warn
  of "error": Error
  of "fatal": Fatal
  of "audit": Audit
  else: Info

proc setLogLevel*(logger: var Logger, level: LogLevel) =
  logger.minLevel = level

proc setLogLevel*(level: LogLevel) =
  if loggerInitialized:
    globalLogger.minLevel = level
