## nimpak/security/trust_policy.nim
## Trust policy management and enforcement for NimPak
##
## This module implements comprehensive trust policy management including:
## - Community reputation system
## - Trust policy enforcement
## - Dynamic trust scoring
## - Policy configuration and validation

import std/[times, json, tables, sequtils, strutils, strformat, algorithm, os, options]
import ../cli/core
import provenance_tracker, keyring_manager, event_logger

type
  ReputationLevel* = enum
    ReputationUnknown = "unknown"
    ReputationUntrusted = "untrusted"
    ReputationLimited = "limited"
    ReputationTrusted = "trusted"
    ReputationHighlyTrusted = "highly_trusted"

  CommunityActor* = object
    actorId*: string
    displayName*: string
    reputation*: ReputationLevel
    trustScore*: float              # 0.0-1.0
    packagesBuilt*: int
    packagesVerified*: int
    lastActivity*: times.DateTime
    verificationHistory*: seq[VerificationEvent]
    publicKeys*: seq[string]        # Associated key IDs

  VerificationEvent* = object
    timestamp*: times.DateTime
    packageId*: string
    eventType*: string              # "build", "verify", "sign", "report"
    outcome*: bool                  # Success/failure
    details*: JsonNode

  TrustPolicyRule* = object
    ruleId*: string
    name*: string
    description*: string
    condition*: string              # Policy condition expression
    action*: string                 # "allow", "deny", "warn", "require_approval"
    priority*: int                  # Higher priority rules evaluated first
    enabled*: bool

  TrustPolicySet* = object
    policyId*: string
    name*: string
    description*: string
    version*: string
    rules*: seq[TrustPolicyRule]
    defaultAction*: string          # Default action when no rules match
    created*: times.DateTime
    lastModified*: times.DateTime

  CommunityReputationSystem* = object
    actors*: Table[string, CommunityActor]
    verificationEvents*: seq[VerificationEvent]
    config*: ReputationConfig

  ReputationConfig* = object
    enableCommunityReputation*: bool
    reputationDecayDays*: int       # Days after which reputation starts decaying
    minimumEventsForTrust*: int     # Minimum events needed for trusted status
    verificationWeight*: float      # Weight of verification events
    buildWeight*: float             # Weight of build events
    reportWeight*: float            # Weight of security reports

  TrustPolicyManager* = object
    policies*: Table[string, TrustPolicySet]
    activePolicyId*: string
    reputationSystem*: CommunityReputationSystem
    config*: TrustPolicyConfig

  TrustPolicyConfig* = object
    enablePolicyEnforcement*: bool
    policyStorePath*: string
    reputationStorePath*: string
    autoUpdateReputation*: bool
    requireApprovalThreshold*: float  # Trust score below which approval is required

# =============================================================================
# Community Reputation System
# =============================================================================

proc newCommunityActor*(actorId: string, displayName: string): CommunityActor =
  ## Create a new community actor
  CommunityActor(
    actorId: actorId,
    displayName: displayName,
    reputation: ReputationUnknown,
    trustScore: 0.5,  # Neutral starting score
    packagesBuilt: 0,
    packagesVerified: 0,
    lastActivity: now(),
    verificationHistory: @[],
    publicKeys: @[]
  )

proc addVerificationEvent*(actor: var CommunityActor, event: VerificationEvent) =
  ## Add a verification event to an actor's history
  actor.verificationHistory.add(event)
  actor.lastActivity = event.timestamp

  # Update counters
  case event.eventType:
  of "build":
    if event.outcome:
      actor.packagesBuilt += 1
  of "verify":
    if event.outcome:
      actor.packagesVerified += 1
  else:
    discard

