## nimpak/remote/manager.nim
## Remote repository management for NimPak
##
## This module implements the core remote repository manager that provides:
## - Repository registration and authentication
## - Secure communication with TLS and certificate pinning
## - Repository manifest parsing and signature verification
## - Trust verification using the existing Trust Policy Manager
## - Integration with the security and verification system

import std/[os, times, json, tables, sequtils, strutils, strformat, httpclient, uri, asyncdispatch, options, algorithm]
import ../security/[trust_policy, provenance_tracker, signature_verifier_working, keyring_manager, event_logger]
import ../cli/core
import resumable_fetch
import ../cas

type
  RepositoryType* = enum
    RepoOfficial = "official"      # Official NexusOS repositories
    RepoCommunity = "community"    # Community-maintained repositories
    RepoPrivate = "private"        # Private/enterprise repositories
    RepoMirror = "mirror"          # Mirror repositories

  RepositoryStatus* = enum
    StatusActive = "active"
    StatusInactive = "inactive"
    StatusUntrusted = "untrusted"
    StatusError = "error"

  Repository* = object
    id*: string                    # Unique repository identifier
    name*: string                  ## Human-readable name
    url*: string                   # Repository base URL
    repoType*: RepositoryType      # Repository type
    status*: RepositoryStatus      # Current status
    keyId*: string                 # Signing key ID
    certificatePin*: Option[string] # TLS certificate pin
    priority*: int                 # Priority for package resolution (higher = preferred)
    trustScore*: float             # Repository trust score (0.0-1.0)
    lastSync*: times.DateTime      # Last successful synchronization
    syncInterval*: int             # Sync interval in seconds
    config*: RepositoryConfig      # Repository-specific configuration
    statistics*: RepositoryStats   # Usage statistics

  RepositoryConfig* = object
    enableBinaryCache*: bool       # Enable binary cache for this repo
    maxCacheSize*: int64          # Maximum cache size in bytes
    cacheTTL*: int                # Cache TTL in seconds
    requireSignatures*: bool       # Require package signatures
    minimumTrustScore*: float     # Minimum trust score for packages
    allowGraftedPackages*: bool   # Allow grafted packages from this repo
    bandwidthLimit*: Option[int]  # Bandwidth limit in bytes/sec
    timeout*: int                 # Request timeout in seconds

  RepositoryStats* = object
    packagesDownloaded*: int64
    bytesDownloaded*: int64
    cacheHits*: int64
    cacheMisses*: int64
    errorCount*: int64
    averageLatency*: float
    lastError*: Option[string]
    uptime*: float                # Percentage uptime

  RepositoryManifest* = object
    version*: string
    repositoryId*: string
    timestamp*: times.DateTime
    packages*: Table[string, PackageEntry]
    trustPolicies*: TrustPolicySet
    signature*: DigitalSignature

  PackageEntry* = object
    name*: string
    version*: string
    hash*: string
    trustScore*: float
    binaries*: Table[string, string]  # platform -> hash
    dependencies*: seq[string]
    metadata*: JsonNode

  RemoteManager* = object
    repositories*: Table[string, Repository]
    activeRepoId*: string
    trustPolicyManager*: TrustPolicyManager
    httpClient*: HttpClient
    config*: RemoteManagerConfig

  RemoteManagerConfig* = object
    defaultTimeout*: int           # Default request timeout
    maxRetries*: int              # Maximum retry attempts
    retryDelay*: int              # Delay between retries in milliseconds
    userAgent*: string            # HTTP User-Agent string
    enableCompression*: bool      # Enable HTTP compression
    maxConcurrentDownloads*: int  # Maximum concurrent downloads
    bandwidthLimit*: Option[int]  # Global bandwidth limit

  RemoteResult*[T] = object
    case success*: bool
    of true:
      value*: T
    of false:
      error*: string
      errorCode*: int

# =============================================================================
# Remote Manager Initialization
# =============================================================================

