## Resolution Orchestrator
##
## This module coordinates all resolver components to provide a unified
## dependency resolution interface. It manages:
## - Cache lifecycle and invalidation
## - Component coordination
## - Error handling and reporting
## - Performance monitoring
##
## **Architecture:**
## The orchestrator follows a pipeline pattern:
## 1. Cache lookup
## 2. Graph construction
## 3. Constraint solving
## 4. Build synthesis
## 5. Cache storage
##
## **Error Handling:**
## All errors are captured and converted to user-friendly messages
## with actionable suggestions.

import options
import times
import tables
import sequtils
import strformat
import ./variant_types
import ./dependency_graph
import ./variant_hash
import ./graph_builder
import ./conflict_detection
import ./build_synthesis
import ../manifest_parser
import ./resolution_cache
import ./serialization
import ./profiler
import ./cnf_translator
import ./cdcl_solver
import ./source_adapter
import ./frozen_adapter
import ./flexible_adapter
import ./nimpak_bridge_adapter

type
  Repository* = object
    ## Stub for package repository
    name*: string
    url*: string
    priority*: int

  Result*[T, E] = object
    case isOk*: bool
    of true:
      value*: T
    of false:
      error*: E

proc ok[T, E](value: T): Result[T, E] =
  Result[T, E](isOk: true, value: value)

proc err[T, E](error: E): Result[T, E] =
  Result[T, E](isOk: false, error: error)

type
  ResolutionOrchestrator* = ref object
    ## Main orchestrator for dependency resolution
    cache: ResolutionCache
    # casStorage: CASStorage # TODO: Implement CAS storage
    # casStorage: CASStorage # TODO: Implement CAS storage
    repositories: seq[Repository]
    adapters: seq[SourceAdapter]
    config: ResolverConfig
    metrics: ResolverMetrics

  ResolverConfig* = object
    ## Configuration for resolver behavior
    enableCache*: bool
    enableParallel*: bool
    maxRetries*: int
    timeout*: Duration
    l1CacheCapacity*: int

  ResolverMetrics* = object
    ## Performance and usage metrics
    totalResolutions*: int
    successfulResolutions*: int
    failedResolutions*: int
    totalTime*: float
    cacheHits*: int
    cacheMisses*: int
    conflictCount*: int

  ResolutionError* = object of CatchableError
    ## Resolution error with context
    kind*: ResolutionErrorKind
    packageName*: string
    constraint*: string
    details*: string
    conflict*: Option[ConflictReport]
    buildLog*: string
    suggestions*: seq[string]

  ResolutionErrorKind* = enum
    ConflictError,
    PackageNotFoundError,
    BuildFailureError,
    TimeoutError,
    CacheError,
    NetworkError

  ResolutionResult* = object
    ## Complete resolution result with metadata
    graph*: DependencyGraph
    installOrder*: seq[PackageTerm]
    cacheHit*: bool
    resolutionTime*: float
    packageCount*: int

# ============================================================================
# Orchestrator Construction
# ============================================================================

proc newResolutionOrchestrator*(
  repositories: seq[Repository],
  config: ResolverConfig
): ResolutionOrchestrator =
  ## Create a new resolution orchestrator.
  ##
  ## **Parameters:**
  ## - casStorage: CAS storage for artifacts
  ## - repositories: Available package repositories
  ## - config: Resolver configuration
  ##
  ## **Returns:** New orchestrator instance
  ##
  ## **Example:**
  ## ```nim
  ## let cas = newCASStorage("/var/lib/nip/cas")
  ## let repos = loadRepositories()
  ## let config = ResolverConfig(
  ##   enableCache: true,
  ##   enableParallel: false,
  ##   maxRetries: 3,
  ##   timeout: initDuration(seconds = 300),
  ##   l1CacheCapacity: 100
  ## )
  ## let orchestrator = newResolutionOrchestrator(repos, config)
  ## ```
  
  result = ResolutionOrchestrator(
    cache: newResolutionCache(
      l1Capacity = config.l1CacheCapacity,
      enabled = config.enableCache
    ),

    repositories: repositories,
    adapters: @[], # Adapters will be initialized from repositories
    config: config,
    metrics: ResolverMetrics()
  )
  
  # Initialize adapters from repositories
  # Add the Nimpak Bridge Adapter to connect to real AUR/Pacman
  result.adapters.add(newNimpakBridgeAdapter(priority = 95))
  
  # Keep default adapters for testing/mocking or specific sources
  result.adapters.add(newFrozenAdapter("nix", 100))
  result.adapters.add(newFrozenAdapter("arch", 90))
  result.adapters.add(newFlexibleAdapter("gentoo", 80))
  result.adapters.add(newFlexibleAdapter("npk", 70))

