## nimpak/cli/audit_commands.nim
## CLI commands for security audit and log management
##
## This module implements the CLI interface for Task 11.1d security audit
## functionality, including the `nip audit log --follow` command.

import std/[os, strutils, strformat, json, times, options, tables, sequtils]
import ../types_fixed
import ../security/event_logger
import ../security/revocation_manager

type
  AuditCommand* = enum
    AuditLog = "log"
    AuditKeys = "keys"
    AuditPackages = "packages"
    AuditIntegrity = "integrity"

  AuditOptions* = object
    command*: AuditCommand
    follow*: bool                 # --follow flag for real-time log streaming
    format*: string              # Output format: json, kdl, table
    startDate*: Option[DateTime] # --since date filter
    endDate*: Option[DateTime]   # --until date filter
    severity*: Option[SecuritySeverity] # --severity filter
    eventType*: Option[SecurityEventType] # --type filter
    keyId*: Option[string]       # --key-id filter
    packageName*: Option[string] # --package filter
    outputFile*: Option[string]  # --output file path
    verbose*: bool               # --verbose flag

# Forward declarations
proc outputEventsAsJson*(events: seq[SecurityEvent], outputFile: Option[string])
proc outputEventsAsKdl*(events: seq[SecurityEvent], outputFile: Option[string])
proc outputEventsAsTable*(events: seq[SecurityEvent], outputFile: Option[string], verbose: bool)

# =============================================================================
# CLI Command Parsing
# =============================================================================

proc parseAuditCommand*(args: seq[string]): Result[AuditOptions, string] =
  ## Parse audit command line arguments
  if args.len == 0:
    return err[AuditOptions, string]("No audit command specified. Use: nip audit log|keys|packages|integrity")

  var options = AuditOptions(
    command: AuditLog,
    follow: false,
    format: "table",
    startDate: none(DateTime),
    endDate: none(DateTime),
    severity: none(SecuritySeverity),
    eventType: none(SecurityEventType),
    keyId: none(string),
    packageName: none(string),
    outputFile: none(string),
    verbose: false
  )

  # Parse command
  case args[0]:
    of "log":
      options.command = AuditLog
    of "keys":
      options.command = AuditKeys
    of "packages":
      options.command = AuditPackages
    of "integrity":
      options.command = AuditIntegrity
    else:
      return err[AuditOptions, string](fmt"Unknown audit command: {args[0]}")

  # Parse flags
  var i = 1
  while i < args.len:
    case args[i]:
      of "--follow", "-f":
        options.follow = true
      of "--format":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--format requires a value")
        options.format = args[i + 1]
        inc i
      of "--since":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--since requires a date value")
        try:
          options.startDate = some(parse(args[i + 1], "yyyy-MM-dd"))
        except:
          return err[AuditOptions, string](fmt"Invalid date format for --since: {args[i + 1]}")
        inc i
      of "--until":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--until requires a date value")
        try:
          options.endDate = some(parse(args[i + 1], "yyyy-MM-dd"))
        except:
          return err[AuditOptions, string](fmt"Invalid date format for --until: {args[i + 1]}")
        inc i
      of "--severity":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--severity requires a value")
        case args[i + 1]:
          of "info":
            options.severity = some(SeverityInfo)
          of "warning":
            options.severity = some(SeverityWarning)
          of "error":
            options.severity = some(SeverityError)
          of "critical":
            options.severity = some(SeverityCritical)
          else:
            return err[AuditOptions, string](fmt"Invalid severity: {args[i + 1]}")
        inc i
      of "--type":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--type requires a value")
        case args[i + 1]:
          of "key_generation":
            options.eventType = some(EventKeyGeneration)
          of "key_revocation":
            options.eventType = some(EventKeyRevocation)
          of "key_rollover":
            options.eventType = some(EventKeyRollover)
          of "signature_verification":
            options.eventType = some(EventSignatureVerification)
          of "trust_violation":
            options.eventType = some(EventTrustViolation)
          else:
            return err[AuditOptions, string](fmt"Invalid event type: {args[i + 1]}")
        inc i
      of "--key-id":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--key-id requires a value")
        options.keyId = some(args[i + 1])
        inc i
      of "--package":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--package requires a value")
        options.packageName = some(args[i + 1])
        inc i
      of "--output", "-o":
        if i + 1 >= args.len:
          return err[AuditOptions, string]("--output requires a file path")
        options.outputFile = some(args[i + 1])
        inc i
      of "--verbose", "-v":
        options.verbose = true
      else:
        return err[AuditOptions, string](fmt"Unknown flag: {args[i]}")
    inc i

  return ok[AuditOptions, string](options)

# =============================================================================
# Log Audit Implementation
# =============================================================================

