## nimpak/security/provenance_tracker.nim
## Provenance tracking and trust system for NimPak
##
## This module implements:
## - Complete package source attribution tracking with cryptographic proof
## - Trust scoring based on event logs and keyring validation
## - nip track command for provenance queries
## - Integration with security event logging for audit trails

import std/[times, json, tables, sequtils, strutils, strformat, algorithm, options]
import hash_verifier, signature_verifier_working, keyring_manager, event_logger

type
  ProvenanceSource* = enum
    SourceOriginal = "original"      # Built from original source
    SourceGrafted = "grafted"        # Imported from external ecosystem
    SourceConverted = "converted"    # Converted from grafted to .npk
    SourceRebuilt = "rebuilt"        # Rebuilt from source with modifications
    SourceMirrored = "mirrored"      # Mirrored from another repository

  ProvenanceStep* = object
    stepType*: string               # "source", "build", "sign", "graft", "convert"
    timestamp*: times.DateTime      # When this step occurred
    actor*: string                  # Who/what performed this step
    location*: string               # Where this step was performed
    inputHash*: string              # Hash of input to this step
    outputHash*: string             # Hash of output from this step
    metadata*: JsonNode             # Step-specific metadata
    signature*: Option[DigitalSignature]  # Cryptographic proof
    verified*: bool                 # Whether this step has been verified

  ProvenanceChain* = object
    packageId*: string              # Package identifier
    version*: string                # Package version
    source*: ProvenanceSource       # How package was obtained
    originalUrl*: Option[string]    # Original source URL if available
    steps*: seq[ProvenanceStep]     # Complete provenance chain
    trustScore*: float              # Calculated trust score (0.0-1.0)
    lastVerified*: times.DateTime   # When provenance was last verified
    verificationErrors*: seq[string] # Any verification issues

  TrustPolicy* = object
    minimumTrustScore*: float       # Minimum required trust score
    requireOriginalSource*: bool    # Require original source provenance
    allowGraftedPackages*: bool     # Allow grafted packages
    requireSignedSteps*: bool       # Require cryptographic signatures
    maxProvenanceAge*: int          # Maximum age in days
    trustedActors*: seq[string]     # List of trusted actors
    trustedLocations*: seq[string]  # List of trusted build locations

  TrustCalculator* = object
    policy*: TrustPolicy
    keyringManager*: KeyringManager
    eventHistory*: seq[SecurityEvent]

  ProvenanceTracker* = object
    provenanceStore*: Table[string, ProvenanceChain]  # packageId -> provenance
    trustCalculator*: TrustCalculator
    config*: ProvenanceConfig

  ProvenanceConfig* = object
    enableTracking*: bool
    storeLocation*: string          # Where to store provenance data
    verificationInterval*: int      # Hours between verification
    autoVerifyOnInstall*: bool      # Verify provenance during installation
    requireProvenanceForInstall*: bool  # Block install without provenance

# Forward declarations
proc validateHashChain*(chain: ProvenanceChain): bool

# =============================================================================
# Provenance Chain Construction
# =============================================================================

proc newProvenanceChain*(packageId: string, version: string,
                        source: ProvenanceSource): ProvenanceChain =
  ## Create a new provenance chain
  ProvenanceChain(
    packageId: packageId,
    version: version,
    source: source,
    originalUrl: none(string),
    steps: @[],
    trustScore: 0.0,
    lastVerified: default(times.DateTime),
    verificationErrors: @[]
  )

proc addProvenanceStep*(chain: var ProvenanceChain, stepType: string,
                       actor: string, location: string, inputHash: string,
                       outputHash: string, metadata: JsonNode = newJNull(),
                       signature: Option[DigitalSignature] = none(DigitalSignature)) =
  ## Add a step to the provenance chain
  let step = ProvenanceStep(
    stepType: stepType,
    timestamp: now().utc(),
    actor: actor,
    location: location,
    inputHash: inputHash,
    outputHash: outputHash,
    metadata: metadata,
    signature: signature,
    verified: false
  )

  chain.steps.add(step)