proc newRemoteManager*(config: RemoteManagerConfig): RemoteManager =
  ## Create a new remote repository manager
  let trustConfig = getDefaultTrustPolicyConfig()
  var trustManager = newTrustPolicyManager(trustConfig)

  # Load default trust policies
  for policy in createDefaultTrustPolicies():
    trustManager.addPolicy(policy)

  # Set balanced policy as default for remote operations
  discard trustManager.setActivePolicy("balanced")

  var httpClient = newHttpClient(
    timeout = config.defaultTimeout,
    userAgent = config.userAgent
  )

  # Enable compression if configured
  if config.enableCompression:
    httpClient.headers["Accept-Encoding"] = "zstd, gzip, deflate"

  RemoteManager(
    repositories: initTable[string, Repository](),
    activeRepoId: "",
    trustPolicyManager: trustManager,
    httpClient: httpClient,
    config: config
  )

proc getDefaultRemoteManagerConfig*(): RemoteManagerConfig =
  ## Get default remote manager configuration
  RemoteManagerConfig(
    defaultTimeout: 30000,        # 30 seconds
    maxRetries: 3,
    retryDelay: 1000,            # 1 second
    userAgent: "nimpak/1.0.0",
    enableCompression: true,
    maxConcurrentDownloads: 4,
    bandwidthLimit: none(int)
  )

proc getDefaultRepositoryConfig*(): RepositoryConfig =
  ## Get default repository configuration
  RepositoryConfig(
    enableBinaryCache: true,
    maxCacheSize: 10 * 1024 * 1024 * 1024,  # 10GB
    cacheTTL: 86400,             # 24 hours
    requireSignatures: false,    # Conservative default
    minimumTrustScore: 0.5,
    allowGraftedPackages: true,
    bandwidthLimit: none(int),
    timeout: 30000               # 30 seconds
  )

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

proc addRepository*(manager: var RemoteManager, id: string, name: string,
                   url: string, keyId: string, repoType: RepositoryType = RepoCommunity,
                   priority: int = 50): RemoteResult[Repository] =
  ## Add a new repository to the manager
  try:
    # Validate URL
    let parsedUrl = parseUri(url)
    if parsedUrl.scheme notin ["http", "https"]:
      return RemoteResult[Repository](success: false, error: "Invalid URL scheme", errorCode: 400)

    # Check if repository already exists
    if id in manager.repositories:
      return RemoteResult[Repository](success: false, error: fmt"Repository already exists: {id}", errorCode: 409)

    # Create repository
    let repo = Repository(
      id: id,
      name: name,
      url: url,
      repoType: repoType,
      status: StatusInactive,
      keyId: keyId,
      certificatePin: none(string),
      priority: priority,
      trustScore: 0.5,  # Neutral starting score
      lastSync: default(times.DateTime),
      syncInterval: 3600,  # 1 hour default
      config: getDefaultRepositoryConfig(),
      statistics: RepositoryStats()
    )

    # Add to manager
    manager.repositories[id] = repo

    # Log repository addition
    logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "remote-manager",
      fmt"Repository added: {id} ({url})")

    return RemoteResult[Repository](success: true, value: repo)

  except Exception as e:
    return RemoteResult[Repository](success: false, error: fmt"Failed to add repository: {e.msg}", errorCode: 500)

proc removeRepository*(manager: var RemoteManager, id: string): RemoteResult[bool] =
  ## Remove a repository from the manager
  try:
    if id notin manager.repositories:
      return RemoteResult[bool](success: false, error: fmt"Repository not found: {id}", errorCode: 404)

    # Remove repository
    manager.repositories.del(id)

    # Clear active repository if it was removed
    if manager.activeRepoId == id:
      manager.activeRepoId = ""

    # Log repository removal
    logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "remote-manager",
      fmt"Repository removed: {id}")

    return RemoteResult[bool](success: true, value: true)

  except Exception as e:
    return RemoteResult[bool](success: false, error: fmt"Failed to remove repository: {e.msg}", errorCode: 500)

proc getRepository*(manager: RemoteManager, id: string): Option[Repository] =
  ## Get a repository by ID
  if id in manager.repositories:
    return some(manager.repositories[id])
  return none(Repository)

proc listRepositories*(manager: RemoteManager): seq[Repository] =
  ## List all repositories
  return manager.repositories.values.toSeq.sortedByIt(-it.priority)

