# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.

## Package Format Infrastructure
##
## This module implements the core package format system with five distinct formats,
## format detection, validation framework, and quantum-resistant cryptographic design.
## Each format is optimized for specific use cases while maintaining unified interfaces.

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

# =============================================================================
# Format Detection and Validation Framework
# =============================================================================

type
  FormatDetectionResult* = object
    format*: PackageFormat
    confidence*: float  ## 0.0 to 1.0 confidence score
    metadata*: JsonNode ## Format-specific metadata

  ValidationResult* = object
    valid*: bool
    errors*: seq[ValidationError]
    warnings*: seq[string]
    format*: PackageFormat

  FormatError* = object of NimPakError
    format*: PackageFormat

# Forward declarations for functions defined later
proc detectFormatFromContent*(filePath: string): Result[FormatDetectionResult, FormatError]

# =============================================================================
# Quantum-Resistant Cryptographic Algorithm Management
# =============================================================================

# Use package format aliases for cleaner code
const
  NprRecipe* = NprRecipeFormat
  NpkBinary* = NpkBinaryFormat
  NcaChunk* = NcaChunkFormat
  NssSnapshot* = NssSnapshotFormat
  NofOverlay* = NofOverlayFormat

proc getDefaultCryptoAlgorithms*(format: PackageFormat): CryptoAlgorithms =
  ## Get default cryptographic algorithms for a package format
  case format:
  of NprRecipeFormat:
    CryptoAlgorithms(hashAlgorithm: "BLAKE2b", signatureAlgorithm: "Ed25519", version: "1.0")
  of NpkBinaryFormat:
    CryptoAlgorithms(hashAlgorithm: "BLAKE3", signatureAlgorithm: "Ed25519", version: "1.0")
  of NcaChunkFormat:
    CryptoAlgorithms(hashAlgorithm: "BLAKE3", signatureAlgorithm: "Ed25519", version: "1.0")
  of NssSnapshotFormat:
    CryptoAlgorithms(hashAlgorithm: "BLAKE3", signatureAlgorithm: "Ed25519", version: "1.0")
  of NofOverlayFormat:
    CryptoAlgorithms(hashAlgorithm: "BLAKE2b", signatureAlgorithm: "Ed25519", version: "1.0")

proc getQuantumMigrationPath*(algorithm: string): string =
  ## Get quantum-resistant replacement for an algorithm
  case algorithm:
  of "BLAKE2b": "SHA3-512"
  of "BLAKE3": "SHA3-512"
  of "Ed25519": "Dilithium"
  else: algorithm

# Migration paths from classic to quantum-resistant algorithms
const QUANTUM_MIGRATION_PATHS* = {
  "BLAKE2b": "SHA3-512",
  "BLAKE3": "SHA3-512",
  "Ed25519": "Dilithium",
  "RSA": "Dilithium",
  "ECDSA": "Dilithium",
  "SHA256": "SHA3-512",
  "SHA512": "SHA3-512"
}.toTable

proc isQuantumResistant*(algorithms: CryptoAlgorithms): bool =
  ## Check if cryptographic algorithms are quantum-resistant
  algorithms.hashAlgorithm in ["SHA3-512", "BLAKE3"] and
  algorithms.signatureAlgorithm in ["Dilithium", "SPHINCS+"]

proc migrateToQuantumResistant*(algorithms: var CryptoAlgorithms): bool =
  ## Migrate algorithms to quantum-resistant alternatives
  var migrated = false

  if algorithms.hashAlgorithm in QUANTUM_MIGRATION_PATHS:
    algorithms.hashAlgorithm = QUANTUM_MIGRATION_PATHS[algorithms.hashAlgorithm]
    migrated = true

  if algorithms.signatureAlgorithm in QUANTUM_MIGRATION_PATHS:
    algorithms.signatureAlgorithm = QUANTUM_MIGRATION_PATHS[algorithms.signatureAlgorithm]
    migrated = true

  if migrated:
    algorithms.version = "2.0"  # Increment version for quantum-resistant

  return migrated

# =============================================================================
# Quantum-Resistant Cryptographic Transitions
# =============================================================================

type
  AlgorithmMigration* = object
    ## Algorithm migration specification
    fromAlgorithm*: string
    toAlgorithm*: string
    migrationDate*: times.DateTime
    compatibility*: bool  ## Whether old and new can coexist
    mandatory*: bool      ## Whether migration is mandatory
    description*: string  ## Human-readable description

  MigrationPlan* = object
    ## Complete migration plan for cryptographic algorithms
    hashMigrations*: seq[AlgorithmMigration]
    signatureMigrations*: seq[AlgorithmMigration]
    targetDate*: times.DateTime
    phaseOutDate*: times.DateTime
    backwardCompatible*: bool

  MigrationStatus* = enum
    NotStarted, InProgress, Completed, PhaseOut

  TransitionPhase* = enum
    ## Phases of cryptographic algorithm transition
    PreTransition,    ## Before transition starts
    DualSupport,      ## Supporting both old and new algorithms
    NewPreferred,     ## New algorithms preferred, old still supported
    NewOnly,          ## Only new algorithms supported
    PostTransition    ## Transition complete

  CryptoTransition* = object
    ## Cryptographic transition state
    currentAlgorithms*: CryptoAlgorithms
    targetAlgorithms*: CryptoAlgorithms
    transitionPhase*: TransitionPhase
    migrationPlan*: MigrationPlan

const
  ## Quantum-resistant algorithm upgrade timeline
  QUANTUM_MIGRATION_TIMELINE* = {
    "BLAKE2b": (target: "SHA3-512", deadline: "2030-01-01"),
    "BLAKE3": (target: "SHA3-512", deadline: "2035-01-01"),
    "Ed25519": (target: "Dilithium", deadline: "2030-01-01")
  }.toTable()