proc createSourceStep*(sourceUrl: string, sourceHash: string,
                      fetchedBy: string): ProvenanceStep =
  ## Create a source fetching step
  ProvenanceStep(
    stepType: "source",
    timestamp: now().utc(),
    actor: fetchedBy,
    location: "source-fetch",
    inputHash: "",
    outputHash: sourceHash,
    metadata: %*{
      "source_url": sourceUrl,
      "fetch_method": "http",
      "user_agent": "nimpak-fetcher/1.0"
    },
    signature: none(DigitalSignature),
    verified: false
  )

proc createBuildStep*(buildSystem: string, buildFlags: seq[string],
                     builder: string, buildLocation: string,
                     inputHash: string, outputHash: string): ProvenanceStep =
  ## Create a build step
  ProvenanceStep(
    stepType: "build",
    timestamp: now().utc(),
    actor: builder,
    location: buildLocation,
    inputHash: inputHash,
    outputHash: outputHash,
    metadata: %*{
      "build_system": buildSystem,
      "build_flags": buildFlags,
      "compiler_version": "nim-2.0.0",  # TODO: Get actual version
      "build_environment": "reproducible"
    },
    signature: none(DigitalSignature),
    verified: false
  )

proc createGraftStep*(sourceEcosystem: string, originalPackage: string,
                     grafter: string, inputHash: string, outputHash: string): ProvenanceStep =
  ## Create a grafting step
  ProvenanceStep(
    stepType: "graft",
    timestamp: now().utc(),
    actor: grafter,
    location: "graft-engine",
    inputHash: inputHash,
    outputHash: outputHash,
    metadata: %*{
      "source_ecosystem": sourceEcosystem,
      "original_package": originalPackage,
      "graft_method": "archive-extraction",
      "verification_performed": true
    },
    signature: none(DigitalSignature),
    verified: false
  )

proc createSigningStep*(signer: string, keyId: string, algorithm: string,
                       inputHash: string, signature: DigitalSignature): ProvenanceStep =
  ## Create a signing step
  ProvenanceStep(
    stepType: "sign",
    timestamp: signature.timestamp,
    actor: signer,
    location: "signing-infrastructure",
    inputHash: inputHash,
    outputHash: inputHash,  # Signing doesn't change content hash
    metadata: %*{
      "key_id": keyId,
      "algorithm": algorithm,
      "signature_timestamp": $signature.timestamp
    },
    signature: some(signature),
    verified: false
  )

# =============================================================================
# Trust Score Calculation
# =============================================================================

proc newTrustCalculator*(policy: TrustPolicy, keyringManager: KeyringManager): TrustCalculator =
  ## Create a new trust calculator
  TrustCalculator(
    policy: policy,
    keyringManager: keyringManager,
    eventHistory: @[]
  )

proc getDefaultTrustPolicy*(): TrustPolicy =
  ## Get default trust policy
  TrustPolicy(
    minimumTrustScore: 0.7,
    requireOriginalSource: false,
    allowGraftedPackages: true,
    requireSignedSteps: false,
    maxProvenanceAge: 365,  # 1 year
    trustedActors: @[
      "nexusos-build-farm",
      "community-builder",
      "automated-graft-engine"
    ],
    trustedLocations: @[
      "nexusos-infrastructure",
      "github-actions",
      "reproducible-builds"
    ]
  )

