## NOF Overlay Fragment Format Handler (.nof)
##
## This module implements the NOF (Nexus Overlay Fragment) format for declarative
## system modifications. NOF overlays provide immutable system configuration
## changes that can be applied atomically to system generations.
##
## Format: .nof (Nexus Overlay Fragment, plain text KDL)
## - Plain-text KDL format for immutable system overlays
## - Overlay application and validation system
## - Ed25519 signature support for overlay integrity
## - Overlay conflict detection and resolution

import std/[os, json, times, strutils, sequtils, tables, options, algorithm]
import ./types_fixed
import ./formats
import ./cas

type
  NofError* = object of NimPakError
    overlayName*: string

  OverlayValidationResult* = object
    valid*: bool
    errors*: seq[ValidationError]
    warnings*: seq[string]

  OverlayOperation* = enum
    ## Types of overlay operations
    AddFile,      ## Add new file
    ModifyFile,   ## Modify existing file
    RemoveFile,   ## Remove file
    AddSymlink,   ## Add symbolic link
    RemoveSymlink,## Remove symbolic link
    SetPermissions,## Set file permissions
    AddPackage,   ## Add package to system
    RemovePackage,## Remove package from system
    SetConfig     ## Set configuration value

  OverlayModification* = object
    ## Individual overlay modification
    operation*: OverlayOperation
    target*: string           ## Target path or package name
    source*: Option[string]   ## Source path (for copies/links)
    content*: Option[string]  ## File content (for inline content)
    permissions*: Option[FilePermissions]  ## File permissions
    metadata*: JsonNode       ## Additional operation-specific metadata

const
  NOF_VERSION* = "1.0"
  MAX_OVERLAY_SIZE* = 100 * 1024 * 1024  ## 100MB maximum overlay size

# =============================================================================
# NOF Overlay Creation and Management
# =============================================================================

proc createNofOverlay*(name: string, description: string,
                      overlayConfig: OverlayConfig): NofOverlay =
  ## Factory method to create NOF overlay with proper defaults
  NofOverlay(
    name: name,
    description: description,
    overlayConfOverlayrlayConfig,
    signature: none(Signature),
    format: NofOverlay,
    cryptoAlgorithms: CryptoAlgorithms(
      hashAlgorithm: "BLAKE2b",
      signatureAlgorithm: "Ed25519",
      version: "1.0"
    )
  )

proc createOverlayConfig*(name: string, description: string,
                         targetGeneration: Option[string] = none(string),
                         modifications: JsonNode = newJObject()): OverlayConfig =
  ## Factory method to create overlay configuration
  OverlayConfig(
    name: name,
    description: description,
    targetGeneration: targetGeneration,
    modifications: modifications
  )

proc createOverlayModification*(operation: OverlayOperation, target: string,
                               source: Option[string] = none(string),
                               content: Option[string] = none(string),
                               permissions: Option[FilePermissions] = none(FilePermissions),
                               metadata: JsonNode = newJObject()): OverlayModification =
  ## Factory method to create overlay modification
  OverlayModification(
    operation: operation,
    target: target,
    source: source,
    content: content,
    permissions: permissions,
    metadata: metadata
  )

# =============================================================================
# KDL Serialization for NOF Format
# =============================================================================

proc escapeKdlString(s: string): string =
  ## Escape special characters in KDL strings
  result = "\""
  for c in s:
    case c:
    of '"': result.add("\\\"")
    of '\\': result.add("\\\\")
    of '\n': result.add("\\n")
    of '\r': result.add("\\r")
    of '\t': result.add("\\t")
    else: result.add(c)
  result.add("\"")

proc formatKdlBoolean(b: bool): string =
  ## Format boolean for KDL
  if b: "true" else: "false"

proc formatKdlArray(items: seq[string]): string =
  ## Format string array for KDL
  if items.len == 0:
    return ""
  result = ""
  for i, item in items:
    if i > 0: result.add(" ")
    result.add(escapeKdlString(item))