proc createMigrationPlan*(targetDate: times.DateTime): MigrationPlan =
  ## Create comprehensive migration plan for quantum-resistant algorithms
  var plan = MigrationPlan(
    hashMigrations: @[],
    signatureMigrations: @[],
    targetDate: targetDate,
    phaseOutDate: targetDate + initDuration(days = 730)  # 2 year phase-out period
  )

  # Hash algorithm migrations
  plan.hashMigrations.add(AlgorithmMigration(
    fromAlgorithm: "BLAKE2b",
    toAlgorithm: "SHA3-512",
    migrationDate: targetDate - initDuration(days = 365),  # Start 1 year before target
    compatibility: true
  ))

  plan.hashMigrations.add(AlgorithmMigration(
    fromAlgorithm: "BLAKE3",
    toAlgorithm: "SHA3-512",
    migrationDate: targetDate - initDuration(days = 180),  # Start 6 months before target
    compatibility: true
  ))

  # Signature algorithm migrations
  plan.signatureMigrations.add(AlgorithmMigration(
    fromAlgorithm: "Ed25519",
    toAlgorithm: "Dilithium",
    migrationDate: targetDate - initDuration(days = 365),
    compatibility: false  # Cannot coexist due to key format differences
  ))

  return plan

proc getMigrationStatus*(algorithm: string, currentDate: times.DateTime = now()): MigrationStatus =
  ## Get current migration status for an algorithm
  if algorithm notin QUANTUM_MIGRATION_TIMELINE:
    return Completed  # Already quantum-resistant or unknown

  let (_, deadlineStr) = QUANTUM_MIGRATION_TIMELINE[algorithm]
  let deadlineTime = deadlineStr.parseTime("yyyy-MM-dd", utc())
  let deadline = deadlineTime.utc()  # Convert Time to DateTime

  let timeToDeadline = deadline - currentDate

  if timeToDeadline > initDuration(days = 730):  # 2 years
    return NotStarted
  elif timeToDeadline > initDuration(days = 0):
    return InProgress
  elif timeToDeadline > initDuration(days = -730):  # -2 years
    return Completed
  else:
    return PhaseOut

proc validateAlgorithmCompatibility*(algorithms: seq[CryptoAlgorithms]): seq[string] =
  ## Validate compatibility between different algorithm versions
  var warnings: seq[string] = @[]
  var hashAlgorithms: Table[string, int] = initTable[string, int]()
  var signatureAlgorithms: Table[string, int] = initTable[string, int]()

  # Count algorithm usage
  for algo in algorithms:
    hashAlgorithms[algo.hashAlgorithm] = hashAlgorithms.getOrDefault(algo.hashAlgorithm, 0) + 1
    signatureAlgorithms[algo.signatureAlgorithm] = signatureAlgorithms.getOrDefault(algo.signatureAlgorithm, 0) + 1

  # Check for mixed quantum/non-quantum usage
  var hasQuantumHash = false
  var hasNonQuantumHash = false
  var hasQuantumSig = false
  var hasNonQuantumSig = false

  for hashAlgo in hashAlgorithms.keys:
    if hashAlgo in ["SHA3-512"]:
      hasQuantumHash = true
    else:
      hasNonQuantumHash = true

  for sigAlgo in signatureAlgorithms.keys:
    if sigAlgo in ["Dilithium", "SPHINCS+"]:
      hasQuantumSig = true
    else:
      hasNonQuantumSig = true

  if hasQuantumHash and hasNonQuantumHash:
    warnings.add("Mixed quantum-resistant and legacy hash algorithms detected")

  if hasQuantumSig and hasNonQuantumSig:
    warnings.add("Mixed quantum-resistant and legacy signature algorithms detected")

  # Check for deprecated algorithms
  for hashAlgo in hashAlgorithms.keys:
    let status = getMigrationStatus(hashAlgo)
    if status == PhaseOut:
      warnings.add("Hash algorithm " & hashAlgo & " is in phase-out period")

  for sigAlgo in signatureAlgorithms.keys:
    let status = getMigrationStatus(sigAlgo)
    if status == PhaseOut:
      warnings.add("Signature algorithm " & sigAlgo & " is in phase-out period")

  return warnings

proc upgradeAlgorithmsInPackage*(packagePath: string, targetAlgorithms: CryptoAlgorithms): VoidResult[FormatError] =
  ## Upgrade cryptographic algorithms in a package file
  try:
    # Detect package format
    let detectionResult = detectFormatFromContent(packagePath)
    if detectionResult.isErr:
      return err[FormatError](detectionResult.getError())

    let detection = detectionResult.get()

    case detection.format:
    of NpkBinary:
      # TODO: Load NPK package, update algorithms, re-sign, save
      # This would involve:
      # 1. Loading the package
      # 2. Updating the cryptoAlgorithms field
      # 3. Recalculating hashes with new algorithm
      # 4. Re-signing with new signature algorithm
      # 5. Saving the updated package
      discard

    of NprRecipe:
      # TODO: Load NPR recipe, update algorithms, re-sign, save
      discard

    of NcaChunk:
      # TODO: Load NCA chunk, update algorithms, save
      discard

    of NssSnapshot:
      # TODO: Load NSS snapshot, update algorithms, re-sign, save
      discard

    of NofOverlay:
      # TODO: Load NOF overlay, update algorithms, re-sign, save
      discard

    return ok(FormatError)

  except Exception as e:
    return err[FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to upgrade algorithms: " & e.msg,
      format: NpkBinary
    ))

