## nip/doctor.nim
## Implementationnip doctor command for system health checks
##
## This module implements the nip doctor command that provides comprehensive
## system health diagnostics including integrity checks, keyring health, and more.

import std/[os, strutils, times, json, sequtils, strformat, algorithm, tables]
import ../nimpak/security/[integrity_monitor, hash_verifier, signature_verifier_working, keyring_manager, event_logger]
import ../nimpak/cli/core

type
  DoctorOptions* = object
    integrityCheck*: bool
    keyringCheck*: bool
    performanceCheck*: bool
    autoRepair*: bool
    verbose*: bool
    outputFormat*: OutputFormat

  HealthCheckCategory* = enum
    HealthIntegrity = "integrity"
    HealthKeyring = "keyring"
    HealthPerformance = "performance"
    HealthConfiguration = "configuration"
    HealthStorage = "storage"

  SystemHealthReport* = object
    overallStatus*: string
    categories*: seq[CategoryHealth]
    recommendations*: seq[string]
    statistics*: JsonNode
    timestamp*: times.DateTime
    duration*: float

  CategoryHealth* = object
    category*: HealthCheckCategory
    status*: string
    score*: float
    issues*: seq[string]
    details*: JsonNode

# Helper functions for disk space and directory size
proc getFreeDiskSpace*(path: string): int64 =
  ## Get free disk space for a path (placeholder implementation)
  try:
    # This is a simplified implementation
    # In a real implementation, you'd use system calls
    return 10_000_000_000  # 10GB placeholder
  except:
    return 0

proc getDirSize*(path: string): int64 =
  ## Get total size of directory (placeholder implementation)
  try:
    var totalSize: int64 = 0
    for file in walkDirRec(path):
      totalSize += getFileSize(file)
    return totalSize
  except:
    return 0

proc parseDoctorOptions*(args: seq[string]): DoctorOptions =
  ## Parse nip doctor command arguments
  var options = DoctorOptions(
    integrityCheck: false,
    keyringCheck: false,
    performanceCheck: false,
    autoRepair: false,
    verbose: false,
    outputFormat: OutputHuman
  )

  # If no specific checks requested, enable all
  if args.len == 0:
    options.integrityCheck = true
    options.keyringCheck = true
    options.performanceCheck = true
    return options

  var i = 0
  while i < args.len:
    case args[i]:
    of "--integrity":
      options.integrityCheck = true
    of "--keyring":
      options.keyringCheck = true
    of "--performance":
      options.performanceCheck = true
    of "--auto-repair":
      options.autoRepair = true
    of "--verbose", "-v":
      options.verbose = true
    of "--output":
      if i + 1 < args.len:
        case args[i + 1].toLower():
        of "json": options.outputFormat = OutputJson
        of "yaml": options.outputFormat = OutputYaml
        of "kdl": options.outputFormat = OutputKdl
        else: options.outputFormat = OutputHuman
        i += 1
    else:
      # If no specific flags, enable all checks
      if not (options.integrityCheck or options.keyringCheck or options.performanceCheck):
        options.integrityCheck = true
        options.keyringCheck = true
        options.performanceCheck = true
    i += 1

  return options