proc calculateActorTrustScore*(actor: CommunityActor, config: ReputationConfig): float =
  ## Calculate trust score for a community actor
  if actor.verificationHistory.len == 0:
    return 0.5  # Neutral score for unknown actors

  var score = 0.5  # Base score
  var totalWeight = 0.0

  # Analyze verification history
  let recentEvents = actor.verificationHistory.filterIt(
    (now() - it.timestamp).inDays <= config.reputationDecayDays
  )

  for event in recentEvents:
    var eventWeight = 0.0
    var eventScore = if event.outcome: 1.0 else: 0.0

    case event.eventType:
    of "build":
      eventWeight = config.buildWeight
    of "verify":
      eventWeight = config.verificationWeight
    of "report":
      eventWeight = config.reportWeight
      # Security reports are weighted differently
      eventScore = if event.outcome: 0.8 else: 0.2
    else:
      eventWeight = 0.1

    score += eventScore * eventWeight
    totalWeight += eventWeight

  # Calculate weighted average
  if totalWeight > 0:
    score = score / totalWeight
  else:
    score = 0.5

  # Apply reputation level modifiers
  case actor.reputation:
  of ReputationHighlyTrusted: score += 0.2
  of ReputationTrusted: score += 0.1
  of ReputationLimited: score -= 0.1
  of ReputationUntrusted: score -= 0.3
  of ReputationUnknown: discard

  # Ensure score is within bounds
  return max(0.0, min(1.0, score))

proc updateActorReputation*(actor: var CommunityActor, config: ReputationConfig) =
  ## Update actor's reputation level based on their history
  let newScore = calculateActorTrustScore(actor, config)
  actor.trustScore = newScore

  # Update reputation level
  let eventCount = actor.verificationHistory.len
  let successRate = if eventCount > 0:
    actor.verificationHistory.countIt(it.outcome).float / eventCount.float
  else:
    0.0

  if eventCount >= config.minimumEventsForTrust and successRate >= 0.9 and newScore >= 0.8:
    actor.reputation = ReputationHighlyTrusted
  elif eventCount >= (config.minimumEventsForTrust div 2) and successRate >= 0.8 and newScore >= 0.7:
    actor.reputation = ReputationTrusted
  elif successRate >= 0.6 and newScore >= 0.5:
    actor.reputation = ReputationLimited
  elif successRate < 0.4 or newScore < 0.3:
    actor.reputation = ReputationUntrusted
  else:
    actor.reputation = ReputationUnknown

proc newCommunityReputationSystem*(config: ReputationConfig): CommunityReputationSystem =
  ## Create a new community reputation system
  CommunityReputationSystem(
    actors: initTable[string, CommunityActor](),
    verificationEvents: @[],
    config: config
  )

proc getDefaultReputationConfig*(): ReputationConfig =
  ## Get default reputation configuration
  ReputationConfig(
    enableCommunityReputation: true,
    reputationDecayDays: 365,       # 1 year
    minimumEventsForTrust: 10,
    verificationWeight: 1.0,
    buildWeight: 0.8,
    reportWeight: 1.2
  )

proc addActor*(system: var CommunityReputationSystem, actor: CommunityActor) =
  ## Add an actor to the reputation system
  system.actors[actor.actorId] = actor

proc getActor*(system: CommunityReputationSystem, actorId: string): Option[CommunityActor] =
  ## Get an actor from the reputation system
  if actorId in system.actors:
    return some(system.actors[actorId])
  return none(CommunityActor)

proc recordVerificationEvent*(system: var CommunityReputationSystem, actorId: string,
                             event: VerificationEvent) =
  ## Record a verification event for an actor
  system.verificationEvents.add(event)

  # Update actor if they exist
  if actorId in system.actors:
    system.actors[actorId].addVerificationEvent(event)
    system.actors[actorId].updateActorReputation(system.config)
  else:
    # Create new actor
    var newActor = newCommunityActor(actorId, actorId)
    newActor.addVerificationEvent(event)
    newActor.updateActorReputation(system.config)
    system.actors[actorId] = newActor

# =============================================================================
# Trust Policy Rules and Evaluation
# =============================================================================

proc newTrustPolicyRule*(ruleId: string, name: string, condition: string,
                        action: string, priority: int = 100): TrustPolicyRule =
  ## Create a new trust policy rule
  TrustPolicyRule(
    ruleId: ruleId,
    name: name,
    description: "",
    condition: condition,
    action: action,
    priority: priority,
    enabled: true
  )