proc auditSecurityLog*(options: AuditOptions): VoidResult[string] =
  ## Audit security log with filtering and formatting
  let logPath = getEnv("NIP_SECURITY_LOG", "/var/log/nip/security.log")
  let casStore = getEnv("NIP_CAS_STORE", "/var/lib/nip/cas")

  let logger = newSecurityEventLogger(logPath, casStore)

  # Determine date range
  let startDate = options.startDate.get(now().utc() - initDuration(days = 30))
  let endDate = options.endDate.get(now().utc())

  # Get events in date range
  let events = logger.auditSecurityLog(startDate, endDate)

  # Filter events
  var filteredEvents: seq[SecurityEvent] = @[]
  for event in events:
    var shouldInclude = true

    if options.severity.isSome() and event.severity != options.severity.get():
      shouldInclude = false

    if options.eventType.isSome() and event.eventType != options.eventType.get():
      shouldInclude = false

    if options.keyId.isSome():
      let keyId = options.keyId.get()
      if not (event.metadata.hasKey("key_id") and event.metadata["key_id"].getStr() == keyId):
        shouldInclude = false

    if options.packageName.isSome():
      let packageName = options.packageName.get()
      if not (event.metadata.hasKey("package") and event.metadata["package"].getStr() == packageName):
        shouldInclude = false

    if shouldInclude:
      filteredEvents.add(event)

  # Format and output events
  case options.format:
    of "json":
      outputEventsAsJson(filteredEvents, options.outputFile)
    of "kdl":
      outputEventsAsKdl(filteredEvents, options.outputFile)
    of "table":
      outputEventsAsTable(filteredEvents, options.outputFile, options.verbose)
    else:
      return err[string](fmt"Unsupported output format: {options.format}")

  return ok(string)

proc followSecurityLog*(options: AuditOptions): VoidResult[string] =
  ## Follow security log in real-time
  let logPath = getEnv("NIP_SECURITY_LOG", "/var/log/nip/security.log")
  let casStore = getEnv("NIP_CAS_STORE", "/var/lib/nip/cas")

  let logger = newSecurityEventLogger(logPath, casStore)

  echo "🔍 Following security log (Ctrl+C to stop)..."
  echo "📁 Log file: ", logPath
  echo "🕒 Started at: ", now().format("yyyy-MM-dd HH:mm:ss")
  echo ""

  # TODO: Implement real-time file watching
  # For now, simulate with periodic polling
  while true:
    sleep(1000)  # Poll every second
    # TODO: Check for new events and display them
    # TODO: Apply filters from options
    # TODO: Format according to options.format

  return ok(string)

# =============================================================================
# Key Audit Implementation
# =============================================================================

proc auditKeys*(options: AuditOptions): VoidResult[string] =
  ## Audit key management events and revocation status
  let crlPath = getEnv("NIP_CRL_PATH", "/var/lib/nip/crl")
  let casStore = getEnv("NIP_CAS_STORE", "/var/lib/nip/cas")
  let logPath = getEnv("NIP_SECURITY_LOG", "/var/log/nip/security.log")

  let logger = newSecurityEventLogger(logPath, casStore)
  let manager = newRevocationManager(crlPath, casStore, logger)

  # Load current revocation list
  let crlResult = manager.loadRevocationList(FormatNexus)

  case options.format:
    of "json":
      if crlResult.isOk():
        let crl = crlResult.get()
        let output = %*{
          "revocation_list": {
            "version": crl.version,
            "issuer": crl.issuer,
            "issued_date": crl.issuedDate.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
            "next_update": crl.nextUpdate.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
            "entries_count": crl.entries.len,
            "revoked_keys": crl.entries.mapIt(%*{
              "key_id": it.keyId,
              "reason": $it.reason,
              "reason_text": it.reasonText,
              "revocation_date": it.revocationDate.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
              "emergency": it.emergencyRevocation
            })
          }
        }

        if options.outputFile.isSome():
          writeFile(options.outputFile.get(), output.pretty())
        else:
          echo output.pretty()
      else:
        echo %*{"error": "No revocation list found"}

    of "table":
      if crlResult.isOk():
        let crl = crlResult.get()
        echo "🔑 Key Revocation List Audit"
        echo "=" .repeat(50)
        echo fmt"Version: {crl.version}"
        echo fmt"Issuer: {crl.issuer}"
        let issuedStr = crl.issuedDate.format("yyyy-MM-dd HH:mm:ss")
        let nextUpdateStr = crl.nextUpdate.format("yyyy-MM-dd HH:mm:ss")
        echo fmt"Issued: {issuedStr}"
        echo fmt"Next Update: {nextUpdateStr}"
        echo fmt"Revoked Keys: {crl.entries.len}"
        echo ""

        if crl.entries.len > 0:
          echo "Revoked Keys:"
          echo "-" .repeat(80)
          echo "Key ID".alignLeft(20) & " " & "Reason".alignLeft(15) & " " & "Date".alignLeft(12) & " " & "Emergency".alignLeft(10)
          echo "-" .repeat(80)

          for entry in crl.entries:
            let emergency = if entry.emergencyRevocation: "YES" else: "NO"
            let dateStr = entry.revocationDate.format("yyyy-MM-dd")
            echo entry.keyId.alignLeft(20) & " " & ($entry.reason).alignLeft(15) & " " & dateStr.alignLeft(12) & " " & emergency.alignLeft(10)
      else:
        echo "❌ No revocation list found"

    else:
      return err[string](fmt"Unsupported format for key audit: {options.format}")

  return ok(string)