proc toHex(b: byte): string =
  ## Convert byte to hex string
  const hexChars = "0123456789abcdef"
  result = $hexChars[b shr 4] & $hexChars[b and 0x0F]

proc serializeModificationsToKdl(modifications: JsonNode): string =
  ## Serialize modifications JSON to KDL format
  ## This is a simplified conversion - full KDL library would be better
  result = ""

  if modifications.kind == JObject:
    for key, value in modifications:
      case value.kind:
      of JString:
        result.add("    " & escapeKdlString(key) & " " & escapeKdlString(value.getStr()) & "\n")
      of JInt:
        result.add("    " & escapeKdlString(key) & " " & $value.getInt() & "\n")
      of JBool:
        result.add("    " & escapeKdlString(key) & " " & formatKdlBoolean(value.getBool()) & "\n")
      of JArray:
        let items = value.getElems().mapIt(it.getStr())
        result.add("    " & escapeKdlString(key) & " " & formatKdlArray(items) & "\n")
      of JObject:
        result.add("    " & escapeKdlString(key) & " {\n")
        for subKey, subValue in value:
          result.add("      " & escapeKdlString(subKey) & " " & escapeKdlString(subValue.getStr()) & "\n")
        result.add("    }\n")
      else:
        result.add("    " & escapeKdlString(key) & " " & escapeKdlString($value) & "\n")

proc serializeNofToKdl*(overlay: NofOverlay): string =
  ## Serialize NOF overlay to KDL format with comprehensive metadata
  ## Plain-text format optimized for immutable system overlays

  result = "overlay " & escapeKdlString(overlay.name) & " {\n"
  result.add("  version " & escapeKdlString(NOF_VERSION) & "\n")
  result.add("  format " & escapeKdlString($overlay.format) & "\n")
  result.add("  description " & escapeKdlString(overlay.description) & "\n")
  result.add("\n")

  # Overlay configuration
  result.add("  config {\n")
  result.add("    name " & escapeKdlString(overlay.overlayConfig.name) & "\n")
  result.add("    description " & escapeKdlString(overlay.overlayConfig.description) & "\n")

  if overlay.overlayConfig.targetGeneration.isSome:
    result.add("    target-generation " & escapeKdlString(overlay.overlayConfig.targetGeneration.get()) & "\n")

  result.add("  }\n\n")

  # Modifications section
  result.add("  modifications {\n")
  result.add(serializeModificationsToKdl(overlay.overlayConfig.modifications))
  result.add("  }\n\n")

  # Cryptographic integrity and signature
  result.add("  integrity {\n")
  result.add("    algorithm " & escapeKdlString(overlay.cryptoAlgorithms.hashAlgorithm) & "\n")
  result.add("    signature-algorithm " & escapeKdlString(overlay.cryptoAlgorithms.signatureAlgorithm) & "\n")
  result.add("    version " & escapeKdlString(overlay.cryptoAlgorithms.version) & "\n")
  if overlay.signature.isSome:
    let sig = overlay.signature.get()
    result.add("    signature " & escapeKdlString(sig.signature.mapIt(it.toHex()).join("")) & "\n")
    result.add("    key-id " & escapeKdlString(sig.keyId) & "\n")
  result.add("  }\n")

  result.add("}\n")

proc deserializeNofFromKdl*(kdlContent: string): Result[NofOverlay, NofError] =
  ## Deserialize NOF overlay from KDL format
  ## TODO: Implement proper KDL parsing when kdl library is available
  ## For now, return an error indicating this is not yet implemented
  return err[NofOverlay, NofError](NofError(
    code: InvalidMetadata,
    msg: "KDL deserialization not yet implemented - waiting for kdl library",
    overlayName: "unknown"
  ))

# =============================================================================
# Overlay Validation
# =============================================================================

