## nimpak/security/revocation_manager.nim
## Key revocation and rollover management system
##
## This module implements comprehensive key revocation list (KRL) management
## and automated key rollover functionality for Task 11.1d.

import std/[times, json, os, strutils, strformat, options, tables, sequtils, random]
import ../types_fixed
import event_logger

type
  RevocationListFormat* = enum
    FormatOpenSSH = "openssh"
    FormatGPG = "gpg"
    FormatNexus = "nexus"

  RevocationEntry* = object
    keyId*: string
    reason*: RevocationReason
    reasonText*: string
    revocationDate*: DateTime
    supersededBy*: Option[string]
    emergencyRevocation*: bool
    gracePeriodEnd*: Option[DateTime]
    forensicMetadata*: JsonNode

  KeyRevocationList* = object
    version*: string
    issuer*: string
    issuedDate*: DateTime
    nextUpdate*: DateTime
    format*: RevocationListFormat
    entries*: seq[RevocationEntry]
    signature*: Option[string]
    distributionUrls*: seq[string]

  RolloverPolicy* = object
    algorithm*: string            # "ed25519", "dilithium", etc.
    keySize*: int                 # Key size in bits
    overlapPeriod*: Duration      # How long old and new keys are both valid
    gracePeriod*: Duration        # Grace period for client updates
    autoRolloverInterval*: Duration # Automatic rollover frequency
    emergencyRolloverEnabled*: bool
    quantumResistant*: bool

  KeyRolloverPlan* = object
    oldKeyId*: string
    newKeyId*: string
    rolloverType*: string         # "scheduled", "emergency", "quantum-transition"
    startDate*: DateTime
    completionDate*: DateTime
    overlapPeriod*: Duration
    affectedRepositories*: seq[string]
    migrationSteps*: seq[string]
    rollbackPlan*: seq[string]

  RevocationManager* = object
    crlPath*: string              # Path to local CRL storage
    casStore*: string             # CAS store for tamper evidence
    distributionUrls*: seq[string] # URLs for CRL distribution
    policies*: Table[string, RolloverPolicy] # Per-algorithm policies
    logger*: SecurityEventLogger  # Event logger for audit trail
    emergencyContacts*: seq[string] # Emergency notification contacts

# =============================================================================
# Revocation List Management
# =============================================================================

proc newRevocationManager*(crlPath: string, casStore: string,
                          logger: SecurityEventLogger): RevocationManager =
  ## Create a new revocation manager
  result = RevocationManager(
    crlPath: crlPath,
    casStore: casStore,
    distributionUrls: @[],
    policies: initTable[string, RolloverPolicy](),
    logger: logger,
    emergencyContacts: @[]
  )

  # Create CRL directory if it doesn't exist
  createDir(crlPath)