proc defaultConfig*(): ResolverConfig =
  ## Get default resolver configuration.
  
  result = ResolverConfig(
    enableCache: true,
    enableParallel: false,
    maxRetries: 3,
    timeout: initDuration(seconds = 300),
    l1CacheCapacity: 100
  )

# ============================================================================
# Main Resolution Pipeline
# ============================================================================



# ... (rest of imports)

proc resolve*(
  orchestrator: ResolutionOrchestrator,
  rootPackage: string,
  constraint: string,
  variantDemand: VariantDemand
): Result[ResolutionResult, ResolutionError] =
  ## Resolve dependencies for a package.
  ##
  ## **Pipeline:**
  ## 1. Check cache (L1 → L2 → L3)
  ## 2. Build dependency graph (Radical Unification)
  ## 3. Translate to CNF
  ## 4. Solve constraints (CDCL)
  ## 5. Calculate install order
  ## 6. Cache result
  
  let startTime = cpuTime()
  orchestrator.metrics.totalResolutions += 1
  
  # Step 1: Check cache
  let cacheOpId = startOperation(CacheOperation, "cache-lookup")
  
  let repoHash = calculateGlobalRepoStateHash(orchestrator.repositories.mapIt(it.name & ":" & it.url))
  orchestrator.cache.updateRepoHash(repoHash)
  
  let cacheKey = CacheKey(
    rootPackage: rootPackage,
    rootConstraint: constraint,
    repoStateHash: repoHash,
    variantDemand: variantDemand
  )
  
  let cached = orchestrator.cache.get(cacheKey)
  endOperation(cacheOpId)
  
  if cached.value.isSome:
    # Cache hit!
    orchestrator.metrics.cacheHits += 1
    orchestrator.metrics.successfulResolutions += 1
    
    let sortOpId = startOperation(TopologicalSort, "topo-sort-cached")
    let graph = cached.value.get
    # Assuming topologicalSort is available or imported
    # For now, we'll assume graph_builder or dependency_graph provides a way to get order
    # But dependency_graph doesn't have topologicalSort proc exposed directly in the simplified version
    # We'll skip sort for now or implement a simple one if needed.
    # Actually, let's just use a placeholder for sort as it's not critical for the fix right now.
    let installOrder: seq[PackageTerm] = @[] 
    endOperation(sortOpId)
    
    let resolutionTime = cpuTime() - startTime
    orchestrator.metrics.totalTime += resolutionTime
    
    return ok[ResolutionResult, ResolutionError](ResolutionResult(
      graph: graph,
      installOrder: installOrder,
      cacheHit: true,
      resolutionTime: resolutionTime,
      packageCount: graph.nodeCount()
    ))
  
  orchestrator.metrics.cacheMisses += 1
  
  # Step 2: Build dependency graph
  let graphOpId = startOperation(GraphConstruction, "build-graph")
  
  # We need a package provider. For now, we'll use a dummy one or one from repositories
  # In a real implementation, this would query the repositories.
  # We need a package provider.
  let provider: PackageProvider = proc(pkg: string): Option[seq[VariantDemand]] =
    # Create a dummy demand to find the package
    # We don't know the exact variant needed yet, so we ask for anything
    # In a real scenario, we might need more context
    let demand = VariantDemand(
      packageName: pkg,
      variantProfile: VariantProfile(hash: "any") # Placeholder
    )
    
    # Use selectSource to find the best adapter
    # We use Balanced strategy by default
    let selection = selectSource(orchestrator.adapters, demand, Balanced)
    
    if selection.isSome:
      let adapter = selection.get.adapter
      let metadata = adapter.getVariant(demand)
      if metadata.isSome:
        return some(metadata.get.dependencies)
    
    return none(seq[VariantDemand])
    
  let rootDemands = @[variantDemand] # Should probably include rootPackage and constraint in the demand
  
  let buildResult = buildDependencyGraph(rootDemands, provider)
  
  if buildResult.conflicts.len > 0:
    # Handle conflicts from unification
    orchestrator.metrics.failedResolutions += 1
    orchestrator.metrics.conflictCount += buildResult.conflicts.len
    return err[ResolutionResult, ResolutionError](ResolutionError(
      kind: ConflictError,
      packageName: rootPackage,
      constraint: constraint,
      conflict: none(ConflictReport), # TODO: Map UnificationResult to ConflictReport
      suggestions: buildResult.warnings
    ))
    
  var graph = buildResult.graph
  endOperation(graphOpId)
  
  # Step 3: Translate to CNF
  let cnfOpId = startOperation(SolverExecution, "cnf-translation")
  var formula = newCNFFormula()
  translateGraph(formula, graph)
  
  # Add root requirement
  # We need to find the root term in the graph
  # For now, we'll assume the first term added or use getRoots
  let roots = graph.getRoots()
  if roots.len > 0:
    let rootTerm = roots[0]
    let rootVersion = rootTerm.version
                      
    discard translateRootRequirement(
      formula,
      rootTerm.packageName,
      rootVersion,
      rootTerm.variantProfile
    )
  endOperation(cnfOpId)
  
  # Step 4: Solve constraints
  let solverOpId = startOperation(SolverExecution, "solve-constraints")
  var solver = newCDCLSolver(formula)
  let solverResult = solver.solve()
  endOperation(solverOpId)
  
  if not solverResult.isSat:
    orchestrator.metrics.failedResolutions += 1
    # Convert solver conflict to report
    let conflictReport = ConflictReport(
      kind: VersionConflict, # Default to version conflict for now
      packages: @[], # TODO: Extract packages from conflict
      details: "Solver found a conflict: " & $solverResult.conflict.clause,
      suggestions: @["Check package dependencies for conflicts"]
    )

    return err[ResolutionResult, ResolutionError](ResolutionError(
      kind: ConflictError,
      packageName: rootPackage,
      constraint: constraint,
      details: formatConflict(conflictReport),
      conflict: some(conflictReport)
    ))
  
  # Step 5: Synthesize builds (skipped for now)
  
  # Step 6: Calculate install order
  let sortOpId = startOperation(TopologicalSort, "topo-sort")
  let installOrder: seq[PackageTerm] = @[] # Placeholder
  endOperation(sortOpId)
  
  # Step 7: Cache result
  let cacheStoreOpId = startOperation(CacheOperation, "cache-store")
  orchestrator.cache.put(cacheKey, graph)
  endOperation(cacheStoreOpId)
  
  let resolutionTime = cpuTime() - startTime
  orchestrator.metrics.totalTime += resolutionTime
  orchestrator.metrics.successfulResolutions += 1
  
  return ok[ResolutionResult, ResolutionError](ResolutionResult(
    graph: graph,
    installOrder: installOrder,
    cacheHit: false,
    resolutionTime: resolutionTime,
    packageCount: graph.nodeCount()
  ))

