## nimpak/remote/manifest.nim
## Repository manifest parsing and validation for NimPak
##
## This module provides comprehensive manifest handling including:
## - KDL and JSON manifest parsing
## - Cryptographic signature verification
## - Trust score validation
## - Package metadata extraction
## - Manifest generation for publishing

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

type
  ManifestFormat* = enum
    FormatJson = "json"
    FormatKdl = "kdl"

  ManifestVersion* = object
    major*: int
    minor*: int
    patch*: int

  PackageMetadata* = object
    name*: string
    version*: string
    description*: string
    homepage*: Option[string]
    license*: string
    hash*: string
    size*: int64
    trustScore*: float
    dependencies*: seq[string]
    binaries*: Table[string, BinaryMetadata]
    tags*: seq[string]
    maintainer*: Option[string]
    buildDate*: times.DateTime
    acul*: AculMetadata

  BinaryMetadata* = object
    platform*: string           # e.g., "x86_64-linux-musl"
    hash*: string
    size*: int64
    compatibility*: CompatibilityInfo

  CompatibilityInfo* = object
    architecture*: string        # x86_64, aarch64, etc.
    libc*: string               # musl, glibc, etc.
    libcVersion*: string
    allocator*: string          # jemalloc, default, etc.
    cpuFeatures*: seq[string]   # sse4.2, avx2, etc.
    abiVersion*: string

  AculMetadata* = object
    required*: bool
    membership*: string
    license*: string
    reproducible*: bool

  RepositoryMetadata* = object
    id*: string
    name*: string
    description*: string
    url*: string
    maintainer*: string
    created*: times.DateTime
    lastUpdated*: times.DateTime
    packageCount*: int
    trustPolicies*: TrustPolicySet

  ParsedManifest* = object
    format*: ManifestFormat
    version*: ManifestVersion
    repository*: RepositoryMetadata
    packages*: Table[string, PackageMetadata]
    signature*: Option[DigitalSignature]
    timestamp*: times.DateTime
    hash*: string               # Manifest content hash

  ManifestValidator* = object
    keyringManager*: KeyringManager
    trustPolicyManager*: TrustPolicyManager
    config*: ValidationConfig

  ValidationConfig* = object
    requireSignature*: bool
    requireTrustScore*: bool
    minimumTrustScore*: float
    allowUnsignedPackages*: bool
    validateDependencies*: bool
    checkAculCompliance*: bool

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

# =============================================================================
# Manifest Version Handling
# =============================================================================

proc parseManifestVersion*(versionStr: string): ManifestVersion =
  ## Parse manifest version string (e.g., "1.2.3")
  let parts = versionStr.split(".")
  if parts.len != 3:
    raise newException(ValueError, fmt"Invalid version format: {versionStr}")

  ManifestVersion(
    major: parts[0].parseInt(),
    minor: parts[1].parseInt(),
    patch: parts[2].parseInt()
  )

proc `$`*(version: ManifestVersion): string =
  ## Convert manifest version to string
  fmt"{version.major}.{version.minor}.{version.patch}"

proc isCompatible*(version: ManifestVersion, requiredVersion: ManifestVersion): bool =
  ## Check if manifest version is compatible with required version
  # Major version must match, minor version must be >= required
  return version.major == requiredVersion.major and version.minor >= requiredVersion.minor

# =============================================================================
# JSON Manifest Parsing
# =============================================================================