proc createAlgorithmTransitionReport*(packages: seq[string]): Result[JsonNode, FormatError] =
  ## Create report on algorithm transition status across packages
  try:
    var algorithmCounts: Table[string, Table[string, int]] = initTable[string, Table[string, int]]()
    var migrationStatus: Table[string, int] = initTable[string, int]()
    var totalPackages = 0

    for packagePath in packages:
      if not fileExists(packagePath):
        continue

      totalPackages += 1

      # Detect format and analyze algorithms (simplified)
      let detectionResult = detectFormatFromContent(packagePath)
      if detectionResult.isOk:
        let detection = detectionResult.get()
        let formatStr = $detection.format

        if formatStr notin algorithmCounts:
          algorithmCounts[formatStr] = initTable[string, int]()

        # For now, assume default algorithms based on format
        # In practice, would parse actual package content
        let defaultAlgos = getDefaultCryptoAlgorithms(detection.format)
        let algoKey = defaultAlgos.hashAlgorithm & "/" & defaultAlgos.signatureAlgorithm

        algorithmCounts[formatStr][algoKey] = algorithmCounts[formatStr].getOrDefault(algoKey, 0) + 1

        # Check migration status
        let hashStatus = getMigrationStatus(defaultAlgos.hashAlgorithm)
        let sigStatus = getMigrationStatus(defaultAlgos.signatureAlgorithm)

        let statusKey = $hashStatus & "/" & $sigStatus
        migrationStatus[statusKey] = migrationStatus.getOrDefault(statusKey, 0) + 1

    let report = %*{
      "total_packages": totalPackages,
      "algorithm_usage": algorithmCounts,
      "migration_status": migrationStatus,
      "generated": $now(),
      "recommendations": [
        "Prioritize upgrading packages with PhaseOut algorithms",
        "Test quantum-resistant algorithms in development environment",
        "Plan gradual migration to avoid compatibility issues"
      ]
    }

    return ok[JsonNode, FormatError](report)

  except Exception as e:
    return err[JsonNode, FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to create transition report: " & e.msg,
      format: NpkBinary
    ))

proc validateQuantumReadiness*(algorithms: CryptoAlgorithms): seq[string] =
  ## Validate quantum readiness of cryptographic algorithms
  var issues: seq[string] = @[]

  # Check hash algorithm
  if algorithms.hashAlgorithm notin ["SHA3-512", "BLAKE3"]:
    issues.add("Hash algorithm " & algorithms.hashAlgorithm & " is not quantum-resistant")

  # Check signature algorithm
  if algorithms.signatureAlgorithm notin ["Dilithium", "SPHINCS+"]:
    issues.add("Signature algorithm " & algorithms.signatureAlgorithm & " is not quantum-resistant")

  # Check version compatibility
  if algorithms.version == "1.0" and isQuantumResistant(algorithms):
    issues.add("Quantum-resistant algorithms should use version 2.0 or higher")

  return issues

proc createBackwardCompatibilityLayer*(oldAlgorithms: CryptoAlgorithms,
                                     newAlgorithms: CryptoAlgorithms): JsonNode =
  ## Create compatibility layer for mixed-algorithm environments
  %*{
    "compatibility_mode": true,
    "primary_algorithms": {
      "hash": newAlgorithms.hashAlgorithm,
      "signature": newAlgorithms.signatureAlgorithm,
      "version": newAlgorithms.version
    },
    "fallback_algorithms": {
      "hash": oldAlgorithms.hashAlgorithm,
      "signature": oldAlgorithms.signatureAlgorithm,
      "version": oldAlgorithms.version
    },
    "verification_strategy": "try_primary_then_fallback",
    "migration_deadline": $QUANTUM_MIGRATION_TIMELINE.getOrDefault(oldAlgorithms.hashAlgorithm, ("", "2030-01-01"))[1]
  }

# =============================================================================
# Format Detection
# =============================================================================

proc detectFormatFromExtension*(filePath: string): Result[PackageFormat, FormatError] =
  ## Detect package format from file extension
  let ext = filePath.splitFile().ext.toLowerAscii()

  case ext:
  of ".npr":
    return ok[PackageFormat, FormatError](NprRecipe)
  of ".zst":
    if filePath.endsWith(".npk.zst"):
      return ok[PackageFormat, FormatError](NpkBinary)
    elif filePath.endsWith(".nss.zst"):
      return ok[PackageFormat, FormatError](NssSnapshot)
    else:
      return err[PackageFormat, FormatError](FormatError(
        code: InvalidMetadata,
        msg: "Unknown .zst format: " & filePath,
        format: NpkBinary
      ))
  of ".nca":
    return ok[PackageFormat, FormatError](NcaChunk)
  of ".nof":
    return ok[PackageFormat, FormatError](NofOverlay)
  of ".tar":
    if filePath.endsWith(".npk.tar"):
      return ok[PackageFormat, FormatError](NpkBinary)
    else:
      return err[PackageFormat, FormatError](FormatError(
        code: InvalidMetadata,
        msg: "Unknown .tar format: " & filePath,
        format: NpkBinary
      ))
  else:
    return err[PackageFormat, FormatError](FormatError(
      code: InvalidMetadata,
      msg: "Unknown file extension: " & ext,
      format: NpkBinary
    ))