proc newTrustPolicySet*(policyId: string, name: string): TrustPolicySet =
  ## Create a new trust policy set
  TrustPolicySet(
    policyId: policyId,
    name: name,
    description: "",
    version: "1.0",
    rules: @[],
    defaultAction: "deny",
    created: now(),
    lastModified: now()
  )

proc addRule*(policySet: var TrustPolicySet, rule: TrustPolicyRule) =
  ## Add a rule to a policy set
  policySet.rules.add(rule)
  policySet.lastModified = now()

proc evaluateCondition*(condition: string, context: JsonNode): bool =
  ## Evaluate a policy condition against context
  # This is a simplified condition evaluator
  # In a real implementation, this would be a proper expression parser

  try:
    # Simple condition patterns
    if condition.contains("trust_score >="):
      let threshold = condition.split(">=")[1].strip().parseFloat()
      return context["trust_score"].getFloat() >= threshold

    elif condition.contains("reputation =="):
      let requiredRep = condition.split("==")[1].strip().replace("\"", "")
      return context["reputation"].getStr() == requiredRep

    elif condition.contains("source_type =="):
      let requiredSource = condition.split("==")[1].strip().replace("\"", "")
      return context["source_type"].getStr() == requiredSource

    elif condition.contains("has_signature"):
      return context["has_signature"].getBool()

    elif condition.contains("actor_trusted"):
      return context["actor_reputation"].getStr() in ["trusted", "highly_trusted"]

    else:
      # Unknown condition - default to false for safety
      return false

  except:
    return false

proc evaluatePolicy*(policySet: TrustPolicySet, context: JsonNode): tuple[action: string, rule: string] =
  ## Evaluate a policy set against context
  # Sort rules by priority (higher priority first)
  let sortedRules = policySet.rules.sortedByIt(-it.priority)

  for rule in sortedRules:
    if rule.enabled and evaluateCondition(rule.condition, context):
      return (rule.action, rule.ruleId)

  # No rules matched - return default action
  return (policySet.defaultAction, "default")

# =============================================================================
# Trust Policy Manager
# =============================================================================

proc newTrustPolicyManager*(config: TrustPolicyConfig): TrustPolicyManager =
  ## Create a new trust policy manager
  let reputationConfig = getDefaultReputationConfig()
  let reputationSystem = newCommunityReputationSystem(reputationConfig)

  TrustPolicyManager(
    policies: initTable[string, TrustPolicySet](),
    activePolicyId: "",
    reputationSystem: reputationSystem,
    config: config
  )

proc getDefaultTrustPolicyConfig*(): TrustPolicyConfig =
  ## Get default trust policy configuration
  TrustPolicyConfig(
    enablePolicyEnforcement: true,
    policyStorePath: "/etc/nimpak/trust-policies",
    reputationStorePath: "/var/lib/nimpak/reputation",
    autoUpdateReputation: true,
    requireApprovalThreshold: 0.5
  )

proc addPolicy*(manager: var TrustPolicyManager, policy: TrustPolicySet) =
  ## Add a policy to the manager
  manager.policies[policy.policyId] = policy

proc setActivePolicy*(manager: var TrustPolicyManager, policyId: string): bool =
  ## Set the active policy
  if policyId in manager.policies:
    manager.activePolicyId = policyId
    return true
  return false

proc getActivePolicy*(manager: TrustPolicyManager): Option[TrustPolicySet] =
  ## Get the currently active policy
  if manager.activePolicyId != "" and manager.activePolicyId in manager.policies:
    return some(manager.policies[manager.activePolicyId])
  return none(TrustPolicySet)

