## nimpak/security/keyring_manager.nim
## Keyring management system with revocation infrastructure
##
## This module implements comprehensive key management including:
## - System/user/repository keyring loading and merging
## - Key Revocation List (KRL) management
## - CLI commands: nip key import/export/list/revoke/rollover
## - Emergency revocation and automated rollover procedures

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

type
  KeyringType* = enum
    KeyringSystem = "system"      # /etc/nexus/keyrings/
    KeyringUser = "user"          # ~/.config/nexus/keyrings/
    KeyringRepository = "repository"  # Per-repository keys

  KeyStatus* = enum
    KeyActive = "active"          # Currently valid for signing/verification
    KeyDeprecated = "deprecated"  # Valid but scheduled for replacement
    KeyRevoked = "revoked"        # Immediately invalid, blacklisted
    KeyExpired = "expired"        # Naturally expired
    KeySuperseded = "superseded"  # Replaced by newer key

  KeyProvenance* = object
    addedBy*: string             # Who added the key
    addedAt*: times.DateTime     # When key was added
    source*: string              # Where key came from
    reason*: string              # Why key was added
    verifiedBy*: seq[string]     # Who verified the key

  ManagedPublicKey* = object
    # Core key data (from signature_verifier)
    publicKey*: PublicKey

    # Management metadata
    status*: KeyStatus
    provenance*: KeyProvenance
    lastUsed*: times.DateTime
    usageCount*: int64

    # Revocation data
    revokedAt*: Option[times.DateTime]
    revokedBy*: string
    revocationReason*: RevocationReason
    supersededBy*: Option[string]

    # Rollover data
    rolloverScheduled*: Option[times.DateTime]
    rolloverNotified*: bool
    gracePeriodEnd*: Option[times.DateTime]

  KeyRevocationEntry* = object
    keyId*: string
    revocationDate*: times.DateTime
    reasonCode*: RevocationReason
    reasonText*: string
    serialNumber*: string
    supersededBy*: Option[string]
    signature*: Option[string]    # CRL signature

  KeyRevocationList* = object
    version*: string
    issuer*: string
    thisUpdate*: times.DateTime
    nextUpdate*: times.DateTime
    revokedKeys*: seq[KeyRevocationEntry]
    signature*: DigitalSignature

  Keyring* = object
    keyringType*: KeyringType
    name*: string                # Keyring identifier
    path*: string                # File path
    keys*: Table[string, ManagedPublicKey]  # keyId -> key
    metadata*: JsonNode
    lastModified*: times.DateTime
    checksum*: string

  KeyringManager* = object
    systemKeyrings*: seq[Keyring]
    userKeyrings*: seq[Keyring]
    repositoryKeyrings*: Table[string, Keyring]  # repoId -> keyring
    revocationLists*: Table[string, KeyRevocationList]  # issuer -> CRL
    config*: KeyringConfig

  KeyringConfig* = object
    systemKeyringPath*: string
    userKeyringPath*: string
    crlUpdateInterval*: int      # Seconds
    autoFetchCRL*: bool
    requireSignedCRL*: bool
    gracePeriodDefault*: int     # Days
    rolloverWarningDays*: int

# =============================================================================
# Keyring Manager Initialization
# =============================================================================

proc newKeyringManager*(config: KeyringConfig): KeyringManager =
  ## Create a new keyring manager
  result = KeyringManager(
    systemKeyrings: @[],
    userKeyrings: @[],
    repositoryKeyrings: initTable[string, Keyring](),
    revocationLists: initTable[string, KeyRevocationList](),
    config: config
  )

proc getDefaultKeyringConfig*(): KeyringConfig =
  ## Get default keyring configuration
  KeyringConfig(
    systemKeyringPath: "/etc/nexus/keyrings",
    userKeyringPath: getHomeDir() / ".config" / "nexus" / "keyrings",
    crlUpdateInterval: 3600,     # 1 hour
    autoFetchCRL: true,
    requireSignedCRL: true,
    gracePeriodDefault: 7,       # 7 days
    rolloverWarningDays: 30
  )

# =============================================================================
# Keyring Loading and Management
# =============================================================================