proc calculateStepTrust*(calculator: TrustCalculator, step: ProvenanceStep): float =
  ## Calculate trust score for a single provenance step
  var score = 0.5  # Base score

  # Actor trust
  if step.actor in calculator.policy.trustedActors:
    score += 0.2

  # Location trust
  if step.location in calculator.policy.trustedLocations:
    score += 0.1

  # Signature verification
  if step.signature.isSome():
    let sig = step.signature.get()
    let keyOpt = calculator.keyringManager.findKey(sig.keyId)

    if keyOpt.isSome():
      let key = keyOpt.get()
      if not calculator.keyringManager.isKeyRevoked(sig.keyId):
        score += 0.2  # Valid signature from trusted key

        # Bonus for key status
        case key.status:
        of KeyActive: score += 0.1
        of KeyDeprecated: score += 0.05
        else: score -= 0.1
      else:
        score -= 0.3  # Revoked key penalty
    else:
      score -= 0.1  # Unknown key penalty

  # Step type trust
  case step.stepType:
  of "source": score += 0.1  # Original source is good
  of "build": score += 0.15  # Reproducible builds are great
  of "sign": score += 0.1   # Signatures add trust
  of "graft": score -= 0.05 # Grafting is slightly less trusted
  of "convert": score -= 0.02 # Conversion is mostly neutral

  # Age penalty
  let age = (now().utc() - step.timestamp).inDays
  if age > calculator.policy.maxProvenanceAge:
    score -= 0.2
  elif age > (calculator.policy.maxProvenanceAge div 2):
    score -= 0.1

  return max(0.0, min(1.0, score))

proc calculateChainTrust*(calculator: TrustCalculator, chain: ProvenanceChain): float =
  ## Calculate overall trust score for a provenance chain
  if chain.steps.len == 0:
    return 0.0

  var totalScore = 0.0
  var stepCount = 0

  # Calculate weighted average of step scores
  for step in chain.steps:
    let stepScore = calculateStepTrust(calculator, step)
    totalScore += stepScore
    inc stepCount

  var chainScore = totalScore / stepCount.float

  # Source type modifiers
  case chain.source:
  of SourceOriginal: chainScore += 0.1   # Original source bonus
  of SourceGrafted: chainScore -= 0.05   # Grafted penalty
  of SourceConverted: chainScore -= 0.02 # Conversion penalty
  of SourceRebuilt: chainScore += 0.05   # Rebuild can be good
  of SourceMirrored: chainScore -= 0.1   # Mirrored is less trusted

  # Chain completeness bonus
  let hasSource = chain.steps.anyIt(it.stepType == "source")
  let hasBuild = chain.steps.anyIt(it.stepType == "build")
  let hasSign = chain.steps.anyIt(it.stepType == "sign")

  if hasSource and hasBuild and hasSign:
    chainScore += 0.1  # Complete chain bonus
  elif hasSource and hasBuild:
    chainScore += 0.05  # Partial completeness

  # Verification errors penalty
  if chain.verificationErrors.len > 0:
    chainScore -= (chain.verificationErrors.len.float * 0.1)

  return max(0.0, min(1.0, chainScore))

# =============================================================================
# Provenance Verification
# =============================================================================

proc verifyProvenanceStep*(calculator: TrustCalculator, step: var ProvenanceStep): bool =
  ## Verify a single provenance step
  var verified = true

  # Verify signature if present
  if step.signature.isSome():
    let sig = step.signature.get()

    # Check if key exists and is not revoked
    let keyOpt = calculator.keyringManager.findKey(sig.keyId)
    if keyOpt.isNone():
      verified = false
    elif calculator.keyringManager.isKeyRevoked(sig.keyId):
      verified = false
    else:
      # TODO: Verify actual signature when crypto is available
      # For now, assume signature is valid if key is trusted
      verified = true

  # Verify hash chain consistency
  if step.inputHash != "" and step.outputHash != "":
    # TODO: Verify that the transformation is valid
    # This would involve re-computing hashes or checking build reproducibility
    verified = verified and true

  step.verified = verified
  return verified