# =============================================================================
# Package Audit Implementation
# =============================================================================

proc auditPackages*(options: AuditOptions): VoidResult[string] =
  ## Audit package verification events
  let logPath = getEnv("NIP_SECURITY_LOG", "/var/log/nip/security.log")
  let casStore = getEnv("NIP_CAS_STORE", "/var/lib/nip/cas")

  let logger = newSecurityEventLogger(logPath, casStore)

  # Get package verification events
  let startDate = options.startDate.get(now().utc() - initDuration(days = 7))
  let endDate = options.endDate.get(now().utc())

  let events = logger.auditSecurityLog(startDate, endDate)

  # Filter for package-related events
  var packageEvents: seq[SecurityEvent] = @[]
  for event in events:
    if event.eventType in [EventPackageVerification, EventSignatureVerification, EventTrustViolation]:
      if options.packageName.isNone() or
         (event.metadata.hasKey("package") and event.metadata["package"].getStr() == options.packageName.get()):
        packageEvents.add(event)

  # Output results
  case options.format:
    of "table":
      echo "📦 Package Security Audit"
      echo "=" .repeat(60)
      let startStr = startDate.format("yyyy-MM-dd")
      let endStr = endDate.format("yyyy-MM-dd")
      echo fmt"Period: {startStr} to {endStr}"
      echo fmt"Events: {packageEvents.len}"
      echo ""

      if packageEvents.len > 0:
        echo "Package".alignLeft(20) & " " & "Event".alignLeft(20) & " " & "Severity".alignLeft(10) & " " & "Date".alignLeft(12)
        echo "-" .repeat(70)

        for event in packageEvents:
          let packageName = if event.metadata.hasKey("package"): event.metadata["package"].getStr() else: "unknown"
          let eventDateStr = event.timestamp.format("yyyy-MM-dd")
          echo packageName.alignLeft(20) & " " & ($event.eventType).alignLeft(20) & " " & ($event.severity).alignLeft(10) & " " & eventDateStr.alignLeft(12)

    of "json":
      let output = %*{
        "package_audit": {
          "period": {
            "start": startDate.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
            "end": endDate.format("yyyy-MM-dd'T'HH:mm:ss'Z'")
          },
          "events_count": packageEvents.len,
          "events": packageEvents.mapIt(%*{
            "id": it.id,
            "timestamp": it.timestamp.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
            "type": $it.eventType,
            "severity": $it.severity,
            "package": if it.metadata.hasKey("package"): it.metadata["package"].getStr() else: "unknown",
            "message": it.message
          })
        }
      }

      if options.outputFile.isSome():
        writeFile(options.outputFile.get(), output.pretty())
      else:
        echo output.pretty()

    else:
      return err[string](fmt"Unsupported format for package audit: {options.format}")

  return ok(string)

# =============================================================================
# Integrity Audit Implementation
# =============================================================================

proc auditIntegrity*(options: AuditOptions): VoidResult[string] =
  ## Audit log integrity and hash chain verification
  let logPath = getEnv("NIP_SECURITY_LOG", "/var/log/nip/security.log")
  let casStore = getEnv("NIP_CAS_STORE", "/var/lib/nip/cas")

  let logger = newSecurityEventLogger(logPath, casStore)

  echo "🔍 Verifying security log integrity..."

  let verificationResult = logger.verifyLogIntegrity()

  case options.format:
    of "table":
      echo "🛡️  Security Log Integrity Audit"
      echo "=" .repeat(50)

      if verificationResult.valid:
        echo "✅ Log integrity: VALID"
        echo "🔗 Hash chain: INTACT"
        echo "📝 All events: VERIFIED"
      else:
        echo "❌ Log integrity: COMPROMISED"
        echo "🚨 Issues found:"
        for error in verificationResult.errors:
          echo fmt"  • {error}"

    of "json":
      let output = %*{
        "integrity_audit": {
          "timestamp": now().utc().format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
          "log_file": logPath,
          "cas_store": casStore,
          "valid": verificationResult.valid,
          "errors": verificationResult.errors
        }
      }

      if options.outputFile.isSome():
        writeFile(options.outputFile.get(), output.pretty())
      else:
        echo output.pretty()

    else:
      return err[string](fmt"Unsupported format for integrity audit: {options.format}")

  return ok(string)