proc detectFormatFromContent*(filePath: string): Result[FormatDetectionResult, FormatError] =
  ## Detect package format from file content analysis
  if not fileExists(filePath):
    return err[FormatDetectionResult, FormatError](FormatError(
      code: PackageNotFound,
      msg: "File not found: " & filePath,
      format: NpkBinary
    ))

  try:
    let content = readFile(filePath)

    # Check for KDL format markers (NPR and NOF)
    if content.contains("package \"") and content.contains("build {"):
      return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
        format: NprRecipe,
        confidence: 0.9,
        metadata: %*{"detected_from": "kdl_build_marker"}
      ))

    if content.contains("overlay \"") and content.contains("modifications {"):
      return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
        format: NofOverlay,
        confidence: 0.9,
        metadata: %*{"detected_from": "kdl_overlay_marker"}
      ))

    # Check for binary formats by magic bytes
    if content.len >= 4:
      let magic = content[0..3]

      # Check for zstd magic bytes (0x28 0xB5 0x2F 0xFD)
      if magic == "\x28\xB5\x2F\xFD":
        # Could be NPK or NSS - need filename to distinguish
        let ext = filePath.splitFile().ext.toLowerAscii()
        if filePath.endsWith(".npk.zst"):
          return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
            format: NpkBinary,
            confidence: 0.95,
            metadata: %*{"detected_from": "zstd_magic", "extension": ".npk.zst"}
          ))
        elif filePath.endsWith(".nss.zst"):
          return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
            format: NssSnapshot,
            confidence: 0.95,
            metadata: %*{"detected_from": "zstd_magic", "extension": ".nss.zst"}
          ))

      # Check for tar magic bytes
      if content.len >= 262 and content[257..261] == "ustar":
        return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
          format: NpkBinary,
          confidence: 0.8,
          metadata: %*{"detected_from": "tar_magic"}
        ))

    # Check for NCA format (binary with hash prefix)
    if content.startsWith("blake3-") or content.startsWith("blake2b-"):
      return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
        format: NcaChunk,
        confidence: 0.7,
        metadata: %*{"detected_from": "hash_prefix"}
      ))

    # Fallback to extension-based detection
    let extResult = detectFormatFromExtension(filePath)
    if extResult.isOk:
      return ok[FormatDetectionResult, FormatError](FormatDetectionResult(
        format: extResult.get(),
        confidence: 0.5,
        metadata: %*{"detected_from": "extension_fallback"}
      ))

    return err[FormatDetectionResult, FormatError](FormatError(
      code: InvalidMetadata,
      msg: "Unable to detect format from content",
      format: NpkBinary
    ))

  except IOError as e:
    return err[FormatDetectionResult, FormatError](FormatError(
      code: FileReadError,
      msg: "Failed to read file for format detection: " & e.msg,
      format: NpkBinary
    ))

# =============================================================================
# Format-Specific Factory Methods
# =============================================================================

proc createNprRecipe*(metadata: Fragment, buildInstructions: BuildTemplate): NprRecipe =
  ## Factory method to create NPR recipe with proper defaults
  NprRecipe(
    metadata: metadata,
    buildInstructions: buildInstructions,
    signature: none(Signature),
    format: NprRecipe,
    cryptoAlgorithms: getDefaultCryptoAlgorithms(NprRecipe)
  )

proc createNpkPackage*(metadata: Fragment, files: seq[PackageFile],
                      manifest: PackageManifest): NpkPackage =
  ## Factory method to create NPK package with proper defaults
  NpkPackage(
    metadata: metadata,
    files: files,
    manifest: manifest,
    signature: none(Signature),
    format: NpkBinary,
    cryptoAlgorithms: getDefaultCryptoAlgorithms(NpkBinary)
  )

proc createNcaChunk*(hash: string, data: seq[byte], compressed: bool = true): NcaChunk =
  ## Factory method to create NCA chunk with proper defaults
  NcaChunk(
    hash: hash,
    data: data,
    compressed: compressed,
    merkleProof: MerkleProof(path: @[], indices: @[]),
    format: NcaChunk,
    cryptoAlgorithms: getDefaultCryptoAlgorithms(NcaChunk)
  )

proc createNssSnapshot*(name: string, lockfile: Lockfile,
                       packages: seq[NpkPackage]): NssSnapshot =
  ## Factory method to create NSS snapshot with proper defaults
  NssSnapshot(
    name: name,
    created: now(),
    lockfile: lockfile,
    packages: packages,
    metadata: SnapshotMetadata(
      description: "System snapshot: " & name,
      creator: "nip",
      tags: @["snapshot"],
      size: 0,  # Will be calculated
      includedGenerations: @[]
    ),
    signature: none(Signature),
    format: NssSnapshot,
    cryptoAlgorithms: getDefaultCryptoAlgorithms(NssSnapshot)
  )

proc createNofOverlay*(name: string, description: string,
                      overlayConfig: OverlayConfig): NofOverlay =
  ## Factory method to create NOF overlay with proper defaults
  NofOverlay(
    name: name,
    description: description,
    overlayConfig: overlayConfig,
    signature: none(Signature),
    format: NofOverlay,
    cryptoAlgorithms: getDefaultCryptoAlgorithms(NofOverlay)
  )

# =============================================================================
# Format Validation Framework
# =============================================================================

proc validateNprRecipe*(recipe: NprRecipe): ValidationResult =
  ## Validate NPR recipe format and content
  var result = ValidationResult(valid: true, errors: @[], warnings: @[], format: NprRecipe)

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

  # Validate build instructions
  if recipe.buildInstructions.system == Custom and recipe.buildInstructions.configureArgs.len == 0:
    result.warnings.add("Custom build system without configure arguments")

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

  return result

proc validateNpkPackage*(package: NpkPackage): ValidationResult =
  ## Validate NPK package format and content
  var result = ValidationResult(valid: true, errors: @[], warnings: @[], format: NpkBinary)

  # Validate basic structure
  if package.files.len == 0:
    result.warnings.add("Package contains no files")

  if package.manifest.merkleRoot.len == 0:
    result.errors.add(ValidationError(
      field: "manifest.merkleRoot",
      message: "Merkle root cannot be empty",
      suggestions: @["Calculate Merkle root from file hashes"]
    ))
    result.valid = false

  # Validate file integrity
  for i, file in package.files:
    if file.hash.len == 0:
      result.errors.add(ValidationError(
        field: "files[" & $i & "].hash",
        message: "File hash cannot be empty",
        suggestions: @["Calculate file hash"]
      ))
      result.valid = false

  return result

proc validateNcaChunk*(chunk: NcaChunk): ValidationResult =
  ## Validate NCA chunk format and content
  var result = ValidationResult(valid: true, errors: @[], warnings: @[], format: NcaChunk)

  if chunk.hash.len == 0:
    result.errors.add(ValidationError(
      field: "hash",
      message: "Chunk hash cannot be empty",
      suggestions: @["Calculate chunk hash"]
    ))
    result.valid = false

  if chunk.data.len == 0:
    result.warnings.add("Chunk contains no data")

  return result