proc parseJsonManifest*(jsonContent: string): ManifestResult[ParsedManifest] =
  ## Parse JSON manifest format
  try:
    let json = parseJson(jsonContent)

    # Parse version
    let version = parseManifestVersion(json["version"].getStr())

    # Parse repository metadata
    let repoJson = json["repository"]
    let repository = RepositoryMetadata(
      id: repoJson["id"].getStr(),
      name: repoJson["name"].getStr(),
      description: repoJson.getOrDefault("description").getStr(""),
      url: repoJson["url"].getStr(),
      maintainer: repoJson.getOrDefault("maintainer").getStr(""),
      created: repoJson["created"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc()),
      lastUpdated: repoJson["last_updated"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc()),
      packageCount: json["packages"].len,
      trustPolicies: newTrustPolicySet("repo-policy", "Repository Policy")  # TODO: Parse policies
    )

    # Parse packages
    var packages = initTable[string, PackageMetadata]()
    for name, packageJson in json["packages"].pairs:
      # Parse binaries
      var binaries = initTable[string, BinaryMetadata]()
      if packageJson.hasKey("binaries"):
        for platform, binaryJson in packageJson["binaries"].pairs:
          let binary = BinaryMetadata(
            platform: platform,
            hash: binaryJson["hash"].getStr(),
            size: binaryJson["size"].getInt(),
            compatibility: CompatibilityInfo(
              architecture: binaryJson["compatibility"]["architecture"].getStr(),
              libc: binaryJson["compatibility"]["libc"].getStr(),
              libcVersion: binaryJson["compatibility"]["libc_version"].getStr(),
              allocator: binaryJson["compatibility"]["allocator"].getStr(),
              cpuFeatures: binaryJson["compatibility"]["cpu_features"].getElems().mapIt(it.getStr()),
              abiVersion: binaryJson["compatibility"]["abi_version"].getStr()
            )
          )
          binaries[platform] = binary

      # Parse ACUL metadata
      let aculJson = packageJson.getOrDefault("acul")
      let acul = if aculJson.kind != JNull:
        AculMetadata(
          required: aculJson["required"].getBool(),
          membership: aculJson["membership"].getStr(),
          license: aculJson["license"].getStr(),
          reproducible: aculJson["reproducible"].getBool()
        )
      else:
        AculMetadata(required: false, membership: "", license: "", reproducible: false)

      let package = PackageMetadata(
        name: name,
        version: packageJson["version"].getStr(),
        description: packageJson.getOrDefault("description").getStr(""),
        homepage: if packageJson.hasKey("homepage"): some(packageJson["homepage"].getStr()) else: none(string),
        license: packageJson.getOrDefault("license").getStr(""),
        hash: packageJson["hash"].getStr(),
        size: packageJson.getOrDefault("size").getInt(),
        trustScore: packageJson.getOrDefault("trust_score").getFloat(0.5),
        dependencies: packageJson.getOrDefault("dependencies").getElems().mapIt(it.getStr()),
        binaries: binaries,
        tags: packageJson.getOrDefault("tags").getElems().mapIt(it.getStr()),
        maintainer: if packageJson.hasKey("maintainer"): some(packageJson["maintainer"].getStr()) else: none(string),
        buildDate: packageJson.getOrDefault("build_date").getStr($now()).parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc()),
        acul: acul
      )
      packages[name] = package

    # Parse signature if present
    var signature: Option[DigitalSignature] = none(DigitalSignature)
    if json.hasKey("signature"):
      let sigJson = json["signature"]
      signature = some(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())
      ))

    # Create manifest
    let manifest = ParsedManifest(
      format: FormatJson,
      version: version,
      repository: repository,
      packages: packages,
      signature: signature,
      timestamp: json["timestamp"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc()),
      hash: ""  # Will be calculated
    )

    return ManifestResult[ParsedManifest](success: true, value: manifest)

  except Exception as e:
    return ManifestResult[ParsedManifest](success: false, error: fmt"JSON parsing failed: {e.msg}", errorCode: 400)

# =============================================================================
# KDL Manifest Parsing
# =============================================================================

proc parseKdlManifest*(kdlContent: string): ManifestResult[ParsedManifest] =
  ## Parse KDL manifest format
  try:
    # TODO: Implement KDL parsing when library is available
    # For now, return error indicating KDL is not yet supported
    return ManifestResult[ParsedManifest](success: false, error: "KDL parsing not yet implemented", errorCode: 501)

  except Exception as e:
    return ManifestResult[ParsedManifest](success: false, error: fmt"KDL parsing failed: {e.msg}", errorCode: 400)

# =============================================================================
# Generic Manifest Parsing
# =============================================================================