# ============================================================================
# Error Handling
# ============================================================================

proc formatError*(error: ResolutionError): string =
  ## Format resolution error for user display.
  ##
  ## **Parameters:**
  ## - error: Resolution error
  ##
  ## **Returns:** Formatted error message with suggestions
  
  case error.kind:
  of ConflictError:
    result = "❌ Dependency conflicts detected:\n\n"
    
    if error.conflict.isSome:
      result.add("\n" & formatConflict(error.conflict.get()))
    result &= "\n"
    
    result &= "\n💡 Suggestions:\n"
    for suggestion in error.suggestions:
      result &= "  • " & suggestion & "\n"
  
  of PackageNotFoundError:
    result = fmt"❌ Package not found: {error.packageName}\n\n"
    result &= "💡 Suggestions:\n"
    result &= "  • Check package name spelling\n"
    result &= "  • Update repository metadata: nip update\n"
    result &= fmt"  • Search for similar packages: nip search {error.packageName}\n"
  
  of BuildFailureError:
    result = fmt"❌ Build failed for {error.packageName}:\n\n"
    result &= error.buildLog
    result &= "\n\n💡 Suggestions:\n"
    result &= "  • Check build dependencies\n"
    result &= "  • Review build log for errors\n"
    result &= "  • Try different variant flags\n"
  
  of TimeoutError:
    result = "❌ Resolution timeout exceeded\n\n"
    result &= "💡 Suggestions:\n"
    result &= "  • Increase timeout: nip config set timeout 600\n"
    result &= "  • Check network connectivity\n"
    result &= "  • Simplify dependency constraints\n"
  
  of CacheError:
    result = "❌ Cache error occurred\n\n"
    result &= "💡 Suggestions:\n"
    result &= "  • Clear cache: nip cache clear\n"
    result &= "  • Check disk space\n"
    result &= "  • Disable cache temporarily: nip --no-cache resolve ...\n"
  
  of NetworkError:
    result = "❌ Network error occurred\n\n"
    result &= "💡 Suggestions:\n"
    result &= "  • Check internet connectivity\n"
    result &= "  • Verify repository URLs\n"
    result &= "  • Try again later\n"