proc validateNssSnapshot*(snapshot: NssSnapshot): ValidationResult =
  ## Validate NSS snapshot format and content
  var result = ValidationResult(valid: true, errors: @[], warnings: @[], format: NssSnapshot)

  if snapshot.name.len == 0:
    result.errors.add(ValidationError(
      field: "name",
      message: "Snapshot name cannot be empty",
      suggestions: @["Provide a valid snapshot name"]
    ))
    result.valid = false

  if snapshot.packages.len == 0:
    result.warnings.add("Snapshot contains no packages")

  return result

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

  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

  return result

# =============================================================================
# Universal Format Validation
# =============================================================================

proc validatePackageFormat*(filePath: string): Result[ValidationResult, FormatError] =
  ## Universal validation function that detects format and validates accordingly
  let detectionResult = detectFormatFromContent(filePath)
  if detectionResult.isErr:
    return err[ValidationResult, FormatError](detectionResult.getError())

  let detection = detectionResult.get()

  # TODO: Load and validate actual package content based on detected format
  # For now, return basic validation result
  let result = ValidationResult(
    valid: true,
    errors: @[],
    warnings: @["Format validation not fully implemented for " & $detection.format],
    format: detection.format
  )

  return ok[ValidationResult, FormatError](result)

# =============================================================================
# Format Information and Utilities
# =============================================================================

proc getFormatInfo*(format: PackageFormat): JsonNode =
  ## Get comprehensive information about a package format
  case format:
  of NprRecipe:
    return %*{
      "name": "NPR Recipe",
      "extension": ".npr",
      "description": "Source-level package definitions in KDL format",
      "compression": "none",
      "signature_support": true,
      "use_cases": ["source_builds", "version_control", "recipes"]
    }
  of NpkBinary:
    return %*{
      "name": "NPK Binary Package",
      "extension": ".npk.zst",
      "description": "Compiled binary packages with zstd compression",
      "compression": "zstd",
      "signature_support": true,
      "use_cases": ["binary_distribution", "fast_installation", "production"]
    }
  of NcaChunk:
    return %*{
      "name": "NCA Content-Addressable Chunk",
      "extension": ".nca",
      "description": "Merkle-tree based CAS storage chunks",
      "compression": "optional_zstd",
      "signature_support": false,
      "use_cases": ["deduplication", "content_addressing", "storage"]
    }
  of NssSnapshot:
    return %*{
      "name": "NSS System Snapshot",
      "extension": ".nss.zst",
      "description": "Complete environment reproducibility snapshots",
      "compression": "zstd",
      "signature_support": true,
      "use_cases": ["system_backup", "reproducibility", "deployment"]
    }
  of NofOverlay:
    return %*{
      "name": "NOF Overlay Fragment",
      "extension": ".nof",
      "description": "Declarative system modifications in KDL format",
      "compression": "none",
      "signature_support": true,
      "use_cases": ["system_overlays", "configuration", "immutable_systems"]
    }

proc listSupportedFormats*(): seq[PackageFormat] =
  ## List all supported package formats
  @[NprRecipe, NpkBinary, NcaChunk, NssSnapshot, NofOverlay]

proc getFormatExtension*(format: PackageFormat): string =
  ## Get the standard file extension for a format
  case format:
  of NprRecipe: ".npr"
  of NpkBinary: ".npk.zst"
  of NcaChunk: ".nca"
  of NssSnapshot: ".nss.zst"
  of NofOverlay: ".nof"

# =============================================================================
# Quantum-Resistant Algorithm Transition Constants
# =============================================================================

var cachedTransitionPlan: Option[MigrationPlan]

proc getQuantumTransitionPlan*(): MigrationPlan =
  ## Get the quantum-resistant algorithm transition timeline
  ## Cached after first initialization
  if cachedTransitionPlan.isSome:
    return cachedTransitionPlan.get()
  
  let plan = MigrationPlan(
    hashMigrations: @[
      AlgorithmMigration(
        fromAlgorithm: "BLAKE2b",
        toAlgorithm: "BLAKE3",
        migrationDate: dateTime(2024, mJan, 1),
        compatibility: true,
        mandatory: false,
        description: "Transition to BLAKE3 for better performance and quantum resistance"
      ),
      AlgorithmMigration(
        fromAlgorithm: "BLAKE3",
        toAlgorithm: "SHA3-512",
        migrationDate: dateTime(2030, mJan, 1),
        compatibility: true,
        mandatory: true,
        description: "Mandatory transition to SHA3-512 for full quantum resistance"
      )
    ],
    signatureMigrations: @[
      AlgorithmMigration(
        fromAlgorithm: "Ed25519",
        toAlgorithm: "Dilithium",
        migrationDate: dateTime(2028, mJan, 1),
        compatibility: false,  # Key formats incompatible
        mandatory: true,
        description: "Mandatory transition to Dilithium for quantum-resistant signatures"
      )
    ],
    targetDate: dateTime(2030, mDec, 31),
    phaseOutDate: dateTime(2032, mDec, 31),
    backwardCompatible: true
  )
  
  cachedTransitionPlan = some(plan)
  return plan

# Convenience alias for backward compatibility
template QUANTUM_TRANSITION_PLAN*(): MigrationPlan = getQuantumTransitionPlan()

proc getCurrentTransitionPhase*(algorithms: CryptoAlgorithms): TransitionPhase =
  ## Determine current transition phase based on algorithms
  let now = now()

  # Check if using quantum-resistant algorithms
  if isQuantumResistant(algorithms):
    return PostTransition

  # Check if in transition period
  for migration in QUANTUM_TRANSITION_PLAN.hashMigrations:
    if now >= migration.migrationDate and algorithms.hashAlgorithm == migration.fromAlgorithm:
      return if migration.mandatory: NewPreferred else: DualSupport

  for migration in QUANTUM_TRANSITION_PLAN.signatureMigrations:
    if now >= migration.migrationDate and algorithms.signatureAlgorithm == migration.fromAlgorithm:
      return if migration.mandatory: NewPreferred else: DualSupport

  return PreTransition