proc validateNofOverlay*(overlay: NofOverlay): OverlayValidationResult =
  ## Validate NOF overlay format and content
  var result = OverlayValidationResult(valid: true, errors: @[], warnings: @[])

  # Validate basic metadata
  if overlay.name.len == 0:
    result.errors.add(ValidationError(
      field: "name",
      message: "Overlay name cannot be empty",
      suggestions: @["Provide a valid overlay name"]
    ))
    result.valid = false

  if overlay.description.len == 0:
    result.warnings.add("Overlay has no description")

  # Validate overlay configuration
  if overlay.overlayConfig.name.len == 0:
    result.errors.add(ValidationError(
      field: "overlayConfig.name",
      message: "Overlay config name cannot be empty",
      suggestions: @["Provide a valid config name"]
    ))
    result.valid = false

  if overlay.overlayConfig.description.len == 0:
    result.warnings.add("Overlay config has no description")

  # Validate modifications structure
  if overlay.overlayConfig.modifications.kind != JObject:
    result.errors.add(ValidationError(
      field: "overlayConfig.modifications",
      message: "Modifications must be a JSON object",
      suggestions: @["Provide valid modifications object"]
    ))
    result.valid = false

  # Validate target generation if specified
  if overlay.overlayConfig.targetGeneration.isSome:
    let targetGen = overlay.overlayConfig.targetGeneration.get()
    if targetGen.len == 0:
      result.errors.add(ValidationError(
        field: "overlayConfig.targetGeneration",
        message: "Target generation cannot be empty if specified",
        suggestions: @["Provide valid generation ID or remove target"]
      ))
      result.valid = false

  # Validate cryptographic algorithms
  if not isQuantumResistant(overlay.cryptoAlgorithms):
    result.warnings.add("Using non-quantum-resistant algorithms: " &
                       overlay.cryptoAlgorithms.hashAlgorithm & "/" &
                       overlay.cryptoAlgorithms.signatureAlgorithm)

  return result

proc validateOverlayModification*(modification: OverlayModification): seq[string] =
  ## Validate individual overlay modification and return warnings
  var warnings: seq[string] = @[]

  case modification.operation:
  of AddFile, ModifyFile:
    if modification.content.isNone and modification.source.isNone:
      warnings.add("File operation without content or source")
    if modification.target.len == 0:
      warnings.add("File operation without target path")

  of RemoveFile, RemoveSymlink:
    if modification.target.len == 0:
      warnings.add("Remove operation without target path")

  of AddSymlink:
    if modification.source.isNone:
      warnings.add("Symlink operation without source")
    if modification.target.len == 0:
      warnings.add("Symlink operation without target")

  of SetPermissions:
    if modification.permissions.isNone:
      warnings.add("Permission operation without permissions")
    if modification.target.len == 0:
      warnings.add("Permission operation without target")

  of AddPackage, RemovePackage:
    if modification.target.len == 0:
      warnings.add("Package operation without package name")

  of SetConfig:
    if modification.target.len == 0:
      warnings.add("Config operation without config key")
    if modification.content.isNone:
      warnings.add("Config operation without value")

  return warnings

# =============================================================================
# Overlay File Operations
# =============================================================================

proc saveNofOverlay*(overlay: NofOverlay, filePath: string): Result[void, NofError] =
  ## Save NOF overlay to file in KDL format
  try:
    let kdlContent = serializeNofToKdl(overlay)

    # Ensure the file has the correct .nof extension
    let finalPath = if filePath.endsWith(".nof"): filePath else: filePath & ".nof"

    # Ensure parent directory exists
    let parentDir = finalPath.parentDir()
    if not dirExists(parentDir):
      createDir(parentDir)

    writeFile(finalPath, kdlContent)
    return ok[void, NofError]()

  except IOError as e:
    return err[void, NofError](NofError(
      code: FileWriteError,
      msg: "Failed to save NOF overlay: " & e.msg,
      overlayName: overlay.name
    ))

proc loadNofOverlay*(filePath: string): Result[NofOverlay, NofError] =
  ## Load NOF overlay from file
  try:
    if not fileExists(filePath):
      return err[NofOverlay, NofError](NofError(
        code: PackageNotFound,
        msg: "NOF overlay file not found: " & filePath,
        overlayName: "unknown"
      ))

    let kdlContent = readFile(filePath)
    return deserializeNofFromKdl(kdlContent)

  except IOError as e:
    return err[NofOverlay, NofError](NofError(
      code: FileReadError,
      msg: "Failed to load NOF overlay: " & e.msg,
      overlayName: "unknown"
    ))