proc runIntegrityHealthCheck*(options: DoctorOptions): CategoryHealth =
  ## Run comprehensive integrity health check
  let startTime = cpuTime()

  var categoryHealth = CategoryHealth(
    category: HealthIntegrity,
    status: "unknown",
    score: 0.0,
    issues: @[],
    details: newJObject()
  )

  try:
    if options.verbose:
      showInfo("Running integrity health check...")

    # Initialize integrity monitor
    let monitor = newIntegrityMonitor(getDefaultIntegrityConfig())
    let integrityResult = runIntegrityHealthCheck(monitor)

    # Extract statistics from integrity check
    let stats = integrityResult.details
    categoryHealth.details = stats

    # Determine health score based on results
    let totalPackages = stats["statistics"]["packages_checked"].getInt()
    let integrityPassed = stats["statistics"]["integrity_passed"].getInt()
    let signaturesPassed = stats["statistics"]["signatures_verified"].getInt()
    let totalIssues = stats["total_issues"].getInt()

    if totalPackages > 0:
      let integrityScore = integrityPassed.float / totalPackages.float
      let signatureScore = if totalPackages > 0: signaturesPassed.float / totalPackages.float else: 1.0
      categoryHealth.score = (integrityScore + signatureScore) / 2.0
    else:
      categoryHealth.score = 0.0

    # Determine status
    if integrityResult.success and totalIssues == 0:
      categoryHealth.status = "healthy"
    elif totalIssues <= 5:  # Configurable threshold
      categoryHealth.status = "warning"
      categoryHealth.issues.add(fmt"Found {totalIssues} integrity issues")
    else:
      categoryHealth.status = "critical"
      categoryHealth.issues.add(fmt"Found {totalIssues} integrity issues (above threshold)")

    # Add specific issues from the integrity check
    if stats.hasKey("issues"):
      for issue in stats["issues"]:
        categoryHealth.issues.add(issue.getStr())

    if options.verbose:
      showSuccess(fmt"Integrity check completed: {categoryHealth.status}")

  except Exception as e:
    categoryHealth.status = "error"
    categoryHealth.score = 0.0
    categoryHealth.issues.add(fmt"Integrity check failed: {e.msg}")
    errorLog(fmt"Integrity health check error: {e.msg}")

  return categoryHealth

proc runKeyringHealthCheck*(options: DoctorOptions): CategoryHealth =
  ## Run keyring health check
  var categoryHealth = CategoryHealth(
    category: HealthKeyring,
    status: "unknown",
    score: 0.0,
    issues: @[],
    details: newJObject()
  )

  try:
    if options.verbose:
      showInfo("Running keyring health check...")

    # Initialize keyring manager
    let config = getDefaultKeyringConfig()
    var keyringManager = newKeyringManager(config)
    keyringManager.loadAllKeyrings()

    # Get keyring statistics
    let stats = keyringManager.getKeyringStatistics()
    categoryHealth.details = stats

    let totalKeys = stats["total_keys"].getInt()
    let validKeys = stats["valid_keys"].getInt()
    let expiredKeys = stats["expired_keys"].getInt()
    let revokedKeys = stats["revoked_keys"].getInt()

    # Calculate health score
    if totalKeys > 0:
      categoryHealth.score = validKeys.float / totalKeys.float
    else:
      categoryHealth.score = 0.0
      categoryHealth.issues.add("No keys found in keyring")

    # Determine status
    if expiredKeys == 0 and revokedKeys == 0 and totalKeys > 0:
      categoryHealth.status = "healthy"
    elif expiredKeys > 0 or revokedKeys > 0:
      categoryHealth.status = "warning"
      if expiredKeys > 0:
        categoryHealth.issues.add(fmt"Found {expiredKeys} expired keys")
      if revokedKeys > 0:
        categoryHealth.issues.add(fmt"Found {revokedKeys} revoked keys")
    else:
      categoryHealth.status = "critical"

    if options.verbose:
      showSuccess(fmt"Keyring check completed: {categoryHealth.status}")

  except Exception as e:
    categoryHealth.status = "error"
    categoryHealth.score = 0.0
    categoryHealth.issues.add(fmt"Keyring check failed: {e.msg}")
    errorLog(fmt"Keyring health check error: {e.msg}")

  return categoryHealth