proc createTransitionPlan*(currentAlgorithms: CryptoAlgorithms): CryptoTransition =
  ## Create transition plan for current algorithms
  var targetAlgorithms = currentAlgorithms

  # Apply hash algorithm transitions
  for migration in QUANTUM_TRANSITION_PLAN.hashMigrations:
    if currentAlgorithms.hashAlgorithm == migration.fromAlgorithm:
      targetAlgorithms.hashAlgorithm = migration.toAlgorithm
      break

  # Apply signature algorithm transitions
  for migration in QUANTUM_TRANSITION_PLAN.signatureMigrations:
    if currentAlgorithms.signatureAlgorithm == migration.fromAlgorithm:
      targetAlgorithms.signatureAlgorithm = migration.toAlgorithm
      break

  # Update version if algorithms changed
  if targetAlgorithms.hashAlgorithm != currentAlgorithms.hashAlgorithm or
     targetAlgorithms.signatureAlgorithm != currentAlgorithms.signatureAlgorithm:
    targetAlgorithms.version = "2.0"

  CryptoTransition(
    currentAlgorithms: currentAlgorithms,
    targetAlgorithms: targetAlgorithms,
    transitionPhase: getCurrentTransitionPhase(currentAlgorithms),
    migrationPlan: QUANTUM_TRANSITION_PLAN
  )

proc validateAlgorithmCompatibility*(algorithms: CryptoAlgorithms,
                                   requiredPhase: TransitionPhase = PreTransition): bool =
  ## Validate algorithm compatibility with required transition phase
  let currentPhase = getCurrentTransitionPhase(algorithms)

  case requiredPhase:
  of PreTransition:
    return true  # All algorithms accepted
  of DualSupport:
    return currentPhase >= DualSupport
  of NewPreferred:
    return currentPhase >= NewPreferred or isQuantumResistant(algorithms)
  of NewOnly:
    return isQuantumResistant(algorithms)
  of PostTransition:
    return isQuantumResistant(algorithms)

proc upgradeAlgorithms*(algorithms: var CryptoAlgorithms,
                       targetPhase: TransitionPhase = NewPreferred): bool =
  ## Upgrade algorithms to target transition phase
  let transition = createTransitionPlan(algorithms)

  case targetPhase:
  of PreTransition:
    return false  # No upgrade needed
  of DualSupport, NewPreferred:
    if transition.targetAlgorithms != algorithms:
      algorithms = transition.targetAlgorithms
      return true
  of NewOnly, PostTransition:
    # Force quantum-resistant algorithms
    if not isQuantumResistant(algorithms):
      algorithms.hashAlgorithm = "SHA3-512"
      algorithms.signatureAlgorithm = "Dilithium"
      algorithms.version = "2.0"
      return true

  return false

proc getAlgorithmMigrationStatus*(algorithms: CryptoAlgorithms): JsonNode =
  ## Get detailed migration status for algorithms
  let transition = createTransitionPlan(algorithms)
  let phase = getCurrentTransitionPhase(algorithms)

  var hashStatus = "current"
  var signatureStatus = "current"

  # Check hash algorithm status
  for migration in QUANTUM_TRANSITION_PLAN.hashMigrations:
    if algorithms.hashAlgorithm == migration.fromAlgorithm:
      hashStatus = if now() >= migration.migrationDate: "needs_migration" else: "scheduled"
      break
    elif algorithms.hashAlgorithm == migration.toAlgorithm:
      hashStatus = "migrated"
      break

  # Check signature algorithm status
  for migration in QUANTUM_TRANSITION_PLAN.signatureMigrations:
    if algorithms.signatureAlgorithm == migration.fromAlgorithm:
      signatureStatus = if now() >= migration.migrationDate: "needs_migration" else: "scheduled"
      break
    elif algorithms.signatureAlgorithm == migration.toAlgorithm:
      signatureStatus = "migrated"
      break

  return %*{
    "current_phase": $phase,
    "quantum_resistant": isQuantumResistant(algorithms),
    "algorithms": %*{
      "hash": %*{
        "current": algorithms.hashAlgorithm,
        "target": transition.targetAlgorithms.hashAlgorithm,
        "status": hashStatus
      },
      "signature": %*{
        "current": algorithms.signatureAlgorithm,
        "target": transition.targetAlgorithms.signatureAlgorithm,
        "status": signatureStatus
      },
      "version": algorithms.version
    },
    "migration_plan": %*{
      "target_date": $QUANTUM_TRANSITION_PLAN.targetDate,
      "backward_compatible": QUANTUM_TRANSITION_PLAN.backwardCompatible
    }
  }

# =============================================================================
# Backward Compatibility Support
# =============================================================================

proc createCompatibilityMatrix*(): Table[string, seq[string]] =
  ## Create compatibility matrix for algorithm versions
  var matrix = initTable[string, seq[string]]()

  # Hash algorithm compatibility
  matrix["BLAKE2b"] = @["BLAKE2b", "BLAKE3"]
  matrix["BLAKE3"] = @["BLAKE2b", "BLAKE3", "SHA3-512"]
  matrix["SHA3-512"] = @["SHA3-512"]

  # Signature algorithm compatibility
  matrix["Ed25519"] = @["Ed25519", "Dilithium"]
  matrix["Dilithium"] = @["Dilithium"]

  return matrix

proc isAlgorithmCompatible*(algorithm1: string, algorithm2: string): bool =
  ## Check if two algorithms are compatible
  let matrix = createCompatibilityMatrix()

  if matrix.hasKey(algorithm1):
    return algorithm2 in matrix[algorithm1]

  return algorithm1 == algorithm2