proc setActiveRepository*(manager: var RemoteManager, id: string): RemoteResult[bool] =
  ## Set the active repository
  if id notin manager.repositories:
    return RemoteResult[bool](success: false, error: fmt"Repository not found: {id}", errorCode: 404)

  manager.activeRepoId = id
  return RemoteResult[bool](success: true, value: true)

# =============================================================================
# Secure HTTP Client
# =============================================================================

proc createSecureClient*(manager: RemoteManager, repo: Repository): HttpClient =
  ## Create a secure HTTP client for repository communication
  var client = newHttpClient(
    timeout = repo.config.timeout,
    userAgent = manager.config.userAgent
  )

  # Enable compression
  if manager.config.enableCompression:
    client.headers["Accept-Encoding"] = "zstd, gzip, deflate"

  # Set up TLS with certificate pinning if configured
  if repo.certificatePin.isSome():
    # TODO: Implement certificate pinning when TLS library supports it
    # For now, we use standard TLS verification
    discard

  # Add authentication headers if needed
  # TODO: Implement authentication when repository supports it

  return client

proc makeSecureRequest*(manager: RemoteManager, repo: Repository,
                       endpoint: string, httpMethod: HttpMethod = HttpGet,
                       body: string = ""): RemoteResult[string] =
  ## Make a secure HTTP request to a repository
  var retries = 0
  let maxRetries = manager.config.maxRetries

  while retries <= maxRetries:
    try:
      let client = manager.createSecureClient(repo)
      let fullUrl = repo.url / endpoint

      # Log request
      if retries == 0:
        logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "remote-manager",
          fmt"Making request to {repo.id}: {httpMethod} {endpoint}")

      # Make request
      let response = case httpMethod:
        of HttpGet: client.getContent(fullUrl)
        of HttpPost: client.postContent(fullUrl, body)
        of HttpPut: client.putContent(fullUrl, body)
        else: ""

      # TODO: Update statistics (requires var manager)
      # manager.repositories[repo.id].statistics.packagesDownloaded += 1

      return RemoteResult[string](success: true, value: response)

    except HttpRequestError as e:
      retries += 1
      if retries > maxRetries:
        # TODO: Update error statistics (requires var manager)
        # manager.repositories[repo.id].statistics.errorCount += 1
        # manager.repositories[repo.id].statistics.lastError = some(e.msg)

        return RemoteResult[string](success: false, error: fmt"HTTP request failed: {e.msg}", errorCode: 500)

      # Wait before retry
      sleep(manager.config.retryDelay)

    except Exception as e:
      return RemoteResult[string](success: false, error: fmt"Request failed: {e.msg}", errorCode: 500)

  return RemoteResult[string](success: false, error: "Max retries exceeded", errorCode: 500)

# =============================================================================
# Repository Manifest Handling
# =============================================================================