proc runPerformanceHealthCheck*(options: DoctorOptions): CategoryHealth =
  ## Run performance health check
  var categoryHealth = CategoryHealth(
    category: HealthPerformance,
    status: "unknown",
    score: 0.0,
    issues: @[],
    details: newJObject()
  )

  try:
    if options.verbose:
      showInfo("Running performance health check...")

    # Check disk space
    let programsSpace = getFreeDiskSpace("/Programs")
    let cacheSpace = getFreeDiskSpace("/var/cache/nip")

    # Check package count and sizes
    var packageCount = 0
    var totalSize: int64 = 0

    if dirExists("/Programs"):
      for packageDir in walkDirs("/Programs/*"):
        inc packageCount
        totalSize += getDirSize(packageDir)

    # Performance metrics
    let stats = %*{
      "package_count": packageCount,
      "total_size_bytes": totalSize,
      "programs_free_space": programsSpace,
      "cache_free_space": cacheSpace,
      "avg_package_size": if packageCount > 0: totalSize div packageCount else: 0
    }

    categoryHealth.details = stats

    # Calculate performance score based on available space and package efficiency
    var score = 1.0

    # Penalize if low disk space
    if programsSpace < 1_000_000_000:  # Less than 1GB
      score -= 0.3
      categoryHealth.issues.add("Low disk space in /Programs")

    if cacheSpace < 500_000_000:  # Less than 500MB
      score -= 0.2
      categoryHealth.issues.add("Low disk space in cache")

    categoryHealth.score = max(0.0, score)

    # Determine status
    if categoryHealth.issues.len == 0:
      categoryHealth.status = "healthy"
    elif categoryHealth.score > 0.7:
      categoryHealth.status = "warning"
    else:
      categoryHealth.status = "critical"

    if options.verbose:
      showSuccess(fmt"Performance check completed: {categoryHealth.status}")

  except Exception as e:
    categoryHealth.status = "error"
    categoryHealth.score = 0.0
    categoryHealth.issues.add(fmt"Performance check failed: {e.msg}")
    errorLog(fmt"Performance health check error: {e.msg}")

  return categoryHealth

proc generateRecommendations*(categories: seq[CategoryHealth]): seq[string] =
  ## Generate recommendations based on health check results
  var recommendations: seq[string] = @[]

  for category in categories:
    case category.category:
    of HealthIntegrity:
      if category.status == "critical":
        recommendations.add("Run 'nip verify --all --auto-repair' to fix integrity issues")
      elif category.status == "warning":
        recommendations.add("Consider running 'nip verify --all' to check specific issues")

    of HealthKeyring:
      if category.status == "warning" or category.status == "critical":
        recommendations.add("Update keyring with 'nip key update' to refresh expired keys")
        recommendations.add("Remove revoked keys with 'nip key cleanup'")

    of HealthPerformance:
      if category.status == "critical":
        recommendations.add("Free up disk space or move packages to larger storage")
        recommendations.add("Run 'nip clean' to remove unnecessary cache files")
      elif category.status == "warning":
        recommendations.add("Consider cleaning package cache with 'nip clean --cache'")

    else:
      discard

  if recommendations.len == 0:
    recommendations.add("System health is good - no immediate actions required")

  return recommendations

proc displayHealthReport*(report: SystemHealthReport, options: DoctorOptions) =
  ## Display health report in human-readable format
  echo bold("🩺 NimPak System Health Report")
  echo "=".repeat(50)
  echo "Generated: " & report.timestamp.format("yyyy-MM-dd HH:mm:ss")
  echo fmt"Duration: {report.duration:.2f}s"
  echo ""

  # Overall status
  let statusSymbol = case report.overallStatus:
    of "healthy": success("✅")
    of "warning": warning("⚠️")
    of "critical": error("🚨")
    else: "❓"

  echo fmt"Overall Status: {statusSymbol} {report.overallStatus.toUpper()}"
  echo ""

  # Category details
  for category in report.categories:
    let categorySymbol = case category.status:
      of "healthy": success("✅")
      of "warning": warning("⚠️")
      of "critical": error("🚨")
      of "error": error("❌")
      else: "❓"

    echo fmt"{categorySymbol} {($category.category).capitalizeAscii()}: {category.status} (score: {category.score:.2f})"

    if category.issues.len > 0:
      for issue in category.issues:
        echo fmt"    • {issue}"

    if options.verbose and category.details != nil:
      echo "    Details:"
      for key, value in category.details.pairs:
        echo fmt"      {key}: {value}"

    echo ""

  # Recommendations
  if report.recommendations.len > 0:
    echo bold("💡 Recommendations:")
    for i, rec in report.recommendations:
      echo fmt"  {i + 1}. {rec}"
    echo ""

  # Statistics summary
  if options.verbose and report.statistics != nil:
    echo bold("📊 System Statistics:")
    for key, value in report.statistics.pairs:
      echo fmt"  {key}: {value}"