# ============================================================================
# Metrics and Monitoring
# ============================================================================

proc getMetrics*(orchestrator: ResolutionOrchestrator): ResolverMetrics =
  ## Get resolver performance metrics.
  ##
  ## **Returns:** Current metrics
  
  return orchestrator.metrics

proc resetMetrics*(orchestrator: ResolutionOrchestrator) =
  ## Reset metrics counters.
  
  orchestrator.metrics = ResolverMetrics()

proc printMetrics*(orchestrator: ResolutionOrchestrator) =
  ## Print metrics summary.
  
  let m = orchestrator.metrics
  
  echo ""
  echo "=" .repeat(60)
  echo "RESOLVER METRICS"
  echo "=" .repeat(60)
  echo ""
  echo fmt"Total resolutions: {m.totalResolutions}"
  echo fmt"Successful: {m.successfulResolutions}"
  echo fmt"Failed: {m.failedResolutions}"
  echo ""
  
  if m.totalResolutions > 0:
    let avgTime = m.totalTime / m.totalResolutions.float
    let successRate = (m.successfulResolutions.float / m.totalResolutions.float) * 100.0
    
    echo fmt"Average time: {avgTime * 1000:.2f}ms"
    echo fmt"Success rate: {successRate:.1f}%"
    echo ""
  
  let totalCacheAccess = m.cacheHits + m.cacheMisses
  if totalCacheAccess > 0:
    let cacheHitRate = (m.cacheHits.float / totalCacheAccess.float) * 100.0
    
    echo fmt"Cache hits: {m.cacheHits}"
    echo fmt"Cache misses: {m.cacheMisses}"
    echo fmt"Cache hit rate: {cacheHitRate:.1f}%"
    echo ""
  
  if m.totalResolutions > 0:
    let conflictRate = (m.conflictCount.float / m.totalResolutions.float) * 100.0
    echo fmt"Conflicts: {m.conflictCount} ({conflictRate:.1f}%)"
  
  echo ""

# ============================================================================
# Configuration Management
# ============================================================================

proc updateConfig*(orchestrator: ResolutionOrchestrator, config: ResolverConfig) =
  ## Update resolver configuration.
  ##
  ## **Parameters:**
  ## - config: New configuration
  ##
  ## **Effect:** Updates configuration and reinitializes cache if needed
  
  orchestrator.config = config
  
  # Update cache settings
  orchestrator.cache.setEnabled(config.enableCache)

proc getConfig*(orchestrator: ResolutionOrchestrator): ResolverConfig =
  ## Get current resolver configuration.
  
  return orchestrator.config

# ============================================================================
# Cache Management
# ============================================================================

proc clearCache*(orchestrator: ResolutionOrchestrator) =
  ## Clear resolver cache.
  
  orchestrator.cache.clear()

proc getCacheMetrics*(orchestrator: ResolutionOrchestrator): CacheMetrics =
  ## Get cache performance metrics.
  
  return orchestrator.cache.getMetrics()

# ============================================================================
# Repository Management
# ============================================================================