proc evaluatePackageTrust*(manager: TrustPolicyManager, chain: ProvenanceChain): tuple[action: string, rule: string, reason: string] =
  ## Evaluate package trust using active policy
  let policyOpt = manager.getActivePolicy()
  if policyOpt.isNone():
    return ("allow", "no_policy", "No active trust policy")

  let policy = policyOpt.get()

  # Build evaluation context
  var context = %*{
    "package_id": chain.packageId,
    "version": chain.version,
    "trust_score": chain.trustScore,
    "source_type": $chain.source,
    "has_signature": chain.steps.anyIt(it.signature.isSome()),
    "verification_errors": chain.verificationErrors.len,
    "step_count": chain.steps.len
  }

  # Add actor reputation if available
  if chain.steps.len > 0:
    let firstActor = chain.steps[0].actor
    let actorOpt = manager.reputationSystem.getActor(firstActor)
    if actorOpt.isSome():
      let actor = actorOpt.get()
      context["actor_reputation"] = %($actor.reputation)
      context["actor_trust_score"] = %actor.trustScore
    else:
      context["actor_reputation"] = %"unknown"
      context["actor_trust_score"] = %0.5

  # Evaluate policy
  let (action, ruleId) = evaluatePolicy(policy, context)

  let reason = case action:
    of "allow": "Package meets trust policy requirements"
    of "deny": fmt"Package denied by policy rule: {ruleId}"
    of "warn": fmt"Package allowed with warning from rule: {ruleId}"
    of "require_approval": fmt"Package requires manual approval due to rule: {ruleId}"
    else: fmt"Unknown policy action: {action}"

  return (action, ruleId, reason)

# =============================================================================
# Default Trust Policies
# =============================================================================

proc createDefaultTrustPolicies*(): seq[TrustPolicySet] =
  ## Create default trust policy sets
  var policies: seq[TrustPolicySet] = @[]

  # Strict policy
  var strictPolicy = newTrustPolicySet("strict", "Strict Security Policy")
  strictPolicy.description = "High security policy requiring signatures and high trust scores"
  strictPolicy.defaultAction = "deny"

  strictPolicy.addRule(newTrustPolicyRule("strict_trust", "High Trust Required",
    "trust_score >= 0.8", "allow", 100))
  strictPolicy.addRule(newTrustPolicyRule("strict_signature", "Signature Required",
    "has_signature", "allow", 90))
  strictPolicy.addRule(newTrustPolicyRule("strict_actor", "Trusted Actor Required",
    "actor_trusted", "allow", 80))

  policies.add(strictPolicy)

  # Balanced policy
  var balancedPolicy = newTrustPolicySet("balanced", "Balanced Security Policy")
  balancedPolicy.description = "Balanced policy allowing most packages with warnings"
  balancedPolicy.defaultAction = "warn"

  balancedPolicy.addRule(newTrustPolicyRule("balanced_high_trust", "High Trust Auto-Allow",
    "trust_score >= 0.7", "allow", 100))
  balancedPolicy.addRule(newTrustPolicyRule("balanced_low_trust", "Low Trust Deny",
    "trust_score < 0.3", "deny", 90))
  balancedPolicy.addRule(newTrustPolicyRule("balanced_grafted", "Grafted Package Warning",
    "source_type == \"grafted\"", "warn", 50))

  policies.add(balancedPolicy)

  # Permissive policy
  var permissivePolicy = newTrustPolicySet("permissive", "Permissive Policy")
  permissivePolicy.description = "Permissive policy allowing most packages"
  permissivePolicy.defaultAction = "allow"

  permissivePolicy.addRule(newTrustPolicyRule("permissive_untrusted", "Block Untrusted",
    "actor_reputation == \"untrusted\"", "deny", 100))

  policies.add(permissivePolicy)

  return policies

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