proc parseManifest*(content: string, format: ManifestFormat): ManifestResult[ParsedManifest] =
  ## Parse manifest in the specified format
  case format:
  of FormatJson:
    return parseJsonManifest(content)
  of FormatKdl:
    return parseKdlManifest(content)

proc detectManifestFormat*(content: string): ManifestFormat =
  ## Detect manifest format from content
  let trimmed = content.strip()
  if trimmed.startsWith("{"):
    return FormatJson
  elif trimmed.contains("repository") and not trimmed.startsWith("{"):
    return FormatKdl
  else:
    return FormatJson  # Default to JSON

proc parseManifestAuto*(content: string): ManifestResult[ParsedManifest] =
  ## Parse manifest with automatic format detection
  let format = detectManifestFormat(content)
  return parseManifest(content, format)

# =============================================================================
# Manifest Validation
# =============================================================================

proc newManifestValidator*(keyringManager: KeyringManager,
                          trustPolicyManager: TrustPolicyManager): ManifestValidator =
  ## Create a new manifest validator
  ManifestValidator(
    keyringManager: keyringManager,
    trustPolicyManager: trustPolicyManager,
    config: ValidationConfig(
      requireSignature: false,
      requireTrustScore: true,
      minimumTrustScore: 0.5,
      allowUnsignedPackages: true,
      validateDependencies: true,
      checkAculCompliance: false
    )
  )

proc validateManifestSignature*(validator: ManifestValidator, manifest: ParsedManifest): ManifestResult[bool] =
  ## Validate manifest signature
  try:
    if manifest.signature.isNone():
      if validator.config.requireSignature:
        return ManifestResult[bool](success: false, error: "Manifest signature required but not found", errorCode: 401)
      else:
        return ManifestResult[bool](success: true, value: true)

    let signature = manifest.signature.get()

    # Find signing key
    let keyOpt = validator.keyringManager.findKey(signature.keyId)
    if keyOpt.isNone():
      return ManifestResult[bool](success: false, error: fmt"Signing key not found: {signature.keyId}", errorCode: 401)

    let key = keyOpt.get()

    # Check if key is revoked
    if validator.keyringManager.isKeyRevoked(signature.keyId):
      return ManifestResult[bool](success: false, error: fmt"Signing key is revoked: {signature.keyId}", errorCode: 401)

    # TODO: Verify actual signature when crypto is available
    # For now, assume signature is valid if key exists and is not revoked

    logGlobalSecurityEvent(EventPackageVerification, SeverityInfo, "manifest-validator",
      fmt"Manifest signature verified: {signature.keyId}")

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

  except Exception as e:
    return ManifestResult[bool](success: false, error: fmt"Signature validation failed: {e.msg}", errorCode: 500)

proc validatePackageTrustScores*(validator: ManifestValidator, manifest: ParsedManifest): ManifestResult[seq[string]] =
  ## Validate package trust scores
  try:
    var lowTrustPackages: seq[string] = @[]

    if not validator.config.requireTrustScore:
      return ManifestResult[seq[string]](success: true, value: lowTrustPackages)

    for name, package in manifest.packages.pairs:
      if package.trustScore < validator.config.minimumTrustScore:
        lowTrustPackages.add(fmt"{name} (score: {package.trustScore:.3f})")

    if lowTrustPackages.len > 0:
      let errorMsg = fmt"Packages below minimum trust score ({validator.config.minimumTrustScore:.3f}): {lowTrustPackages.join(\", \")}"
      return ManifestResult[seq[string]](success: false, error: errorMsg, errorCode: 403)

    return ManifestResult[seq[string]](success: true, value: lowTrustPackages)

  except Exception as e:
    return ManifestResult[seq[string]](success: false, error: fmt"Trust score validation failed: {e.msg}", errorCode: 500)