# =============================================================================
# Overlay Digital Signatures
# =============================================================================

proc signNofOverlay*(overlay: var NofOverlay, keyId: string, privateKey: seq[byte]): Result[void, NofError] =
  ## Sign NOF overlay with Ed25519 private key
  ## Creates a comprehensive signature payload including all critical overlay metadata
  try:
    # Create comprehensive signature payload from overlay metadata and modifications
    let payload = overlay.name &
                 overlay.description &
                 overlay.overlayConfig.name &
                 overlay.overlayConfig.description &
                 (if overlay.overlayConfig.targetGeneration.isSome: overlay.overlayConfig.targetGeneration.get() else: "") &
                 $overlay.overlayConfig.modifications

    # TODO: Implement actual Ed25519 signing when crypto library is available
    # For now, create a deterministic placeholder signature based on payload
    let payloadHash = calculateBlake2b(payload.toOpenArrayByte(0, payload.len - 1).toSeq())
    let placeholderSig = payloadHash[0..63].toOpenArrayByte(0, 63).toSeq()  # 64 bytes like Ed25519

    let signature = Signature(
      keyId: keyId,
      algorithm: overlay.cryptoAlgorithms.signatureAlgorithm,
      signature: placeholderSig
    )

    overlay.signature = some(signature)
    return ok[void, NofError]()

  except Exception as e:
    return err[void, NofError](NofError(
      code: UnknownError,
      msg: "Failed to sign overlay: " & e.msg,
      overlayName: overlay.name
    ))

proc verifyNofSignature*(overlay: NofOverlay, publicKey: seq[byte]): Result[bool, NofError] =
  ## Verify NOF overlay signature
  ## TODO: Implement proper Ed25519 verification when crypto library is available
  if overlay.signature.isNone:
    return ok[bool, NofError](false)  # No signature to verify

  try:
    let sig = overlay.signature.get()

    # TODO: Implement actual Ed25519 verification
    # For now, just check if signature exists and has correct length
    let isValid = sig.signature.len == 64 and sig.keyId.len > 0

    return ok[bool, NofError](isValid)

  except Exception as e:
    return err[bool, NofError](NofError(
      code: UnknownError,
      msg: "Failed to verify signature: " & e.msg,
      overlayName: overlay.name
    ))

# =============================================================================
# Overlay Application System
# =============================================================================

proc applyOverlay*(overlay: NofOverlay, targetDir: string, dryRun: bool = false): Result[seq[string], NofError] =
  ## Apply overlay modifications to target directory
  ## Returns list of operations performed
  try:
    var operations: seq[string] = @[]

    # Parse modifications from JSON
    if overlay.overlayConfig.modifications.kind != JObject:
      return err[seq[string], NofError](NofError(
        code: InvalidMetadata,
        msg: "Invalid modifications format",
        overlayName: overlay.name
      ))

    # Process each modification
    for key, value in overlay.overlayConfig.modifications:
      let operation = "Apply " & key & ": " & $value
      operations.add(operation)

      if not dryRun:
        # TODO: Implement actual overlay application logic
        # This would involve:
        # - Parsing the modification type and parameters
        # - Applying file operations (create, modify, delete)
        # - Managing symlinks and permissions
        # - Handling package operations
        # - Setting configuration values
        discard

    return ok[seq[string], NofError](operations)

  except Exception as e:
    return err[seq[string], NofError](NofError(
      code: UnknownError,
      msg: "Failed to apply overlay: " & e.msg,
      overlayName: overlay.name
    ))

proc detectOverlayConflicts*(overlays: seq[NofOverlay]): seq[string] =
  ## Detect conflicts between multiple overlays
  var conflicts: seq[string] = @[]
  var targetPaths: Table[string, string] = initTable[string, string]()

  for overlay in overlays:
    if overlay.overlayConfig.modifications.kind == JObject:
      for key, value in overlay.overlayConfig.modifications:
        if key in targetPaths:
          conflicts.add("Conflict: " & key & " modified by both " &
                       targetPaths[key] & " and " & overlay.name)
        else:
          targetPaths[key] = overlay.name

  return conflicts