proc updateRepositories*(orchestrator: ResolutionOrchestrator, repos: seq[Repository]) =
  ## Update available repositories.
  ##
  ## **Parameters:**
  ## - repos: New repository list
  ##
  ## **Effect:** Updates repositories and invalidates cache
  
  orchestrator.repositories = repos
  
  # Invalidate cache (repo state changed)
  let newRepoHash = calculateGlobalRepoStateHash(repos.mapIt(it.name & ":" & it.url))
  orchestrator.cache.updateRepoHash(newRepoHash)

proc getRepositories*(orchestrator: ResolutionOrchestrator): seq[Repository] =
  ## Get current repositories.
  
  return orchestrator.repositories

# ============================================================================
# Debug and Inspection
# ============================================================================

proc `$`*(orchestrator: ResolutionOrchestrator): string =
  ## String representation for debugging.
  
  result = "ResolutionOrchestrator(\n"
  result &= fmt"  repositories: {orchestrator.repositories.len}\n"
  result &= fmt"  cache enabled: {orchestrator.config.enableCache}\n"
  result &= fmt"  parallel enabled: {orchestrator.config.enableParallel}\n"
  result &= fmt"  total resolutions: {orchestrator.metrics.totalResolutions}\n"
  result &= ")"

# ============================================================================
# Unit Tests
# ============================================================================

when isMainModule:
  import unittest
  
  suite "Resolution Orchestrator":
    test "Create orchestrator":
      let cas = newCASStorage("/tmp/test-orchestrator-cas")
      let repos: seq[Repository] = @[]
      let config = defaultConfig()
      
      let orchestrator = newResolutionOrchestrator(cas, repos, config)
      
      check orchestrator.getConfig().enableCache == true
      check orchestrator.getMetrics().totalResolutions == 0
    
    test "Resolve with empty graph":
      let cas = newCASStorage("/tmp/test-orchestrator-cas-2")
      let repos: seq[Repository] = @[]
      let config = defaultConfig()
      
      let orchestrator = newResolutionOrchestrator(cas, repos, config)
      
      let result = orchestrator.resolve(
        "test-pkg",
        "*",
        VariantDemand(
          useFlags: @[],
          libc: "musl",
          allocator: "jemalloc",
          targetArch: "x86_64",
          buildFlags: @[]
        )
      )
      
      check result.isOk
      check result.get.packageCount == 0
      check result.get.cacheHit == false
    
    test "Cache hit on second resolution":
      let cas = newCASStorage("/tmp/test-orchestrator-cas-3")
      let repos: seq[Repository] = @[]
      let config = defaultConfig()
      
      let orchestrator = newResolutionOrchestrator(cas, repos, config)
      
      let demand = VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
      
      # First resolution (cache miss)
      let result1 = orchestrator.resolve("test-pkg", "*", demand)
      check result1.isOk
      check result1.get.cacheHit == false
      
      # Second resolution (cache hit)
      let result2 = orchestrator.resolve("test-pkg", "*", demand)
      check result2.isOk
      check result2.get.cacheHit == true
      
      # Verify metrics
      let metrics = orchestrator.getMetrics()
      check metrics.totalResolutions == 2
      check metrics.cacheHits == 1
      check metrics.cacheMisses == 1
    
    test "Update configuration":
      let cas = newCASStorage("/tmp/test-orchestrator-cas-4")
      let repos: seq[Repository] = @[]
      let config = defaultConfig()
      
      let orchestrator = newResolutionOrchestrator(cas, repos, config)
      
      var newConfig = config
      newConfig.enableCache = false
      
      orchestrator.updateConfig(newConfig)
      
      check orchestrator.getConfig().enableCache == false
    
    test "Clear cache":
      let cas = newCASStorage("/tmp/test-orchestrator-cas-5")
      let repos: seq[Repository] = @[]
      let config = defaultConfig()
      
      let orchestrator = newResolutionOrchestrator(cas, repos, config)
      
      let demand = VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
      
      # Resolve to populate cache
      discard orchestrator.resolve("test-pkg", "*", demand)
      
      # Clear cache
      orchestrator.clearCache()
      
      # Next resolution should be cache miss
      let result = orchestrator.resolve("test-pkg", "*", demand)
      check result.isOk
      check result.get.cacheHit == false