proc validatePackageDependencies*(validator: ManifestValidator, manifest: ParsedManifest): ManifestResult[seq[string]] =
  ## Validate package dependencies
  try:
    var missingDependencies: seq[string] = @[]

    if not validator.config.validateDependencies:
      return ManifestResult[seq[string]](success: true, value: missingDependencies)

    for name, package in manifest.packages.pairs:
      for dependency in package.dependencies:
        # Check if dependency exists in manifest
        if dependency notin manifest.packages:
          missingDependencies.add(fmt"{name} -> {dependency}")

    if missingDependencies.len > 0:
      let errorMsg = fmt"Missing dependencies: {missingDependencies.join(\", \")}"
      return ManifestResult[seq[string]](success: false, error: errorMsg, errorCode: 400)

    return ManifestResult[seq[string]](success: true, value: missingDependencies)

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

proc validateAculCompliance*(validator: ManifestValidator, manifest: ParsedManifest): ManifestResult[seq[string]] =
  ## Validate ACUL compliance
  try:
    var nonCompliantPackages: seq[string] = @[]

    if not validator.config.checkAculCompliance:
      return ManifestResult[seq[string]](success: true, value: nonCompliantPackages)

    for name, package in manifest.packages.pairs:
      if package.acul.required and not package.acul.reproducible:
        nonCompliantPackages.add(fmt"{name} (not reproducible)")

    if nonCompliantPackages.len > 0:
      let errorMsg = fmt"ACUL non-compliant packages: {nonCompliantPackages.join(\", \")}"
      return ManifestResult[seq[string]](success: false, error: errorMsg, errorCode: 403)

    return ManifestResult[seq[string]](success: true, value: nonCompliantPackages)

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

proc validateManifest*(validator: ManifestValidator, manifest: ParsedManifest): ManifestResult[bool] =
  ## Perform comprehensive manifest validation
  try:
    # Validate signature
    let sigResult = validator.validateManifestSignature(manifest)
    if not sigResult.success:
      return ManifestResult[bool](success: false, error: sigResult.error, errorCode: sigResult.errorCode)

    # Validate trust scores
    let trustResult = validator.validatePackageTrustScores(manifest)
    if not trustResult.success:
      return ManifestResult[bool](success: false, error: trustResult.error, errorCode: trustResult.errorCode)

    # Validate dependencies
    let depResult = validator.validatePackageDependencies(manifest)
    if not depResult.success:
      return ManifestResult[bool](success: false, error: depResult.error, errorCode: depResult.errorCode)

    # Validate ACUL compliance
    let aculResult = validator.validateAculCompliance(manifest)
    if not aculResult.success:
      return ManifestResult[bool](success: false, error: aculResult.error, errorCode: aculResult.errorCode)

    logGlobalSecurityEvent(EventPackageVerification, SeverityInfo, "manifest-validator",
      fmt"Manifest validation passed: {manifest.repository.id}")

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

  except Exception as e:
    return ManifestResult[bool](success: false, error: fmt"Manifest validation failed: {e.msg}", errorCode: 500)

# =============================================================================
# Manifest Generation
# =============================================================================

proc generateJsonManifest*(repository: RepositoryMetadata, packages: Table[string, PackageMetadata],
                          signature: Option[DigitalSignature] = none(DigitalSignature)): string =
  ## Generate JSON manifest
  var manifest = %*{
    "version": "1.0",
    "timestamp": $now(),
    "repository": {
      "id": repository.id,
      "name": repository.name,
      "description": repository.description,
      "url": repository.url,
      "maintainer": repository.maintainer,
      "created": $repository.created,
      "last_updated": $repository.lastUpdated
    },
    "packages": newJObject()
  }

  # Add packages
  for name, package in packages.pairs:
    var packageJson = %*{
      "version": package.version,
      "description": package.description,
      "license": package.license,
      "hash": package.hash,
      "size": package.size,
      "trust_score": package.trustScore,
      "dependencies": package.dependencies,
      "tags": package.tags,
      "build_date": $package.buildDate,
      "acul": {
        "required": package.acul.required,
        "membership": package.acul.membership,
        "license": package.acul.license,
        "reproducible": package.acul.reproducible
      }
    }

    # Add optional fields
    if package.homepage.isSome():
      packageJson["homepage"] = %package.homepage.get()

    if package.maintainer.isSome():
      packageJson["maintainer"] = %package.maintainer.get()

    # Add binaries
    if package.binaries.len > 0:
      var binariesJson = newJObject()
      for platform, binary in package.binaries.pairs:
        binariesJson[platform] = %*{
          "hash": binary.hash,
          "size": binary.size,
          "compatibility": {
            "architecture": binary.compatibility.architecture,
            "libc": binary.compatibility.libc,
            "libc_version": binary.compatibility.libcVersion,
            "allocator": binary.compatibility.allocator,
            "cpu_features": binary.compatibility.cpuFeatures,
            "abi_version": binary.compatibility.abiVersion
          }
        }
      packageJson["binaries"] = binariesJson

    manifest["packages"][name] = packageJson

  # Add signature if present
  if signature.isSome():
    let sig = signature.get()
    manifest["signature"] = %*{
      "algorithm": $sig.algorithm,
      "key_id": sig.keyId,
      "signature": sig.signature,
      "timestamp": $sig.timestamp
    }

  return manifest.pretty()