proc verifyProvenanceChain*(calculator: TrustCalculator, chain: var ProvenanceChain): bool =
  ## Verify an entire provenance chain
  var allVerified = true
  chain.verificationErrors = @[]

  # Verify each step
  for step in chain.steps.mitems:
    if not verifyProvenanceStep(calculator, step):
      allVerified = false
      chain.verificationErrors.add(fmt"Step {step.stepType} verification failed")

  # Verify hash chain continuity
  for i in 1..<chain.steps.len:
    let prevStep = chain.steps[i-1]
    let currStep = chain.steps[i]

    if prevStep.outputHash != currStep.inputHash:
      allVerified = false
      let prevIndex = i-1
      let currIndex = i
      chain.verificationErrors.add(fmt"Hash chain break between steps {prevIndex} and {currIndex}")

  # Update trust score
  chain.trustScore = calculateChainTrust(calculator, chain)
  chain.lastVerified = now().utc()

  return allVerified

# =============================================================================
# Provenance Storage and Retrieval
# =============================================================================

proc newProvenanceTracker*(config: ProvenanceConfig): ProvenanceTracker =
  ## Create a new provenance tracker
  let keyringConfig = getDefaultKeyringConfig()
  var keyringManager = newKeyringManager(keyringConfig)
  keyringManager.loadAllKeyrings()

  let trustPolicy = getDefaultTrustPolicy()
  let trustCalculator = newTrustCalculator(trustPolicy, keyringManager)

  ProvenanceTracker(
    provenanceStore: initTable[string, ProvenanceChain](),
    trustCalculator: trustCalculator,
    config: config
  )

proc getDefaultProvenanceConfig*(): ProvenanceConfig =
  ## Get default provenance configuration
  ProvenanceConfig(
    enableTracking: true,
    storeLocation: "/var/lib/nimpak/provenance",
    verificationInterval: 24,  # 24 hours
    autoVerifyOnInstall: true,
    requireProvenanceForInstall: false  # Conservative default
  )

proc storeProvenance*(tracker: var ProvenanceTracker, chain: ProvenanceChain) =
  ## Store provenance chain
  let key = fmt"{chain.packageId}-{chain.version}"
  tracker.provenanceStore[key] = chain

  # TODO: Persist to disk
  # let provenanceFile = tracker.config.storeLocation / fmt"{key}.json"
  # writeFile(provenanceFile, chain.toJson().pretty())

proc getProvenance*(tracker: ProvenanceTracker, packageId: string,
                   version: string): Option[ProvenanceChain] =
  ## Retrieve provenance chain
  let key = fmt"{packageId}-{version}"
  if key in tracker.provenanceStore:
    return some(tracker.provenanceStore[key])

  # TODO: Load from disk if not in memory
  return none(ProvenanceChain)

# =============================================================================
# CLI Integration Functions
# =============================================================================

proc trackPackageProvenance*(packageId: string, version: string = ""): Option[ProvenanceChain] =
  ## Track provenance for a package (for nip track command)
  let tracker = newProvenanceTracker(getDefaultProvenanceConfig())

  # If no version specified, find the installed version
  let finalVersion = if version == "":
    # TODO: Query installed version from package manager
    "current"
  else:
    version

  return tracker.getProvenance(packageId, finalVersion)

proc createGraftProvenance*(packageId: string, version: string,
                          sourceEcosystem: string, originalUrl: string,
                          graftHash: string): ProvenanceChain =
  ## Create provenance chain for a grafted package
  var chain = newProvenanceChain(packageId, version, SourceGrafted)
  chain.originalUrl = some(originalUrl)

  # Add source step
  let sourceStep = createSourceStep(originalUrl, "source-hash-placeholder", "graft-engine")
  chain.steps.add(sourceStep)

  # Add graft step
  let graftStep = createGraftStep(sourceEcosystem, packageId, "automated-graft-engine",
                                 "source-hash-placeholder", graftHash)
  chain.steps.add(graftStep)

  return chain

# =============================================================================
# Trust Policy Enforcement
# =============================================================================