proc nipTrustCommand*(args: seq[string]): CommandResult =
  ## Implement nip trust command for trust policy management
  try:
    if args.len == 0:
      return errorResult("Usage: nip trust <policy|actor|reputation> [options]")

    let subcommand = args[0].toLower()

    case subcommand:
    of "policy":
      if args.len < 2:
        return errorResult("Usage: nip trust policy <list|set|show> [policy_id]")

      let config = getDefaultTrustPolicyConfig()
      var manager = newTrustPolicyManager(config)

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

      case args[1].toLower():
      of "list":
        let policies = manager.policies.values.toSeq
        let policyData = %*{
          "active_policy": manager.activePolicyId,
          "available_policies": policies.mapIt(%*{
            "policy_id": it.policyId,
            "name": it.name,
            "description": it.description,
            "rule_count": it.rules.len,
            "default_action": it.defaultAction
          })
        }
        outputData(policyData, "Trust Policies")
        return successResult(fmt"Found {policies.len} trust policies")

      of "set":
        if args.len < 3:
          return errorResult("Usage: nip trust policy set <policy_id>")

        let policyId = args[2]
        if manager.setActivePolicy(policyId):
          return successResult(fmt"Active trust policy set to: {policyId}")
        else:
          return errorResult(fmt"Trust policy not found: {policyId}")

      of "show":
        let policyId = if args.len > 2: args[2] else: manager.activePolicyId
        if policyId in manager.policies:
          let policy = manager.policies[policyId]
          let policyData = %*{
            "policy_id": policy.policyId,
            "name": policy.name,
            "description": policy.description,
            "version": policy.version,
            "default_action": policy.defaultAction,
            "rules": policy.rules.mapIt(%*{
              "rule_id": it.ruleId,
              "name": it.name,
              "condition": it.condition,
              "action": it.action,
              "priority": it.priority,
              "enabled": it.enabled
            })
          }
          outputData(policyData, fmt"Trust Policy: {policy.name}")
          return successResult(fmt"Trust policy details retrieved: {policyId}")
        else:
          return errorResult(fmt"Trust policy not found: {policyId}")

      else:
        return errorResult(fmt"Unknown policy command: {args[1]}")

    of "actor":
      if args.len < 2:
        return errorResult("Usage: nip trust actor <list|show> [actor_id]")

      let config = getDefaultTrustPolicyConfig()
      var manager = newTrustPolicyManager(config)

      case args[1].toLower():
      of "list":
        let actors = manager.reputationSystem.actors.values.toSeq
        let actorData = %*{
          "total_actors": actors.len,
          "actors": actors.mapIt(%*{
            "actor_id": it.actorId,
            "display_name": it.displayName,
            "reputation": $it.reputation,
            "trust_score": it.trustScore,
            "packages_built": it.packagesBuilt,
            "packages_verified": it.packagesVerified,
            "last_activity": $it.lastActivity
          })
        }
        outputData(actorData, "Community Actors")
        return successResult(fmt"Found {actors.len} community actors")

      of "show":
        if args.len < 3:
          return errorResult("Usage: nip trust actor show <actor_id>")

        let actorId = args[2]
        let actorOpt = manager.reputationSystem.getActor(actorId)
        if actorOpt.isSome():
          let actor = actorOpt.get()
          let actorData = %*{
            "actor_id": actor.actorId,
            "display_name": actor.displayName,
            "reputation": $actor.reputation,
            "trust_score": actor.trustScore,
            "packages_built": actor.packagesBuilt,
            "packages_verified": actor.packagesVerified,
            "last_activity": $actor.lastActivity,
            "public_keys": actor.publicKeys,
            "verification_history": actor.verificationHistory.mapIt(%*{
              "timestamp": $it.timestamp,
              "package_id": it.packageId,
              "event_type": it.eventType,
              "outcome": it.outcome
            })
          }
          outputData(actorData, fmt"Actor: {actor.displayName}")
          return successResult(fmt"Actor details retrieved: {actorId}")
        else:
          return errorResult(fmt"Actor not found: {actorId}")

      else:
        return errorResult(fmt"Unknown actor command: {args[1]}")

    else:
      return errorResult(fmt"Unknown trust subcommand: {subcommand}")

  except Exception as e:
    return errorResult(fmt"Trust command failed: {e.msg}")

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

export ReputationLevel, CommunityActor, VerificationEvent
export TrustPolicyRule, TrustPolicySet, CommunityReputationSystem
export TrustPolicyManager, TrustPolicyConfig, ReputationConfig
export newCommunityActor, addVerificationEvent, calculateActorTrustScore
export newTrustPolicyRule, newTrustPolicySet, evaluatePolicy
export newTrustPolicyManager, getDefaultTrustPolicyConfig
export evaluatePackageTrust, createDefaultTrustPolicies
export nipTrustCommand