proc loadKeyring*(path: string, keyringType: KeyringType): Keyring =
  ## Load a keyring from file
  if not fileExists(path):
    raise newException(IOError, fmt"Keyring file not found: {path}")

  let content = readFile(path)
  let keyringJson = parseJson(content)

  var keyring = Keyring(
    keyringType: keyringType,
    name: keyringJson["name"].getStr(),
    path: path,
    keys: initTable[string, ManagedPublicKey](),
    metadata: keyringJson.getOrDefault("metadata"),
    lastModified: getFileInfo(path).lastWriteTime.utc(),
    checksum: ""  # TODO: Calculate actual checksum
  )

  # Load keys
  for keyData in keyringJson["keys"]:
    # TODO: Implement proper key parsing
    let managedKey = ManagedPublicKey(
      publicKey: PublicKey(
        keyId: keyData["key_id"].getStr(),
        algorithm: parseEnum[SignatureAlgorithm](keyData["algorithm"].getStr()),
        keyData: keyData["key_data"].getStr(),
        validFrom: now().utc(),
        validUntil: now().utc() + initDuration(days = 365),
        revoked: false
      ),
      status: KeyActive,
      provenance: KeyProvenance(
        source: keyData.getOrDefault("source").getStr("unknown"),
        addedAt: now().utc(),
        addedBy: "system",
        reason: "keyring_import",
        verifiedBy: @[]
      ),
      lastUsed: now().utc(),
      usageCount: 0,
      revokedAt: none(DateTime),
      revokedBy: ""
    )
    keyring.keys[managedKey.publicKey.keyId] = managedKey

  return keyring

proc saveKeyring*(keyring: Keyring) =
  ## Save keyring to file
  let keyringJson = %*{
    "version": "1.0",
    "name": keyring.name,
    "type": $keyring.keyringType,
    "created": $now().utc(),
    "keys": keyring.keys.values.toSeq.mapIt(%*{
      "key_id": it.publicKey.keyId,
      "algorithm": $it.publicKey.algorithm,
      "key_data": it.publicKey.keyData,
      "status": $it.status
    }),
    "metadata": keyring.metadata
  }

  # Ensure directory exists
  createDir(parentDir(keyring.path))

  # Write with atomic operation
  let tempPath = keyring.path & ".tmp"
  writeFile(tempPath, keyringJson.pretty())
  moveFile(tempPath, keyring.path)

proc loadAllKeyrings*(manager: var KeyringManager) =
  ## Load all keyrings (system, user, repository)
  # Load system keyrings
  if dirExists(manager.config.systemKeyringPath):
    for file in walkFiles(manager.config.systemKeyringPath / "*.json"):
      try:
        let keyring = loadKeyring(file, KeyringSystem)
        manager.systemKeyrings.add(keyring)
      except Exception as e:
        echo fmt"Warning: Failed to load system keyring {file}: {e.msg}"

  # Load user keyrings
  if dirExists(manager.config.userKeyringPath):
    for file in walkFiles(manager.config.userKeyringPath / "*.json"):
      try:
        let keyring = loadKeyring(file, KeyringUser)
        manager.userKeyrings.add(keyring)
      except Exception as e:
        echo fmt"Warning: Failed to load user keyring {file}: {e.msg}"

  # TODO: Load repository-specific keyrings

# =============================================================================
# Key Management Operations
# =============================================================================

proc addKey*(manager: var KeyringManager, key: PublicKey, keyringType: KeyringType,
            addedBy: string, source: string, reason: string): bool =
  ## Add a key to the appropriate keyring
  let provenance = KeyProvenance(
    addedBy: addedBy,
    addedAt: now().utc(),
    source: source,
    reason: reason,
    verifiedBy: @[]
  )

  let managedKey = ManagedPublicKey(
    publicKey: key,
    status: KeyActive,
    provenance: provenance,
    lastUsed: default(times.DateTime),
    usageCount: 0,
    revokedAt: none(times.DateTime),
    revokedBy: "",
    revocationReason: ReasonUnspecified,
    supersededBy: none(string),
    rolloverScheduled: none(times.DateTime),
    rolloverNotified: false,
    gracePeriodEnd: none(times.DateTime)
  )

  # Find appropriate keyring or create new one
  var targetKeyring: ptr Keyring = nil

  case keyringType:
  of KeyringSystem:
    if manager.systemKeyrings.len == 0:
      let newKeyring = Keyring(
        keyringType: KeyringSystem,
        name: "system-default",
        path: manager.config.systemKeyringPath / "system-default.json",
        keys: initTable[string, ManagedPublicKey](),
        metadata: newJNull(),
        lastModified: now(),
        checksum: ""
      )
      manager.systemKeyrings.add(newKeyring)
    targetKeyring = manager.systemKeyrings[0].addr

  of KeyringUser:
    if manager.userKeyrings.len == 0:
      let newKeyring = Keyring(
        keyringType: KeyringUser,
        name: "user-default",
        path: manager.config.userKeyringPath / "user-default.json",
        keys: initTable[string, ManagedPublicKey](),
        metadata: newJNull(),
        lastModified: now(),
        checksum: ""
      )
      manager.userKeyrings.add(newKeyring)
    targetKeyring = manager.userKeyrings[0].addr

  of KeyringRepository:
    # Repository keyrings handled separately
    return false

  if targetKeyring != nil:
    targetKeyring[].keys[key.keyId] = managedKey
    saveKeyring(targetKeyring[])

    # Log key addition event
    logGlobalSecurityEvent(EventKeyGeneration, SeverityInfo, "keyring-manager",
      fmt"Key {key.keyId} added to {keyringType} keyring by {addedBy}")

    return true

  return false

