## NimPak Error Handling
##
## Comprehensive error handling utilities for the NimPak system.
## Provides formatted error messages, recovery suggestions, and error chaining.
## Task 37: Implement comprehensive error handling.

import std/[strformat, strutils, times, tables, terminal]
import ../nip/types

# ############################################################################
# Error Formatting
# ############################################################################

proc errorCodeColor(code: ErrorCode): ForegroundColor =
  ## Get color for error code based on severity
  case code
  of PermissionDenied, ElevationRequired, ReadOnlyViolation,
     AculViolation, PolicyViolation, SignatureInvalid, TrustViolation:
    fgRed
  of PackageNotFound, DependencyConflict, ChecksumMismatch,
     InvalidMetadata, PackageCorrupted, VersionMismatch:
    fgYellow
  of NetworkError, DownloadFailed, RepositoryUnavailable, TimeoutError:
    fgMagenta
  of BuildFailed, CompilationError, MissingDependency:
    fgCyan
  else:
    fgWhite

proc formatError*(err: NimPakError): string =
  ## Format a NimPakError into a human-readable string with context and suggestions
  result = fmt"Error [{err.code}]: {err.msg}"
  if err.context.len > 0:
    result.add fmt"\n  Context: {err.context}"
  if err.suggestions.len > 0:
    result.add "\n  Suggestions:"
    for suggestion in err.suggestions:
      result.add fmt"\n    • {suggestion}"

proc formatErrorColored*(err: NimPakError, useColor: bool = true): string =
  ## Format error with ANSI colors for terminal output
  if not useColor:
    return formatError(err)
  
  let color = errorCodeColor(err.code)
  result = "\e[" & $ord(color) & "mError [" & $err.code & "]\e[0m: " & err.msg
  if err.context.len > 0:
    result.add fmt"\n  \e[90mContext:\e[0m {err.context}"
  if err.suggestions.len > 0:
    result.add "\n  \e[32mSuggestions:\e[0m"
    for suggestion in err.suggestions:
      result.add fmt"\n    \e[32m•\e[0m {suggestion}"

# ############################################################################
# Error Factory Functions
# ############################################################################

proc newNimPakError*(code: ErrorCode, message: string,
                     context: string = "",
                     suggestions: seq[string] = @[]): NimPakError =
  ## Create a new NimPakError with the specified details
  result = NimPakError(
    code: code,
    msg: message,
    context: context,
    suggestions: suggestions
  )

# --- Package Errors ---

proc packageNotFoundError*(packageName: string): NimPakError =
  ## Create a standardized package not found error
  newNimPakError(
    PackageNotFound,
    fmt"Package '{packageName}' not found",
    "The requested package could not be located in any configured repository",
    @[
      "Check the package name for typos",
      "Verify that the package repository is accessible",
      "Try updating the package index with 'nip update'"
    ]
  )

proc dependencyConflictError*(pkg1, pkg2, dep: string): NimPakError =
  newNimPakError(
    DependencyConflict,
    fmt"Dependency conflict between '{pkg1}' and '{pkg2}' for '{dep}'",
    "Multiple packages require incompatible versions of the same dependency",
    @[
      "Try updating both packages to their latest versions",
      "Check if one package can be downgraded",
      "Consider using 'nip resolve --strategy=best-effort'"
    ]
  )

proc checksumMismatchError*(filePath: string, expected: string, actual: string): NimPakError =
  newNimPakError(
    ChecksumMismatch,
    fmt"Checksum verification failed for '{filePath}'",
    fmt"Expected: {expected[0..min(15, expected.len-1)]}..., Got: {actual[0..min(15, actual.len-1)]}...",
    @[
      "The file may have been corrupted during download",
      "Try re-downloading the package",
      "Verify the integrity of your storage device"
    ]
  )

proc packageCorruptedError*(packagePath: string): NimPakError =
  newNimPakError(
    PackageCorrupted,
    fmt"Package at '{packagePath}' appears to be corrupted",
    "The package structure or manifest is invalid",
    @[
      "Re-download the package from the source",
      "Verify the package signature if available",
      "Check for disk errors with 'fsck' or equivalent"
    ]
  )

proc invalidMetadataError*(field: string, value: string, expected: string): NimPakError =
  newNimPakError(
    InvalidMetadata,
    fmt"Invalid metadata: field '{field}' has invalid value",
    fmt"Got: '{value}', Expected: {expected}",
    @[
      "Check the package manifest for syntax errors",
      "Verify the package was created with a compatible version"
    ]
  )