proc runSystemHealthCheck*(options: DoctorOptions): SystemHealthReport =
  ## Run comprehensive system health check
  let startTime = cpuTime()

  var report = SystemHealthReport(
    overallStatus: "unknown",
    categories: @[],
    recommendations: @[],
    statistics: newJObject(),
    timestamp: now(),
    duration: 0.0
  )

  try:
    showInfo("🩺 Starting comprehensive system health check...")

    # Run individual health checks
    if options.integrityCheck:
      report.categories.add(runIntegrityHealthCheck(options))

    if options.keyringCheck:
      report.categories.add(runKeyringHealthCheck(options))

    if options.performanceCheck:
      report.categories.add(runPerformanceHealthCheck(options))

    # Calculate overall status
    var totalScore = 0.0
    var criticalCount = 0
    var warningCount = 0
    var healthyCount = 0

    for category in report.categories:
      totalScore += category.score
      case category.status:
      of "critical", "error": inc criticalCount
      of "warning": inc warningCount
      of "healthy": inc healthyCount

    let avgScore = if report.categories.len > 0: totalScore / report.categories.len.float else: 0.0

    # Determine overall status
    if criticalCount > 0:
      report.overallStatus = "critical"
    elif warningCount > 0:
      report.overallStatus = "warning"
    elif healthyCount > 0:
      report.overallStatus = "healthy"
    else:
      report.overallStatus = "unknown"

    # Generate recommendations
    report.recommendations = generateRecommendations(report.categories)

    # Compile statistics
    report.statistics = %*{
      "categories_checked": report.categories.len,
      "healthy_categories": healthyCount,
      "warning_categories": warningCount,
      "critical_categories": criticalCount,
      "average_score": avgScore,
      "check_duration": cpuTime() - startTime
    }

    report.duration = cpuTime() - startTime

    showSuccess(fmt"Health check completed: {report.overallStatus}")

  except Exception as e:
    report.overallStatus = "error"
    report.recommendations.add(fmt"Health check failed: {e.msg}")
    errorLog(fmt"System health check error: {e.msg}")

  return report

proc nipDoctorCommand*(args: seq[string]): CommandResult =
  ## Main implementation of nip doctor command
  try:
    let options = parseDoctorOptions(args)

    # Run health check
    let report = runSystemHealthCheck(options)

    # Display results
    case options.outputFormat:
    of OutputHuman:
      displayHealthReport(report, options)
    else:
      let reportJson = %*{
        "overall_status": report.overallStatus,
        "categories": report.categories.mapIt(%*{
          "category": $it.category,
          "status": it.status,
          "score": it.score,
          "issues": it.issues,
          "details": it.details
        }),
        "recommendations": report.recommendations,
        "statistics": report.statistics,
        "timestamp": $report.timestamp,
        "duration": report.duration
      }
      outputData(reportJson)

    # Log health check event
    let severity = case report.overallStatus:
      of "healthy": SeverityInfo
      of "warning": SeverityWarning
      of "critical": SeverityCritical
      else: SeverityError

    logGlobalSecurityEvent(EventSystemHealthCheck, severity, "nip-doctor",
      fmt"System health check completed: {report.overallStatus}")

    # Return appropriate result
    case report.overallStatus:
    of "healthy":
      return successResult("System health check passed - all systems healthy")
    of "warning":
      return successResult("System health check completed with warnings")
    of "critical":
      return errorResult("System health check found critical issues", 1)
    else:
      return errorResult("System health check encountered errors", 2)

  except Exception as e:
    return errorResult(fmt"Doctor command failed: {e.msg}")

export nipDoctorCommand, DoctorOptions, parseDoctorOptions, SystemHealthReport