proc checkTrustPolicy*(chain: ProvenanceChain, policy: TrustPolicy): tuple[allowed: bool, reason: string] =
  ## Check if a package meets trust policy requirements
  # Check minimum trust score
  if chain.trustScore < policy.minimumTrustScore:
    return (false, fmt"Trust score {chain.trustScore:.2f} below minimum {policy.minimumTrustScore:.2f}")

  # Check original source requirement
  if policy.requireOriginalSource and chain.source != SourceOriginal:
    return (false, fmt"Policy requires original source, but package is {chain.source}")

  # Check grafted package policy
  if not policy.allowGraftedPackages and chain.source == SourceGrafted:
    return (false, "Policy does not allow grafted packages")

  # Check signed steps requirement
  if policy.requireSignedSteps:
    let hasSignedStep = chain.steps.anyIt(it.signature.isSome())
    if not hasSignedStep:
      return (false, "Policy requires signed steps, but none found")

  # Check provenance age
  if chain.steps.len > 0:
    var oldestStep = chain.steps[0]
    for step in chain.steps:
      if step.timestamp < oldestStep.timestamp:
        oldestStep = step
    let age = (now().utc() - oldestStep.timestamp).inDays
    if age > policy.maxProvenanceAge:
      return (false, fmt"Provenance age {age} days exceeds maximum {policy.maxProvenanceAge} days")

  return (true, "Package meets trust policy requirements")

# =============================================================================
# Integration with Grafting and Conversion Operations
# =============================================================================

proc preserveProvenanceDuringGraft*(originalChain: ProvenanceChain,
                                   graftedPackageName: string, graftedVersion: string,
                                   sourceEcosystem: string): ProvenanceChain =
  ## Preserve provenance information during grafting operations
  var newChain = newProvenanceChain(graftedPackageName, graftedVersion, SourceGrafted)

  # Copy original URL if available
  newChain.originalUrl = originalChain.originalUrl

  # Copy original steps with metadata indicating they're from grafting
  for step in originalChain.steps:
    var preservedStep = step
    preservedStep.metadata["preserved_from_graft"] = %true
    preservedStep.metadata["original_package"] = %originalChain.packageId
    newChain.steps.add(preservedStep)

  # Add the grafting step
  let graftStep = createGraftStep(sourceEcosystem, originalChain.packageId,
                                 "nimpak-graft-engine",
                                 originalChain.steps[^1].outputHash,
                                 "grafted-hash-placeholder")
  newChain.steps.add(graftStep)

  return newChain

proc preserveProvenanceDuringConvert*(graftedChain: ProvenanceChain,
                                     convertedHash: string): ProvenanceChain =
  ## Preserve provenance information during package conversion
  var convertedChain = graftedChain
  convertedChain.source = SourceConverted

  # Add conversion step
  let convertStep = ProvenanceStep(
    stepType: "convert",
    timestamp: now().utc(),
    actor: "nimpak-converter",
    location: "conversion-engine",
    inputHash: graftedChain.steps[^1].outputHash,
    outputHash: convertedHash,
    metadata: %*{
      "conversion_type": "graft_to_npk",
      "original_source": $graftedChain.source,
      "conversion_timestamp": $now()
    },
    signature: none(DigitalSignature),
    verified: false
  )

  convertedChain.steps.add(convertStep)
  return convertedChain

proc integrateWithSecurityEventLogging*(chain: ProvenanceChain, eventType: SecurityEventType) =
  ## Integrate provenance tracking with security event logging
  let eventDetails = %*{
    "package_id": chain.packageId,
    "version": chain.version,
    "source_type": $chain.source,
    "trust_score": chain.trustScore,
    "step_count": chain.steps.len,
    "has_signatures": chain.steps.anyIt(it.signature.isSome()),
    "verification_errors": chain.verificationErrors.len
  }

  let severity = if chain.trustScore >= 0.8: SeverityInfo
                elif chain.trustScore >= 0.5: SeverityWarning
                else: SeverityError

  logGlobalSecurityEvent(eventType, severity, "provenance-tracker",
    fmt"Provenance tracked for {chain.packageId}: trust score {chain.trustScore:.3f}",
    eventDetails)