proc findKey*(manager: KeyringManager, keyId: string): Option[ManagedPublicKey] =
  ## Find a key across all keyrings (system -> user -> repository precedence)
  # Search system keyrings first
  for keyring in manager.systemKeyrings:
    if keyId in keyring.keys:
      return some(keyring.keys[keyId])

  # Search user keyrings
  for keyring in manager.userKeyrings:
    if keyId in keyring.keys:
      return some(keyring.keys[keyId])

  # Search repository keyrings
  for keyring in manager.repositoryKeyrings.values:
    if keyId in keyring.keys:
      return some(keyring.keys[keyId])

  return none(ManagedPublicKey)

proc listKeys*(manager: KeyringManager, keyringType: Option[KeyringType] = none(KeyringType),
              status: Option[KeyStatus] = none(KeyStatus)): seq[ManagedPublicKey] =
  ## List keys with optional filtering
  var allKeys: seq[ManagedPublicKey] = @[]

  # Collect keys from appropriate keyrings
  if keyringType.isNone() or keyringType.get() == KeyringSystem:
    for keyring in manager.systemKeyrings:
      allKeys.add(keyring.keys.values.toSeq)

  if keyringType.isNone() or keyringType.get() == KeyringUser:
    for keyring in manager.userKeyrings:
      allKeys.add(keyring.keys.values.toSeq)

  if keyringType.isNone() or keyringType.get() == KeyringRepository:
    for keyring in manager.repositoryKeyrings.values:
      allKeys.add(keyring.keys.values.toSeq)

  # Filter by status if specified
  if status.isSome():
    allKeys = allKeys.filterIt(it.status == status.get())

  return allKeys.sortedByIt(it.publicKey.keyId)

# =============================================================================
# Key Revocation Operations
# =============================================================================

proc revokeKey*(manager: var KeyringManager, keyId: string, reason: RevocationReason,
               reasonText: string, revokedBy: string, supersededBy: Option[string] = none(string)): bool =
  ## Revoke a key immediately
  # Find the key across all keyrings
  var keyFound = false

  # Update key in system keyrings
  for keyring in manager.systemKeyrings.mitems:
    if keyId in keyring.keys:
      keyring.keys[keyId].status = KeyRevoked
      keyring.keys[keyId].revokedAt = some(now().utc())
      keyring.keys[keyId].revokedBy = revokedBy
      keyring.keys[keyId].revocationReason = reason
      keyring.keys[keyId].supersededBy = supersededBy
      saveKeyring(keyring)
      keyFound = true

  # Update key in user keyrings
  for keyring in manager.userKeyrings.mitems:
    if keyId in keyring.keys:
      keyring.keys[keyId].status = KeyRevoked
      keyring.keys[keyId].revokedAt = some(now().utc())
      keyring.keys[keyId].revokedBy = revokedBy
      keyring.keys[keyId].revocationReason = reason
      keyring.keys[keyId].supersededBy = supersededBy
      saveKeyring(keyring)
      keyFound = true

  # Update key in repository keyrings
  for keyring in manager.repositoryKeyrings.mvalues:
    if keyId in keyring.keys:
      keyring.keys[keyId].status = KeyRevoked
      keyring.keys[keyId].revokedAt = some(now().utc())
      keyring.keys[keyId].revokedBy = revokedBy
      keyring.keys[keyId].revocationReason = reason
      keyring.keys[keyId].supersededBy = supersededBy
      saveKeyring(keyring)
      keyFound = true

  if keyFound:
    # Log revocation event
    let revocationEvent = RevocationEvent(
      keyId: keyId,
      reason: reason,
      reasonText: reasonText,
      revocationDate: now().utc(),
      supersededBy: supersededBy,
      affectedPackages: @[],  # TODO: Determine affected packages
      emergencyRevocation: reason == ReasonKeyCompromise,
      responseActions: @["keyring_update", "crl_update"]
    )

    logKeyRevocation(globalSecurityLogger, revocationEvent)

    # Update CRL
    # TODO: Implement updateRevocationList function

    return true

  return false