proc resolveOverlayConflicts*(overlays: seq[NofOverlay],
                             resolution: Table[string, string]): seq[NofOverlay] =
  ## Resolve overlay conflicts using provided resolution strategy
  ## Resolution table maps conflict keys to preferred overlay names
  var resolved: seq[NofOverlay] = @[]

  for overlay in overlays:
    var resolvedOverlay = overlay

    if overlay.overlayConfig.modifications.kind == JObject:
      var newModifications = newJObject()

      for key, value in overlay.overlayConfig.modifications:
        if key in resolution:
          if resolution[key] == overlay.name:
            newModifications[key] = value
        else:
          newModifications[key] = value

      resolvedOverlay.overlayConfig.modifications = newModifications

    resolved.add(resolvedOverlay)

  return resolved

# =============================================================================
# Overlay Templates and Presets
# =============================================================================

proc createFileOverlay*(name: string, description: string,
                       filePath: string, content: string,
                       permissions: Option[FilePermissions] = none(FilePermissions)): NofOverlay =
  ## Create overlay for adding/modifying a file
  let modifications = %*{
    "files": {
      filePath: {
        "operation": "add_file",
        "content": content,
        "permissions": if permissions.isSome: %*{
          "mode": permissions.get().mode,
          "owner": permissions.get().owner,
          "group": permissions.get().group
        } else: newJNull()
      }
    }
  }

  let config = createOverlayConfig(name, description, modifications = modifications)
  return createNofOverlay(name, description, config)

proc createPackageOverlay*(name: string, description: string,
                          packageName: string, packageVersion: string,
                          operation: string = "add"): NofOverlay =
  ## Create overlay for adding/removing a package
  let modifications = %*{
    "packages": {
      packageName: {
        "operation": operation,
        "version": packageVersion
      }
    }
  }

  let config = createOverlayConfig(name, description, modifications = modifications)
  return createNofOverlay(name, description, config)

proc createConfigOverlay*(name: string, description: string,
                         configKey: string, configValue: string): NofOverlay =
  ## Create overlay for setting configuration values
  let modifications = %*{
    "config": {
      configKey: {
        "operation": "set_config",
        "value": configValue
      }
    }
  }

  let config = createOverlayConfig(name, description, modifications = modifications)
  return createNofOverlay(name, description, config)

proc createSymlinkOverlay*(name: string, description: string,
                          linkPath: string, targetPath: string): NofOverlay =
  ## Create overlay for adding symbolic links
  let modifications = %*{
    "symlinks": {
      linkPath: {
        "operation": "add_symlink",
        "target": targetPath
      }
    }
  }

  let config = createOverlayConfig(name, description, modifications = modifications)
  return createNofOverlay(name, description, config)

# =============================================================================
# Utility Functions
# =============================================================================

proc getNofInfo*(overlay: NofOverlay): string =
  ## Get human-readable overlay information
  result = "NOF Overlay: " & overlay.name & "\n"
  result.add("Description: " & overlay.description & "\n")
  result.add("Config: " & overlay.overlayConfig.name & "\n")
  if overlay.overlayConfig.targetGeneration.isSome:
    result.add("Target Generation: " & overlay.overlayConfig.targetGeneration.get() & "\n")
  result.add("Modifications: " & $overlay.overlayConfig.modifications.len & " items\n")
  if overlay.signature.isSome:
    result.add("Signed: Yes (Key: " & overlay.signature.get().keyId & ")\n")
  else:
    result.add("Signed: No\n")

proc calculateBlake2b*(data: seq[byte]): string =
  ## Calculate BLAKE2b hash - imported from CAS module
  cas.calculateBlake2b(data)

proc calculateBlake3*(data: seq[byte]): string =
  ## Calculate BLAKE3 hash - imported from CAS module
  cas.calculateBlake3(data)