# =============================================================================
# Advanced Trust Scoring with Community Reputation
# =============================================================================

proc calculateAdvancedTrustScore*(chain: ProvenanceChain, calculator: TrustCalculator,
                                 communityReputation: Option[string] = none(string)): float =
  ## Calculate advanced trust score incorporating community reputation
  var baseScore = calculateChainTrust(calculator, chain)

  # Apply community reputation bonus/penalty
  if communityReputation.isSome():
    let actor = communityReputation.get()
    # TODO: Implement proper CommunityActor type with reputation field
    # For now, just apply a small bonus for having community reputation
    baseScore += 0.05

  # Apply additional scoring factors
  let hasCompleteChain = chain.steps.anyIt(it.stepType == "source") and
                        chain.steps.anyIt(it.stepType == "build") and
                        chain.steps.anyIt(it.stepType == "sign")

  if hasCompleteChain:
    baseScore += 0.05

  # Penalize for verification errors
  if chain.verificationErrors.len > 0:
    baseScore -= (chain.verificationErrors.len.float * 0.05)

  return max(0.0, min(1.0, baseScore))

# =============================================================================
# Audit Trail Integration
# =============================================================================

proc generateProvenanceAuditTrail*(chain: ProvenanceChain): JsonNode =
  ## Generate comprehensive audit trail for provenance chain
  %*{
    "audit_trail": {
      "package_id": chain.packageId,
      "version": chain.version,
      "source_type": $chain.source,
      "trust_score": chain.trustScore,
      "last_verified": $chain.lastVerified,
      "audit_timestamp": $now(),
      "provenance_steps": chain.steps.mapIt(%*{
        "step_number": chain.steps.find(it) + 1,
        "step_type": it.stepType,
        "timestamp": $it.timestamp,
        "actor": it.actor,
        "location": it.location,
        "input_hash": it.inputHash,
        "output_hash": it.outputHash,
        "verified": it.verified,
        "has_signature": it.signature.isSome(),
        "metadata": it.metadata
      }),
      "verification_errors": chain.verificationErrors,
      "chain_integrity": {
        "hash_chain_valid": validateHashChain(chain),
        "signature_count": chain.steps.countIt(it.signature.isSome()),
        "verified_steps": chain.steps.countIt(it.verified),
        "total_steps": chain.steps.len
      }
    }
  }

proc validateHashChain*(chain: ProvenanceChain): bool =
  ## Validate that the hash chain is consistent
  if chain.steps.len <= 1:
    return true

  for i in 1..<chain.steps.len:
    let prevStep = chain.steps[i-1]
    let currStep = chain.steps[i]

    # Skip validation for certain step types that don't modify content
    if currStep.stepType in ["sign", "verify"]:
      continue

    if prevStep.outputHash != currStep.inputHash:
      return false

  return true

# =============================================================================
# Export enhanced functions
# =============================================================================

export ProvenanceSource, ProvenanceStep, ProvenanceChain, TrustPolicy
export TrustCalculator, ProvenanceTracker, ProvenanceConfig
export newProvenanceChain, addProvenanceStep
export createSourceStep, createBuildStep, createGraftStep, createSigningStep
export newTrustCalculator, getDefaultTrustPolicy
export calculateStepTrust, calculateChainTrust, calculateAdvancedTrustScore
export verifyProvenanceStep, verifyProvenanceChain
export newProvenanceTracker, getDefaultProvenanceConfig
export storeProvenance, getProvenance
export trackPackageProvenance, createGraftProvenance, checkTrustPolicy
export preserveProvenanceDuringGraft, preserveProvenanceDuringConvert
export integrateWithSecurityEventLogging, generateProvenanceAuditTrail
export validateHashChain