proc scheduleKeyRollover*(manager: var KeyringManager, oldKeyId: string,
                         newKey: PublicKey, rolloverDate: times.DateTime,
                         gracePeriod: int = 7): bool =
  ## Schedule a key rollover
  let keyOpt = manager.findKey(oldKeyId)
  if keyOpt.isNone():
    return false

  # Add new key
  if not manager.addKey(newKey, KeyringUser, "system", "rollover", "Scheduled key rollover"):
    return false

  # Update old key with rollover schedule
  # TODO: Update key in appropriate keyring

  # Log rollover scheduling
  let rolloverEvent = RolloverEvent(
    oldKeyId: oldKeyId,
    newKeyId: newKey.keyId,
    rolloverType: "scheduled",
    overlapPeriod: fmt"{gracePeriod}d",
    affectedRepositories: @["all"],  # TODO: Determine affected repositories
    validationResults: %*{"scheduled": true}
  )

  logKeyRollover(globalSecurityLogger, rolloverEvent)

  return true

# =============================================================================
# Key Revocation List (CRL) Management
# =============================================================================

proc updateRevocationList*(manager: var KeyringManager, keyId: string,
                          reason: RevocationReason, reasonText: string,
                          supersededBy: Option[string]) =
  ## Update the Certificate/Key Revocation List
  let issuer = "nexusos-keyring-authority"

  # Get or create CRL
  if issuer notin manager.revocationLists:
    manager.revocationLists[issuer] = KeyRevocationList(
      version: "1.0",
      issuer: issuer,
      thisUpdate: now().utc(),
      nextUpdate: now().utc() + initDuration(days = 1),
      revokedKeys: @[],
      signature: createDigitalSignature(SigEd25519, "crl-signing-key", "placeholder-signature")
    )

  # Add revocation entry
  let revocationEntry = KeyRevocationEntry(
    keyId: keyId,
    revocationDate: now().utc(),
    reasonCode: reason,
    reasonText: reasonText,
    serialNumber: fmt"serial-{epochTime().int}",
    supersededBy: supersededBy,
    signature: none(string)
  )

  manager.revocationLists[issuer].revokedKeys.add(revocationEntry)
  manager.revocationLists[issuer].thisUpdate = now().utc()

  # Save CRL to file
  # TODO: Fix forward declaration issue

proc saveCRL*(crl: KeyRevocationList) =
  ## Save CRL to file in KDL format
  let crlPath = "/etc/nexus/crl" / fmt"{crl.issuer}.crl"
  createDir(parentDir(crlPath))

  # Create KDL format CRL
  var crlContent = fmt"""revocation_list {{
  version "{crl.version}"
  issuer "{crl.issuer}"
  this_update "{crl.thisUpdate}"
  next_update "{crl.nextUpdate}"

"""

  for entry in crl.revokedKeys:
    crlContent.add(fmt"""  revoked_key {{
    key_id "{entry.keyId}"
    revocation_date "{entry.revocationDate}"
    reason_code {entry.reasonCode.int}
    reason_text "{entry.reasonText}"
    serial_number "{entry.serialNumber}"
""")
    if entry.supersededBy.isSome():
      crlContent.add(fmt"""    superseded_by "{entry.supersededBy.get()}"
""")
    crlContent.add("  }\n\n")

  crlContent.add(fmt"""  signature {{
    algorithm "{crl.signature.algorithm}"
    key_id "{crl.signature.keyId}"
    value "{crl.signature.signature}"
  }}
}}""")

  writeFile(crlPath, crlContent)

proc loadCRL*(crlPath: string): KeyRevocationList =
  ## Load CRL from file
  # TODO: Implement KDL parsing when library is available
  # For now, return empty CRL
  KeyRevocationList(
    version: "1.0",
    issuer: "placeholder",
    thisUpdate: now().utc(),
    nextUpdate: now().utc() + initDuration(days = 1),
    revokedKeys: @[],
    signature: createDigitalSignature(SigEd25519, "placeholder", "placeholder")
  )

proc isKeyRevoked*(manager: KeyringManager, keyId: string): bool =
  ## Check if a key is revoked according to CRL
  for crl in manager.revocationLists.values:
    for entry in crl.revokedKeys:
      if entry.keyId == keyId:
        return true
  return false

# =============================================================================
# Utility Functions for Serialization
# =============================================================================