# --- Permission Errors ---

proc permissionDeniedError*(path: string, operation: string): NimPakError =
  newNimPakError(
    PermissionDenied,
    fmt"Permission denied: cannot {operation} '{path}'",
    "The current user lacks the required permissions",
    @[
      "Try running with elevated privileges (sudo)",
      "Check file/directory ownership and permissions",
      "Verify SELinux/AppArmor policies if applicable"
    ]
  )

proc elevationRequiredError*(operation: string): NimPakError =
  newNimPakError(
    ElevationRequired,
    fmt"Elevated privileges required for '{operation}'",
    "This operation modifies system directories",
    @[
      "Run with 'sudo nip ...'",
      "Configure polkit rules for non-interactive elevation",
      "Use --user flag to install to user directory instead"
    ]
  )

proc readOnlyViolationError*(path: string): NimPakError =
  newNimPakError(
    ReadOnlyViolation,
    fmt"Cannot write to read-only path: '{path}'",
    "The CAS or package directory is protected",
    @[
      "Use proper elevation to temporarily unlock",
      "Check if nip-protect is active",
      "Verify mount options for the filesystem"
    ]
  )

# --- Network Errors ---

proc networkError*(url: string, details: string): NimPakError =
  newNimPakError(
    NetworkError,
    fmt"Network error accessing '{url}'",
    details,
    @[
      "Check your internet connection",
      "Verify the repository URL is correct",
      "Try using a different mirror"
    ]
  )

proc downloadFailedError*(url: string, httpCode: int = 0): NimPakError =
  let context = if httpCode > 0: fmt"HTTP status: {httpCode}" else: "Connection failed"
  newNimPakError(
    DownloadFailed,
    fmt"Failed to download from '{url}'",
    context,
    @[
      "Retry the download",
      "Check if the file exists at the URL",
      "Try using 'nip fetch --retry=3'"
    ]
  )

proc repositoryUnavailableError*(repoUrl: string): NimPakError =
  newNimPakError(
    RepositoryUnavailable,
    fmt"Repository at '{repoUrl}' is unavailable",
    "Could not connect to the package repository",
    @[
      "Check repository status at the provider's site",
      "Try an alternative mirror",
      "Verify DNS resolution works"
    ]
  )

proc timeoutError*(operation: string, timeoutSeconds: int): NimPakError =
  newNimPakError(
    TimeoutError,
    fmt"Operation '{operation}' timed out after {timeoutSeconds}s",
    "The operation took too long to complete",
    @[
      "Retry with a longer timeout: --timeout=<seconds>",
      "Check network latency to the server",
      "Try during off-peak hours"
    ]
  )

# --- Build Errors ---

proc buildFailedError*(packageName: string, stage: string, output: string = ""): NimPakError =
  let context = if output.len > 0: output[0..min(200, output.len-1)] else: stage
  newNimPakError(
    BuildFailed,
    fmt"Build failed for package '{packageName}' at stage '{stage}'",
    context,
    @[
      "Check the build log for details",
      "Verify build dependencies are installed",
      "Try 'nip build --verbose' for more information"
    ]
  )

proc missingDependencyError*(packageName: string, depName: string): NimPakError =
  newNimPakError(
    MissingDependency,
    fmt"Missing dependency '{depName}' for package '{packageName}'",
    "A required dependency could not be resolved",
    @[
      fmt"Install the dependency first: nip install {depName}",
      "Check if the dependency exists in configured repositories",
      "Verify version constraints in the package manifest"
    ]
  )

# --- ACUL/Policy Errors ---

proc signatureInvalidError*(packageName: string, keyId: string = ""): NimPakError =
  let context = if keyId.len > 0: fmt"Key ID: {keyId}" else: "No valid signature found"
  newNimPakError(
    SignatureInvalid,
    fmt"Signature verification failed for '{packageName}'",
    context,
    @[
      "The package may have been tampered with",
      "Update trusted keys with 'nip trust update'",
      "Re-download from an official source"
    ]
  )

proc trustViolationError*(keyId: string, reason: string): NimPakError =
  newNimPakError(
    TrustViolation,
    fmt"Trust chain violation for key '{keyId}'",
    reason,
    @[
      "Verify the key in your trust store",
      "Check for key revocation",
      "Contact the package maintainer"
    ]
  )