proc validateMixedAlgorithmEnvironment*(packages: seq[CryptoAlgorithms]): JsonNode =
  ## Validate environment with mixed algorithm versions
  var algorithmCounts = initTable[string, int]()
  var incompatiblePairs: seq[tuple[alg1: string, alg2: string]] = @[]

  # Count algorithm usage
  for pkg in packages:
    let hashKey = "hash:" & pkg.hashAlgorithm
    let sigKey = "sig:" & pkg.signatureAlgorithm

    algorithmCounts[hashKey] = algorithmCounts.getOrDefault(hashKey, 0) + 1
    algorithmCounts[sigKey] = algorithmCounts.getOrDefault(sigKey, 0) + 1

  # Check for incompatible combinations
  let matrix = createCompatibilityMatrix()
  for i in 0..<packages.len:
    for j in (i + 1)..<packages.len:
      let pkg1 = packages[i]
      let pkg2 = packages[j]

      if not isAlgorithmCompatible(pkg1.hashAlgorithm, pkg2.hashAlgorithm):
        incompatiblePairs.add((pkg1.hashAlgorithm, pkg2.hashAlgorithm))

      if not isAlgorithmCompatible(pkg1.signatureAlgorithm, pkg2.signatureAlgorithm):
        incompatiblePairs.add((pkg1.signatureAlgorithm, pkg2.signatureAlgorithm))

  return %*{
    "algorithm_counts": algorithmCounts,
    "incompatible_pairs": incompatiblePairs.mapIt(%*{"alg1": it.alg1, "alg2": it.alg2}),
    "compatible": incompatiblePairs.len == 0,
    "total_packages": packages.len
  }

# =============================================================================
# CAS Integration for All Package Formats
# =============================================================================

import ./cas

type
  CasIntegrationResult* = object
    hash*: string
    size*: int64
    format*: PackageFormat
    metadata*: JsonNode

proc storePackageInCas*(format: PackageFormat, data: seq[byte], cas: var CasManager): types_fixed.Result[CasIntegrationResult, FormatError] =
  ## Store package format data in content-addressable storage
  try:
    let storeResult = cas.storeObject(data)
    if not storeResult.isOk:
      return types_fixed.err[CasIntegrationResult, FormatError](FormatError(
        code: CasGeneralError,
        msg: "Failed to store package in CAS: " & storeResult.errValue.msg,
        format: format
      ))

    let casObject = storeResult.okValue
    let result = CasIntegrationResult(
      hash: casObject.hash,
      size: casObject.size,
      format: format,
      metadata: %*{
        "compressed": casObject.compressed,
        "chunks": casObject.chunks.len,
        "stored_at": $now()
      }
    )

    return types_fixed.ok[CasIntegrationResult, FormatError](result)

  except Exception as e:
    return types_fixed.err[CasIntegrationResult, FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to store package: " & e.msg,
      format: format
    ))

proc retrievePackageFromCas*(hash: string, cas: var CasManager): types_fixed.Result[seq[byte], FormatError] =
  ## Retrieve package format data from content-addressable storage
  try:
    let retrieveResult = cas.retrieveObject(hash)
    if not retrieveResult.isOk:
      return types_fixed.err[seq[byte], FormatError](FormatError(
        code: CasGeneralError,
        msg: "Failed to retrieve package from CAS: " & retrieveResult.errValue.msg,
        format: NpkBinary  # Default format for error
      ))

    return types_fixed.ok[seq[byte], FormatError](retrieveResult.okValue)

  except Exception as e:
    return types_fixed.err[seq[byte], FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to retrieve package: " & e.msg,
      format: NpkBinary
    ))

proc deduplicatePackages*(packages: seq[string], cas: var CasManager): types_fixed.Result[Table[string, seq[string]], FormatError] =
  ## Perform cross-format deduplication and return duplicate groups
  try:
    var hashGroups: Table[string, seq[string]] = initTable[string, seq[string]]()

    for packagePath in packages:
      if not fileExists(packagePath):
        continue

      let data = readFile(packagePath)
      let hash = calculateBlake3(data.toOpenArrayByte(0, data.len - 1).toSeq())

      if hash notin hashGroups:
        hashGroups[hash] = @[]
      hashGroups[hash].add(packagePath)

    # Filter to only return groups with duplicates
    var duplicates: Table[string, seq[string]] = initTable[string, seq[string]]()
    for hash, paths in hashGroups:
      if paths.len > 1:
        duplicates[hash] = paths

    return types_fixed.ok[Table[string, seq[string]], FormatError](duplicates)

  except Exception as e:
    return types_fixed.err[Table[string, seq[string]], FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to deduplicate packages: " & e.msg,
      format: NpkBinary
    ))

proc garbageCollectFormats*(cas: var CasManager, reachableHashes: seq[string] = @[]): types_fixed.Result[int, FormatError] =
  ## Garbage collect unreferenced package format objects
  try:
    let reachableSet = reachableHashes.toHashSet()
    let gcResult = cas.garbageCollect(reachableSet)

    if not gcResult.isOk:
      return types_fixed.err[int, FormatError](FormatError(
        code: CasGeneralError,
        msg: "Failed to garbage collect: " & gcResult.errValue.msg,
        format: NpkBinary
      ))

    return types_fixed.ok[int, FormatError](gcResult.okValue)

  except Exception as e:
    return types_fixed.err[int, FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to garbage collect: " & e.msg,
      format: NpkBinary
    ))

# =============================================================================
# Format Conversion Support
# =============================================================================

type
  ConversionPath* = object
    fromFormat*: PackageFormat
    toFormat*: PackageFormat
    supported*: bool
    description*: string
    requiresCas*: bool