proc loadRevocationList*(manager: RevocationManager, format: RevocationListFormat): Result[KeyRevocationList, string] =
  ##revocation list from storage
  let crlFile = manager.crlPath / fmt"revocation_list.{format}"

  if not fileExists(crlFile):
    return err[KeyRevocationList, string]("Revocation list not found")

  try:
    let content = readFile(crlFile)
    let jsonData = parseJson(content)

    var entries: seq[RevocationEntry] = @[]
    for entryJson in jsonData["entries"]:
      let entry = RevocationEntry(
        keyId: entryJson["key_id"].getStr(),
        reason: RevocationReason(entryJson["reason"].getInt(0)),
        reasonText: entryJson["reason_text"].getStr(),
        revocationDate: parse(entryJson["revocation_date"].getStr(), "yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
        supersededBy: if entryJson.hasKey("superseded_by"): some(entryJson["superseded_by"].getStr()) else: none(string),
        emergencyRevocation: entryJson["emergency_revocation"].getBool(),
        gracePeriodEnd: if entryJson.hasKey("grace_period_end"): some(parse(entryJson["grace_period_end"].getStr(), "yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'")) else: none(DateTime),
        forensicMetadata: entryJson.getOrDefault("forensic_metadata")
      )
      entries.add(entry)

    let crl = KeyRevocationList(
      version: jsonData["version"].getStr(),
      issuer: jsonData["issuer"].getStr(),
      issuedDate: parse(jsonData["issued_date"].getStr(), "yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
      nextUpdate: parse(jsonData["next_update"].getStr(), "yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
      format: format,
      entries: entries,
      signature: if jsonData.hasKey("signature"): some(jsonData["signature"].getStr()) else: none(string),
      distributionUrls: jsonData["distribution_urls"].mapIt(it.getStr())
    )

    return Result[KeyRevocationList, string](isOk: true, okValue: crl)
  except:
    return err[KeyRevocationList, string](fmt"Failed to load revocation list: {getCurrentExceptionMsg()}")

proc saveRevocationList*(manager: RevocationManager, crl: KeyRevocationList): Result[void, string] =
  ## Save revocation list to storage and CAS
  let crlFile = manager.crlPath / fmt"revocation_list.{crl.format}"

  try:
    let entriesJson = newJArray()
    for entry in crl.entries:
      let entryJson = %*{
        "key_id": entry.keyId,
        "reason": entry.reason.int,
        "reason_text": entry.reasonText,
        "revocation_date": entry.revocationDate.format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
        "emergency_revocation": entry.emergencyRevocation,
        "forensic_metadata": entry.forensicMetadata
      }

      if entry.supersededBy.isSome():
        entryJson["superseded_by"] = %entry.supersededBy.get()

      if entry.gracePeriodEnd.isSome():
        entryJson["grace_period_end"] = %entry.gracePeriodEnd.get().format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'")

      entriesJson.add(entryJson)

    let crlJson = %*{
      "version": crl.version,
      "issuer": crl.issuer,
      "issued_date": crl.issuedDate.format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
      "next_update": crl.nextUpdate.format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
      "format": $crl.format,
      "entries": entriesJson,
      "distribution_urls": crl.distributionUrls
    }

    if crl.signature.isSome():
      crlJson["signature"] = %crl.signature.get()

    # Write to file
    writeFile(crlFile, crlJson.pretty())

    # Store in CAS for tamper evidence
    let crlHash = fmt"blake3-{crlJson.pretty().len:08x}"  # TODO: Use actual BLAKE3
    let casPath = manager.casStore / crlHash
    writeFile(casPath, crlJson.pretty())

    # Log CRL update
    var logger = manager.logger
    logger.logCRLUpdate("local", crl.entries.mapIt(it.keyId), true)

    return Result[void, string](isOk: true)
  except:
    return err[void, string](fmt"Failed to save revocation list: {getCurrentExceptionMsg()}")

# =============================================================================
# Emergency Revocation
# =============================================================================

proc emergencyRevocation*(manager: var RevocationManager, keyId: string,
                         reason: string, affectedPackages: seq[string]): Result[void, string] =
  ## Perform emergency key revocation with immediate CRL distribution

  # Load current CRL
  var crlResult = manager.loadRevocationList(FormatNexus)
  var crl: KeyRevocationList

  if crlResult.isErr():
    # Create new CRL if none exists
    crl = KeyRevocationList(
      version: "1.0",
      issuer: "nexus-security",
      issuedDate: now().utc(),
      nextUpdate: now().utc() + initDuration(days = 7),
      format: FormatNexus,
      entries: @[],
      signature: none(string),
      distributionUrls: manager.distributionUrls
    )
  else:
    crl = crlResult.get()

  # Create revocation entry
  let revocationEntry = RevocationEntry(
    keyId: keyId,
    reason: ReasonKeyCompromise,
    reasonText: reason,
    revocationDate: now().utc(),
    supersededBy: none(string),
    emergencyRevocation: true,
    gracePeriodEnd: none(DateTime),  # No grace period for emergency revocation
    forensicMetadata: %*{
      "affected_packages": affectedPackages,
      "response_actions": ["immediate_crl_update", "package_re_signing", "client_notification"],
      "incident_id": "EMRG-" & now().format("yyyy-MM-dd") & "-" & keyId[0..7]
    }
  )

  # Add to CRL
  crl.entries.add(revocationEntry)
  crl.issuedDate = now().utc()

  # Save updated CRL
  let saveResult = manager.saveRevocationList(crl)
  if saveResult.isErr():
    return err[void, string](fmt"Failed to save emergency revocation: {saveResult.errValue}")

  # Log emergency revocation
  var logger = manager.logger
  logger.logEmergencyRevocation(keyId, reason, affectedPackages)

  # TODO: Distribute CRL to all distribution URLs
  # TODO: Send emergency notifications

  return Result[void, string](isOk: true)

# =============================================================================
# Scheduled Rollover
# =============================================================================

proc scheduleKeyRollover*(manager: var RevocationManager, oldKeyId: string,
                         algorithm: string, repositories: seq[string]): Result[KeyRolloverPlan, string] =
  ## Schedule a key rollover with overlap period

  if not manager.policies.hasKey(algorithm):
    return err[KeyRolloverPlan, string](fmt"No rollover policy defined for algorithm: {algorithm}")

  let policy = manager.policies[algorithm]
  let newKeyId = algorithm & "-" & now().format("yyyy-MM-dd") & "-" & $rand(9999)

  let rolloverPlan = KeyRolloverPlan(
    oldKeyId: oldKeyId,
    newKeyId: newKeyId,
    rolloverType: "scheduled",
    startDate: now().utc(),
    completionDate: now().utc() + policy.overlapPeriod + policy.gracePeriod,
    overlapPeriod: policy.overlapPeriod,
    affectedRepositories: repositories,
    migrationSteps: @[
      "Generate new key pair",
      "Distribute new public key",
      "Begin signing with new key",
      "Maintain old key validation",
      "Revoke old key after grace period"
    ],
    rollbackPlan: @[
      "Stop using new key",
      "Revert to old key signing",
      "Remove new key from distribution",
      "Extend old key validity"
    ]
  )

  # Log scheduled rollover
  var logger = manager.logger
  logger.logScheduledRollover(oldKeyId, newKeyId, repositories)

  return Result[KeyRolloverPlan, string](isOk: true, okValue: rolloverPlan)

# =============================================================================
# Quantum-Resistant Transition
# =============================================================================

proc planQuantumTransition*(manager: var RevocationManager,
                           classicalKeyId: string,
                           quantumAlgorithm: string): Result[KeyRolloverPlan, string] =
  ## Plan transition from classical to quantum-resistant cryptography

  let newKeyId = quantumAlgorithm & "-" & now().format("yyyy-MM-dd") & "-QR"

  let transitionPlan = KeyRolloverPlan(
    oldKeyId: classicalKeyId,
    newKeyId: newKeyId,
    rolloverType: "quantum-transition",
    startDate: now().utc(),
    completionDate: now().utc() + initDuration(days = 90),  # Longer transition period
    overlapPeriod: initDuration(days = 60),
    affectedRepositories: @["all"],  # Quantum transition affects all repositories
    migrationSteps: @[
      "Generate quantum-resistant key pair",
      "Implement hybrid signature validation",
      "Begin dual-signing packages",
      "Update client verification logic",
      "Gradually phase out classical signatures",
      "Complete quantum-only signing"
    ],
    rollbackPlan: @[
      "Revert to classical-only signing",
      "Remove quantum signature validation",
      "Extend classical key validity",
      "Notify clients of rollback"
    ]
  )

  # Log quantum transition planning
  var logger = manager.logger
  let metadata = %*{
    "transition_type": "classical_to_quantum",
    "classical_algorithm": "ed25519",  # Assume current classical algorithm
    "quantum_algorithm": quantumAlgorithm,
    "transition_duration": "90d",
    "hybrid_period": "60d"
  }

  var event = createSecurityEvent(EventKeyRollover, SeverityInfo, "quantum-transition",
                                 fmt"Quantum transition planned: {classicalKeyId} → {newKeyId}", metadata)
  logger.logSecurityEvent(event)

  return Result[KeyRolloverPlan, string](isOk: true, okValue: transitionPlan)

# =============================================================================
# Grace Period Management
# =============================================================================

proc setRolloverPolicy*(manager: var RevocationManager, algorithm: string, policy: RolloverPolicy) =
  ## Set rollover policy for a specific algorithm
  manager.policies[algorithm] = policy

proc getDefaultPolicies*(): Table[string, RolloverPolicy] =
  ## Get default rollover policies for common algorithms
  result = initTable[string, RolloverPolicy]()

  # Ed25519 policy
  result["ed25519"] = RolloverPolicy(
    algorithm: "ed25519",
    keySize: 256,
    overlapPeriod: initDuration(days = 30),
    gracePeriod: initDuration(days = 7),
    autoRolloverInterval: initDuration(days = 365),
    emergencyRolloverEnabled: true,
    quantumResistant: false
  )

  # Dilithium policy (post-quantum)
  result["dilithium"] = RolloverPolicy(
    algorithm: "dilithium",
    keySize: 2048,
    overlapPeriod: initDuration(days = 60),  # Longer overlap for PQ transition
    gracePeriod: initDuration(days = 14),
    autoRolloverInterval: initDuration(days = 730),  # Less frequent rollover
    emergencyRolloverEnabled: true,
    quantumResistant: true
  )

proc checkGracePeriods*(manager: var RevocationManager): Result[seq[string], string] =
  ## Check for expired grace periods and finalize revocations
  var expiredKeys: seq[string] = @[]

  let crlResult = manager.loadRevocationList(FormatNexus)
  if crlResult.isErr():
    return Result[seq[string], string](isOk: true, okValue: expiredKeys)  # No CRL means no expired keys

  var crl = crlResult.get()
  let currentTime = now().utc()

  for i, entry in crl.entries:
    if entry.gracePeriodEnd.isSome() and currentTime > entry.gracePeriodEnd.get():
      expiredKeys.add(entry.keyId)

      # Log grace period expiration
      var logger = manager.logger
      let metadata = %*{
        "key_id": entry.keyId,
        "grace_period_end": entry.gracePeriodEnd.get().format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
        "final_revocation": true
      }

      var event = createSecurityEvent(EventKeyExpiration, SeverityWarning, "grace-period-manager",
                                     fmt"Grace period expired for key {entry.keyId}", metadata)
      logger.logSecurityEvent(event)

  return Result[seq[string], string](isOk: true, okValue: expiredKeys)

# =============================================================================
# CRL Distribution and Fetching
# =============================================================================

proc distributeCRL*(manager: RevocationManager, crl: KeyRevocationList): Result[void, string] =
  ## Distribute CRL to all configured distribution URLs
  # TODO: Implement HTTP/HTTPS distribution
  # TODO: Implement peer-to-peer distribution for air-gapped systems
  # TODO: Implement emergency broadcast system

  var logger = manager.logger
  logger.logCRLUpdate("distributed", crl.entries.mapIt(it.keyId), true)

  return Result[void, string](isOk: true)

proc fetchRemoteCRL*(manager: RevocationManager, url: string): Result[KeyRevocationList, string] =
  ## Fetch CRL from remote URL
  # TODO: Implement HTTP/HTTPS fetching
  # TODO: Implement signature verification
  # TODO: Implement offline CRL support

  return err[KeyRevocationList, string]("Remote CRL fetching not yet implemented")

# =============================================================================
# Offline Revocation Support
# =============================================================================

proc createOfflineRevocationPackage*(manager: RevocationManager,
                                    keyIds: seq[string]): Result[string, string] =
  ## Create offline revocation package for air-gapped systems
  let dateStr = now().format("yyyy-MM-dd")
  let packagePath = manager.crlPath / fmt"offline_revocation_{dateStr}.pkg"

  let offlinePackage = %*{
    "version": "1.0",
    "created": now().utc().format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'"),
    "revoked_keys": keyIds,
    "instructions": [
      "Copy this package to air-gapped system",
      "Run: nip security import-revocation offline_revocation.pkg",
      "Verify revocation with: nip verify --check-revocation"
    ]
  }

  try:
    writeFile(packagePath, offlinePackage.pretty())
    return Result[string, string](isOk: true, okValue: packagePath)
  except:
    return err[string, string](fmt"Failed to create offline package: {getCurrentExceptionMsg()}")

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

export RevocationListFormat, RevocationEntry, KeyRevocationList
export RolloverPolicy, KeyRolloverPlan, RevocationManager
export newRevocationManager, loadRevocationList, saveRevocationList
export emergencyRevocation, scheduleKeyRollover, planQuantumTransition
export setRolloverPolicy, getDefaultPolicies, checkGracePeriods
export distributeCRL, fetchRemoteCRL, createOfflineRevocationPackage