proc aculViolationError*(packageName: string, violation: string): NimPakError =
  newNimPakError(
    AculViolation,
    fmt"ACUL policy violation for package '{packageName}'",
    violation,
    @[
      "Review the ACUL policy requirements",
      "Check if the package meets compliance standards",
      "Contact your administrator for policy exceptions"
    ]
  )

# --- Storage Errors ---

proc objectNotFoundError*(hash: string): NimPakError =
  newNimPakError(
    ObjectNotFound,
    fmt"Object not found in CAS: {hash[0..min(20, hash.len-1)]}...",
    "The requested object does not exist in any CAS location",
    @[
      "Run garbage collection verification: nip gc --verify",
      "Re-install the package that requires this object",
      "Check CAS integrity with 'nip cas verify'"
    ]
  )

proc storageFull*(path: string, required: int64, available: int64): NimPakError =
  newNimPakError(
    StorageFull,
    "Insufficient storage space",
    fmt"Required: {required div (1024*1024)}MB, Available: {available div (1024*1024)}MB at {path}",
    @[
      "Free up disk space",
      "Run garbage collection: nip gc",
      "Move CAS to a larger partition"
    ]
  )

# --- GC Errors ---

proc gcFailedError*(reason: string): NimPakError =
  newNimPakError(
    GarbageCollectionFailed,
    "Garbage collection failed",
    reason,
    @[
      "Check for locked files or active operations",
      "Retry with 'nip gc --force'",
      "Verify CAS integrity first: nip cas verify"
    ]
  )

proc referenceIntegrityError*(hash: string, expectedRefs: int, actualRefs: int): NimPakError =
  newNimPakError(
    ReferenceIntegrityError,
    fmt"Reference count mismatch for object {hash[0..min(12, hash.len-1)]}...",
    fmt"Expected: {expectedRefs}, Actual: {actualRefs}",
    @[
      "Run reference rebuild: nip gc --rebuild-refs",
      "This may indicate storage corruption"
    ]
  )

# --- Transaction Errors ---

proc transactionFailedError*(txId: string, operation: string): NimPakError =
  newNimPakError(
    TransactionFailed,
    fmt"Transaction '{txId}' failed during '{operation}'",
    "The atomic operation could not complete",
    @[
      "Automatic rollback was attempted",
      "Check system logs for details",
      "Retry the operation"
    ]
  )

proc rollbackFailedError*(txId: string, reason: string): NimPakError =
  newNimPakError(
    RollbackFailed,
    fmt"Rollback failed for transaction '{txId}'",
    reason,
    @[
      "Manual intervention may be required",
      "Check partial state in /var/lib/nip/transactions/",
      "Contact support if data loss occurred"
    ]
  )

# ############################################################################
# Error Recovery Strategies
# ############################################################################

type
  RecoveryStrategy* = enum
    Retry, Skip, Abort, Fallback, Manual

  RecoveryAction* = object
    strategy*: RecoveryStrategy
    message*: string
    action*: proc(): bool {.closure.}

proc suggestRecovery*(err: NimPakError): RecoveryStrategy =
  ## Suggest a recovery strategy based on error type
  case err.code
  of NetworkError, DownloadFailed, TimeoutError:
    Retry
  of PackageNotFound, ObjectNotFound:
    Fallback
  of PermissionDenied, ElevationRequired:
    Manual
  of ChecksumMismatch, SignatureInvalid:
    Abort
  else:
    Abort

proc isRecoverable*(err: NimPakError): bool =
  ## Check if error can potentially be recovered from
  err.code in {NetworkError, DownloadFailed, TimeoutError, 
               RepositoryUnavailable, PackageNotFound}

# ############################################################################
# Error Chaining
# ############################################################################

proc wrapError*(cause: NimPakError, code: ErrorCode, message: string): NimPakError =
  ## Wrap an existing error with additional context
  result = newNimPakError(
    code,
    message,
    fmt"Caused by: {cause.msg}",
    cause.suggestions
  )

proc chain*(errors: varargs[NimPakError]): string =
  ## Format a chain of related errors
  result = "Error chain:\n"
  for i, err in errors:
    result.add fmt"  {i+1}. [{err.code}] {err.msg}\n"