# =============================================================================
# Output Formatting Functions
# =============================================================================

proc outputEventsAsJson*(events: seq[SecurityEvent], outputFile: Option[string]) =
  ## Output events in JSON format
  let output = %*{
    "security_events": events.mapIt(%*{
      "id": it.id,
      "timestamp": it.timestamp.format("yyyy-MM-dd'T'HH:mm:ss'Z'"),
      "type": $it.eventType,
      "severity": $it.severity,
      "source": it.source,
      "message": it.message,
      "metadata": it.metadata,
      "hash_chain_prev": it.hashChainPrev,
      "hash_chain_current": it.hashChainCurrent
    })
  }

  if outputFile.isSome():
    writeFile(outputFile.get(), output.pretty())
  else:
    echo output.pretty()

proc outputEventsAsKdl*(events: seq[SecurityEvent], outputFile: Option[string]) =
  ## Output events in KDL format
  var kdlOutput = "security_events {\n"

  for event in events:
    let timestampStr = event.timestamp.format("yyyy-MM-dd'T'HH:mm:ss'Z'")
    kdlOutput.add(fmt"""
  event "{event.id}" {{
    timestamp "{timestampStr}"
    type "{event.eventType}"
    severity "{event.severity}"
    source "{event.source}"
    message "{event.message}"
    hash_chain_prev "{event.hashChainPrev}"
    hash_chain_current "{event.hashChainCurrent}"
  }}
""")

  kdlOutput.add("}\n")

  if outputFile.isSome():
    writeFile(outputFile.get(), kdlOutput)
  else:
    echo kdlOutput

proc outputEventsAsTable*(events: seq[SecurityEvent], outputFile: Option[string], verbose: bool) =
  ## Output events in table format
  var output = ""

  output.add("🔍 Security Events\n")
  output.add("=" .repeat(80) & "\n")

  if events.len == 0:
    output.add("No events found matching criteria.\n")
  else:
    if verbose:
      output.add("ID".alignLeft(20) & " " & "Timestamp".alignLeft(20) & " " & "Type".alignLeft(15) & " " & "Severity".alignLeft(10) & " " & "Source".alignLeft(15) & "\n")
      output.add("-" .repeat(80) & "\n")

      for event in events:
        let timeStr = event.timestamp.format("MM-dd HH:mm:ss")
        output.add(fmt"{event.id:<20} {timeStr:<20} {$event.eventType:<15} {$event.severity:<10} {event.source:<15}\n")
        output.add(fmt"  Message: {event.message}\n")
        if event.metadata != newJNull():
          output.add(fmt"  Metadata: {event.metadata.pretty(indent = 0)}\n")
        output.add("\n")
    else:
      output.add("Timestamp".alignLeft(20) & " " & "Type".alignLeft(15) & " " & "Severity".alignLeft(10) & " " & "Message".alignLeft(30) & "\n")
      output.add("-" .repeat(75) & "\n")

      for event in events:
        let shortMessage = if event.message.len > 30: event.message[0..26] & "..." else: event.message
        let timeStr2 = event.timestamp.format("MM-dd HH:mm:ss")
        output.add(fmt"{timeStr2:<20} {$event.eventType:<15} {$event.severity:<10} {shortMessage:<30}\n")

  if outputFile.isSome():
    writeFile(outputFile.get(), output)
  else:
    echo output

# =============================================================================
# Main CLI Entry Point
# =============================================================================

proc executeAuditCommand*(args: seq[string]): VoidResult[string] =
  ## Execute audit command based on parsed arguments
  let optionsResult = parseAuditCommand(args)
  if optionsResult.isErr():
    return err[string](optionsResult.errValue)

  let options = optionsResult.get()

  case options.command:
    of AuditLog:
      if options.follow:
        return followSecurityLog(options)
      else:
        return auditSecurityLog(options)
    of AuditKeys:
      return auditKeys(options)
    of AuditPackages:
      return auditPackages(options)
    of AuditIntegrity:
      return auditIntegrity(options)

# =============================================================================
# Export main functions
# =============================================================================

export AuditCommand, AuditOptions
export parseAuditCommand, executeAuditCommand
export auditSecurityLog, followSecurityLog, auditKeys, auditPackages, auditIntegrity