proc parseManagedPublicKey*(keyData: JsonNode): ManagedPublicKey =
  ## Parse ManagedPublicKey from JSON
  # TODO: Implement full parsing
  let publicKey = createPublicKey(
    parseEnum[SignatureAlgorithm](keyData["algorithm"].getStr()),
    keyData["key_id"].getStr(),
    keyData["key_data"].getStr(),
    keyData["valid_from"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc()),
    keyData["valid_until"].getStr().parse("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc())
  )

  ManagedPublicKey(
    publicKey: publicKey,
    status: parseEnum[KeyStatus](keyData.getOrDefault("status").getStr("active")),
    provenance: KeyProvenance(),  # TODO: Parse provenance
    lastUsed: default(times.DateTime),
    usageCount: 0,
    revokedAt: none(times.DateTime),
    revokedBy: "",
    revocationReason: ReasonUnspecified,
    supersededBy: none(string),
    rolloverScheduled: none(times.DateTime),
    rolloverNotified: false,
    gracePeriodEnd: none(times.DateTime)
  )

proc serializeManagedPublicKey*(key: ManagedPublicKey): JsonNode =
  ## Serialize ManagedPublicKey to JSON
  %*{
    "algorithm": $key.publicKey.algorithm,
    "key_id": key.publicKey.keyId,
    "key_data": key.publicKey.keyData,
    "valid_from": $key.publicKey.validFrom,
    "valid_until": $key.publicKey.validUntil,
    "status": $key.status,
    "provenance": {
      "added_by": key.provenance.addedBy,
      "added_at": $key.provenance.addedAt,
      "source": key.provenance.source,
      "reason": key.provenance.reason
    },
    "last_used": $key.lastUsed,
    "usage_count": key.usageCount,
    "revoked_at": if key.revokedAt.isSome(): $key.revokedAt.get() else: "",
    "revoked_by": key.revokedBy,
    "revocation_reason": key.revocationReason.int
  }

proc getKeyringStatistics*(manager: KeyringManager): JsonNode =
  ## Get comprehensive keyring statistics
  var totalKeys = 0
  var validKeys = 0
  var expiredKeys = 0
  var revokedKeys = 0
  var deprecatedKeys = 0
  var supersededKeys = 0

  let now = times.now()

  # Count keys across all keyrings
  for keyring in manager.systemKeyrings:
    for key in keyring.keys.values:
      totalKeys.inc()
      case key.status:
      of KeyActive:
        if key.publicKey.validUntil > now:
          validKeys.inc()
        else:
          expiredKeys.inc()
      of KeyDeprecated:
        deprecatedKeys.inc()
      of KeyRevoked:
        revokedKeys.inc()
      of KeyExpired:
        expiredKeys.inc()
      of KeySuperseded:
        supersededKeys.inc()

  for keyring in manager.userKeyrings:
    for key in keyring.keys.values:
      totalKeys.inc()
      case key.status:
      of KeyActive:
        if key.publicKey.validUntil > now:
          validKeys.inc()
        else:
          expiredKeys.inc()
      of KeyDeprecated:
        deprecatedKeys.inc()
      of KeyRevoked:
        revokedKeys.inc()
      of KeyExpired:
        expiredKeys.inc()
      of KeySuperseded:
        supersededKeys.inc()

  for keyring in manager.repositoryKeyrings.values:
    for key in keyring.keys.values:
      totalKeys.inc()
      case key.status:
      of KeyActive:
        if key.publicKey.validUntil > now:
          validKeys.inc()
        else:
          expiredKeys.inc()
      of KeyDeprecated:
        deprecatedKeys.inc()
      of KeyRevoked:
        revokedKeys.inc()
      of KeyExpired:
        expiredKeys.inc()
      of KeySuperseded:
        supersededKeys.inc()

  return %*{
    "total_keys": totalKeys,
    "valid_keys": validKeys,
    "expired_keys": expiredKeys,
    "revoked_keys": revokedKeys,
    "deprecated_keys": deprecatedKeys,
    "superseded_keys": supersededKeys
  }

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

export KeyringType, KeyStatus, KeyProvenance, ManagedPublicKey
export KeyRevocationEntry, KeyRevocationList, Keyring, KeyringManager, KeyringConfig
export newKeyringManager, getDefaultKeyringConfig
export loadKeyring, saveKeyring, loadAllKeyrings
export addKey, findKey, listKeys
export revokeKey, scheduleKeyRollover
export updateRevocationList, saveCRL, loadCRL, isKeyRevoked
export getKeyringStatistics