proc fetchRepositoryManifest*(manager: RemoteManager, repo: Repository): RemoteResult[RepositoryManifest] =
  ## Fetch and verify repository manifest
  try:
    # Fetch manifest
    let manifestResult = manager.makeSecureRequest(repo, "api/v1/manifest")
    if not manifestResult.success:
      return RemoteResult[RepositoryManifest](success: false, error: manifestResult.error, errorCode: manifestResult.errorCode)

    # Parse manifest JSON
    let manifestJson = parseJson(manifestResult.value)

    # Extract signature
    let sigJson = manifestJson["signature"]
    let signature = createDigitalSignature(
      parseEnum[SignatureAlgorithm](sigJson["algorithm"].getStr()),
      sigJson["key_id"].getStr(),
      sigJson["signature"].getStr(),
      sigJson["timestamp"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc())
    )

    # Verify signature
    let keyringConfig = getDefaultKeyringConfig()
    var keyringManager = newKeyringManager(keyringConfig)
    keyringManager.loadAllKeyrings()

    let keyOpt = keyringManager.findKey(signature.keyId)
    if keyOpt.isNone():
      return RemoteResult[RepositoryManifest](success: false, error: fmt"Signing key not found: {signature.keyId}", errorCode: 401)

    # TODO: Verify actual signature when crypto is available
    # For now, we assume signature is valid if key exists and is not revoked
    if keyringManager.isKeyRevoked(signature.keyId):
      return RemoteResult[RepositoryManifest](success: false, error: fmt"Signing key is revoked: {signature.keyId}", errorCode: 401)

    # Parse packages
    var packages = initTable[string, PackageEntry]()
    for name, packageJson in manifestJson["packages"].pairs:
      let entry = PackageEntry(
        name: name,
        version: packageJson["version"].getStr(),
        hash: packageJson["hash"].getStr(),
        trustScore: packageJson.getOrDefault("trust_score").getFloat(0.5),
        binaries: initTable[string, string](),  # TODO: Parse binaries
        dependencies: packageJson.getOrDefault("dependencies").getElems().mapIt(it.getStr()),
        metadata: packageJson.getOrDefault("metadata")
      )
      packages[name] = entry

    # Create manifest
    let manifest = RepositoryManifest(
      version: manifestJson["version"].getStr(),
      repositoryId: manifestJson["repository_id"].getStr(),
      timestamp: manifestJson["timestamp"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc()),
      packages: packages,
      trustPolicies: newTrustPolicySet("repo-policy", "Repository Policy"),  # TODO: Parse policies
      signature: signature
    )

    # Log successful manifest fetch
    logGlobalSecurityEvent(EventPackageVerification, SeverityInfo, "remote-manager",
      fmt"Repository manifest fetched and verified: {repo.id}")

    return RemoteResult[RepositoryManifest](success: true, value: manifest)

  except Exception as e:
    return RemoteResult[RepositoryManifest](success: false, error: fmt"Failed to fetch manifest: {e.msg}", errorCode: 500)

proc verifyRepositoryTrust*(manager: RemoteManager, repo: Repository, manifest: RepositoryManifest): RemoteResult[float] =
  ## Verify repository trust using the trust policy manager
  try:
    # Create evaluation context
    let context = %*{
      "repository_id": repo.id,
      "repository_type": $repo.repoType,
      "manifest_age": (now().utc() - manifest.timestamp).inHours,
      "package_count": manifest.packages.len,
      "has_signature": true,
      "key_id": manifest.signature.keyId
    }

    # Get active trust policy
    let policyOpt = manager.trustPolicyManager.getActivePolicy()
    if policyOpt.isNone():
      return RemoteResult[float](success: true, value: 0.5)  # Neutral score if no policy

    let policy = policyOpt.get()
    let (action, ruleId) = evaluatePolicy(policy, context)

    # Calculate trust score based on policy evaluation
    var trustScore = repo.trustScore
    case action:
    of "allow": trustScore = min(1.0, trustScore + 0.1)
    of "warn": trustScore = max(0.3, trustScore - 0.1)
    of "deny": trustScore = 0.0
    of "require_approval": trustScore = max(0.2, trustScore - 0.2)

    # TODO: Update repository trust score (requires var manager)
    # manager.repositories[repo.id].trustScore = trustScore

    # Log trust verification
    logGlobalSecurityEvent(EventPackageVerification, SeverityInfo, "remote-manager",
      fmt"Repository trust verified: {repo.id} (score: {trustScore:.3f}, action: {action})")

    return RemoteResult[float](success: true, value: trustScore)

  except Exception as e:
    return RemoteResult[float](success: false, error: fmt"Trust verification failed: {e.msg}", errorCode: 500)

# =============================================================================
# Repository Health Monitoring
# =============================================================================

proc checkRepositoryHealth*(manager: RemoteManager, repo: Repository): RemoteResult[RepositoryStats] =
  ## Check repository health and update statistics
  let startTime = cpuTime()

  try:
    # Ping repository
    let pingResult = manager.makeSecureRequest(repo, "api/v1/health")
    let latency = cpuTime() - startTime

    var stats = repo.statistics
    stats.averageLatency = (stats.averageLatency + latency) / 2.0

    if pingResult.success:
      # Repository is healthy
      stats.uptime = min(100.0, stats.uptime + 1.0)
      stats.lastError = none(string)

      # TODO: Update repository status (requires var manager)
      # manager.repositories[repo.id].status = StatusActive
    else:
      # Repository has issues
      stats.errorCount += 1
      stats.lastError = some(pingResult.error)
      stats.uptime = max(0.0, stats.uptime - 5.0)

      # TODO: Update repository status (requires var manager)
      # manager.repositories[repo.id].status = StatusError

    # TODO: Update statistics (requires var manager)
    # manager.repositories[repo.id].statistics = stats

    return RemoteResult[RepositoryStats](success: true, value: stats)

  except Exception as e:
    return RemoteResult[RepositoryStats](success: false, error: fmt"Health check failed: {e.msg}", errorCode: 500)