proc getSupportedConversions*(): seq[ConversionPath] =
  ## Get list of supported format conversions
  @[
    ConversionPath(
      fromFormat: NprRecipe,
      toFormat: NpkBinary,
      supported: true,
      description: "Build recipe into binary package",
      requiresCas: true
    ),
    ConversionPath(
      fromFormat: NpkBinary,
      toFormat: NcaChunk,
      supported: true,
      description: "Store binary package in content-addressable storage",
      requiresCas: true
    ),
    ConversionPath(
      fromFormat: NpkBinary,
      toFormat: NssSnapshot,
      supported: true,
      description: "Include binary package in system snapshot",
      requiresCas: true
    ),
    ConversionPath(
      fromFormat: NcaChunk,
      toFormat: NpkBinary,
      supported: true,
      description: "Reconstruct binary package from chunks",
      requiresCas: true
    ),
    ConversionPath(
      fromFormat: NssSnapshot,
      toFormat: NpkBinary,
      supported: true,
      description: "Extract binary packages from snapshot",
      requiresCas: true
    )
  ]

proc canConvert*(fromFormat: PackageFormat, toFormat: PackageFormat): bool =
  ## Check if conversion between formats is supported
  let conversions = getSupportedConversions()
  for conversion in conversions:
    if conversion.fromFormat == fromFormat and conversion.toFormat == toFormat:
      return conversion.supported
  return false

proc convertPackageFormat*(fromPath: string, toPath: string,
                          fromFormat: PackageFormat, toFormat: PackageFormat,
                          cas: var CasManager): types_fixed.VoidResult[FormatError] =
  ## Convert package from one format to another using CAS
  try:
    if not canConvert(fromFormat, toFormat):
      return err[FormatError](FormatError(
        code: InvalidMetadata,
        msg: "Conversion not supported: " & $fromFormat & " -> " & $toFormat,
        format: fromFormat
      ))

    # Load source package
    if not fileExists(fromPath):
      return err[FormatError](FormatError(
        code: PackageNotFound,
        msg: "Source package not found: " & fromPath,
        format: fromFormat
      ))

    let sourceData = readFile(fromPath)
    let sourceBytes = sourceData.toOpenArrayByte(0, sourceData.len - 1).toSeq()

    # Store in CAS for conversion pipeline
    let storeResult = storePackageInCas(fromFormat, sourceBytes, cas)
    if not storeResult.isOk:
      return err[FormatError](storeResult.errValue)

    let casResult = storeResult.okValue

    # Retrieve and convert (simplified conversion logic)
    let retrieveResult = retrievePackageFromCas(casResult.hash, cas)
    if not retrieveResult.isOk:
      return err[FormatError](retrieveResult.errValue)

    let convertedData = retrieveResult.okValue

    # Write converted package
    let parentDir = toPath.parentDir()
    if not dirExists(parentDir):
      createDir(parentDir)

    writeFile(toPath, cast[string](convertedData))

    return types_fixed.ok(FormatError)

  except Exception as e:
    return err[FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to convert package: " & e.msg,
      format: fromFormat
    ))

# =============================================================================
# Unified Package Retrieval and Reconstruction
# =============================================================================

proc reconstructPackageFromCas*(hash: string, format: PackageFormat,
                               outputPath: string, cas: var CasManager): types_fixed.VoidResult[FormatError] =
  ## Reconstruct package from CAS storage with format-specific handling
  try:
    let retrieveResult = retrievePackageFromCas(hash, cas)
    if not retrieveResult.isOk:
      return err[FormatError](retrieveResult.errValue)

    let data = retrieveResult.okValue

    # Format-specific reconstruction logic
    case format:
    of NpkBinary:
      # NPK packages may be chunked, handle reconstruction
      writeFile(outputPath, cast[string](data))

    of NcaChunk:
      # NCA chunks are already individual pieces
      writeFile(outputPath, cast[string](data))

    of NprRecipe:
      # NPR recipes are plain text
      writeFile(outputPath, cast[string](data))

    of NssSnapshot:
      # NSS snapshots may need decompression
      writeFile(outputPath, cast[string](data))

    of NofOverlay:
      # NOF overlays are plain text
      writeFile(outputPath, cast[string](data))

    return types_fixed.ok(FormatError)

  except Exception as e:
    return err[FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to reconstruct package: " & e.msg,
      format: format
    ))

proc getPackageFormatStats*(cas: var CasManager): types_fixed.Result[JsonNode, FormatError] =
  ## Get statistics about package formats in CAS
  try:
    let casStats = cas.getStats()
    let objects = cas.listObjects()

    var formatCounts: Table[string, int] = initTable[string, int]()
    var totalSizes: Table[string, int64] = initTable[string, int64]()

    # Analyze objects by format (simplified detection)
    for objHash in objects:
      let retrieveResult = cas.retrieveObject(objHash)
      if retrieveResult.isOk:
        let data = retrieveResult.okValue
        let size = data.len.int64

        # Simple format detection based on content
        var detectedFormat = "unknown"
        if data.len >= 4:
          let header = cast[string](data[0..3])
          if header == "NCA\x01":
            detectedFormat = "nca"
          elif cast[string](data).contains("package \""):
            detectedFormat = "npr"
          elif cast[string](data).contains("overlay \""):
            detectedFormat = "nof"
          elif cast[string](data).contains("\"snapshot\""):
            detectedFormat = "nss"
          else:
            detectedFormat = "npk"

        formatCounts[detectedFormat] = formatCounts.getOrDefault(detectedFormat, 0) + 1
        totalSizes[detectedFormat] = totalSizes.getOrDefault(detectedFormat, 0) + size

    let stats = %*{
      "total_objects": casStats.objectCount,
      "total_size": casStats.totalSize,
      "compression_ratio": casStats.compressionRatio,
      "formats": formatCounts,
      "format_sizes": totalSizes
    }

    return types_fixed.ok[JsonNode, FormatError](stats)

  except Exception as e:
    return types_fixed.err[JsonNode, FormatError](FormatError(
      code: UnknownError,
      msg: "Failed to get format stats: " & e.msg,
      format: NpkBinary
    ))