proc generateKdlManifest*(repository: RepositoryMetadata, packages: Table[string, PackageMetadata],
                         signature: Option[DigitalSignature] = none(DigitalSignature)): string =
  ## Generate KDL manifest
  # TODO: Implement KDL generation when library is available
  return "// KDL manifest generation not yet implemented"

# =============================================================================
# Manifest Utilities
# =============================================================================

proc calculateManifestHash*(content: string): string =
  ## Calculate hash of manifest content
  # TODO: Use actual hash function from security module
  return fmt"blake3-manifest-{content.len}"

proc getPackagesByTag*(manifest: ParsedManifest, tag: string): seq[PackageMetadata] =
  ## Get packages by tag
  var result: seq[PackageMetadata] = @[]
  for name, package in manifest.packages.pairs:
    if tag in package.tags:
      result.add(package)
  return result

proc getPackagesByTrustScore*(manifest: ParsedManifest, minScore: float): seq[PackageMetadata] =
  ## Get packages with trust score above threshold
  var result: seq[PackageMetadata] = @[]
  for name, package in manifest.packages.pairs:
    if package.trustScore >= minScore:
      result.add(package)
  return result.sortedByIt(-it.trustScore)

proc getManifestStatistics*(manifest: ParsedManifest): JsonNode =
  ## Get manifest statistics
  var totalSize: int64 = 0
  var trustScores: seq[float] = @[]
  var tagCounts = initTable[string, int]()

  for name, package in manifest.packages.pairs:
    totalSize += package.size
    trustScores.add(package.trustScore)

    for tag in package.tags:
      if tag in tagCounts:
        tagCounts[tag] += 1
      else:
        tagCounts[tag] = 1

  let avgTrustScore = if trustScores.len > 0: trustScores.sum() / trustScores.len.float else: 0.0

  return %*{
    "package_count": manifest.packages.len,
    "total_size": totalSize,
    "average_trust_score": avgTrustScore,
    "min_trust_score": if trustScores.len > 0: trustScores.min() else: 0.0,
    "max_trust_score": if trustScores.len > 0: trustScores.max() else: 0.0,
    "tag_counts": %tagCounts,
    "has_signature": manifest.signature.isSome(),
    "manifest_age": (now() - manifest.timestamp).inHours
  }

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

export ManifestFormat, ManifestVersion, PackageMetadata, BinaryMetadata
export CompatibilityInfo, AculMetadata, RepositoryMetadata, ParsedManifest
export ManifestValidator, ValidationConfig, ManifestResult
export parseManifestVersion, isCompatible
export parseJsonManifest, parseKdlManifest, parseManifest, detectManifestFormat, parseManifestAuto
export newManifestValidator, validateManifestSignature, validatePackageTrustScores
export validatePackageDependencies, validateAculCompliance, validateManifest
export generateJsonManifest, generateKdlManifest
export calculateManifestHash, getPackagesByTag, getPackagesByTrustScore, getManifestStatistics