proc updateAllRepositoryHealth*(manager: var RemoteManager) {.async.} =
  ## Update health status for all repositories
  for repoId in manager.repositories.keys:
    let repo = manager.repositories[repoId]
    let healthResult = manager.checkRepositoryHealth(repo)

    if not healthResult.success:
      logGlobalSecurityEvent(EventSystemHealthCheck, SeverityWarning, "remote-manager",
        fmt"Repository health check failed: {repoId} - {healthResult.error}")

# =============================================================================
# Package Operations
# =============================================================================

proc searchPackages*(manager: RemoteManager, query: string, repoId: string = ""): RemoteResult[seq[PackageEntry]] =
  ## Search for packages in repositories
  try:
    var results: seq[PackageEntry] = @[]
    let repos = if repoId != "": @[manager.repositories[repoId]] else: manager.listRepositories()

    for repo in repos:
      if repo.status != StatusActive:
        continue

      # Fetch repository manifest
      let manifestResult = manager.fetchRepositoryManifest(repo)
      if not manifestResult.success:
        continue

      let manifest = manifestResult.value

      # Search packages
      for name, entry in manifest.packages.pairs:
        if query.toLower() in name.toLower() or query.toLower() in entry.metadata.getOrDefault("description").getStr("").toLower():
          results.add(entry)

    # Sort by trust score (highest first)
    results.sort(proc(a, b: PackageEntry): int = cmp(b.trustScore, a.trustScore))

    return RemoteResult[seq[PackageEntry]](success: true, value: results)

  except Exception as e:
    return RemoteResult[seq[PackageEntry]](success: false, error: fmt"Search failed: {e.msg}", errorCode: 500)

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

proc formatBytes*(bytes: int64): string =
  ## Format bytes in human-readable format
  const units = ["B", "KB", "MB", "GB", "TB"]
  var size = float(bytes)
  var unitIndex = 0

  while size >= 1024.0 and unitIndex < units.len - 1:
    size /= 1024.0
    inc unitIndex

  if unitIndex == 0:
    result = fmt"{bytes} {units[unitIndex]}"
  else:
    result = fmt"{size:.1f} {units[unitIndex]}"

proc downloadPackageResumable*(manager: RemoteManager, packageName: string,
                              version: string, url: string): Future[FetchResult[string]] {.async.} =
  ## Download package using resumable fetch with progress reporting
  try:
    # Initialize CAS manager for the download
    var casManager = newCasManager("~/.nip/cas", "/var/lib/nip/cas")

    # Log download start
    logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "remote-manager",
      fmt"Starting resumable download: {packageName} v{version}")

    # Use specialized binary package download
    let result = await fetchBinaryPackage(packageName, version, url, casManager)

    if result.success:
      # Log successful download
      logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "remote-manager",
        fmt"Download completed: {packageName} v{version} ({formatBytes(result.bytesTransferred)})")
    else:
      # Log download failure
      logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "remote-manager",
        fmt"Download failed: {packageName} v{version} - {result.error}")

    return result

  except Exception as e:
    return FetchResult[string](
      success: false,
      error: fmt"Download error: {e.msg}",
      errorCode: 500
    )



export RepositoryType, RepositoryStatus, Repository, RepositoryConfig
export RepositoryStats, RepositoryManifest, PackageEntry
export RemoteManager, RemoteManagerConfig, RemoteResult
export newRemoteManager, getDefaultRemoteManagerConfig, getDefaultRepositoryConfig
export addRepository, removeRepository, getRepository, listRepositories, setActiveRepository
export createSecureClient, makeSecureRequest
export fetchRepositoryManifest, verifyRepositoryTrust
export checkRepositoryHealth, updateAllRepositoryHealth
export searchPackages, downloadPackageResumable, formatBytes