## NEXTER Manifest Schema - Container Format
##
## **Purpose:**
## Defines the NEXTER (Nexus Container) manifest schema for lightweight containers.
## NEXTER containers provide isolated environments for development and deployment.
##
## **Design Principles:**
## - Lightweight container isolation
## - Base image support with CAS deduplication
## - Environment variable configuration
## - Namespace isolation
## - Ed25519 signature support
##
## **Requirements:**
## - Requirement 5.1: manifest.kdl, environment config, CAS chunks, signature
## - Requirement 5.2: container name, base image, packages, environment variables
## - Requirement 6.2: KDL format with chunk references by xxh3 hash
## - Requirement 6.5: exact versions and build hashes for dependencies

import std/[times, options, strutils, tables, algorithm]
import nip/manifest_parser

type
  # ============================================================================
  # NEXTER-Specific Types
  # ============================================================================

  NEXTERManifest* = object
    ## Complete NEXTER manifest for containers
    # Core identity
    name*: string
    version*: SemanticVersion
    buildDate*: DateTime

    # Container metadata
    metadata*: ContainerInfo
    provenance*: ProvenanceInfo
    buildConfig*: BuildConfiguration

    # Base configuration
    base*: BaseConfig

    # Environment variables
    environment*: Table[string, string]

    # CAS chunk references
    casChunks*: seq[ChunkReference]

    # Namespace configuration
    namespace*: ContainerNamespace

    # Startup configuration
    startup*: StartupConfig

    # Integrity
    buildHash*: string      ## xxh3-128 hash of build configuration
    signature*: SignatureInfo

  ContainerInfo* = object
    ## Container metadata
    description*: string
    homepage*: Option[string]
    license*: string
    author*: Option[string]
    maintainer*: Option[string]
    tags*: seq[string]
    purpose*: Option[string]  ## Container purpose (e.g., "development", "production")

  ProvenanceInfo* = object
    ## Complete provenance tracking
    source*: string          ## Source URL or repository
    sourceHash*: string      ## xxh3-128 hash of source
    upstream*: Option[string]  ## Upstream project URL
    buildTimestamp*: DateTime
    builder*: Option[string]   ## Who built this container

  BuildConfiguration* = object
    ## Build configuration for reproducibility
    configureFlags*: seq[string]
    compilerFlags*: seq[string]
    compilerVersion*: string
    targetArchitecture*: string
    libc*: string            ## musl, glibc
    allocator*: string       ## jemalloc, tcmalloc, default
    buildSystem*: string     ## cmake, meson, autotools, etc.

  BaseConfig* = object
    ## Base image configuration
    baseImage*: Option[string]  ## Base image name (e.g., "alpine", "debian")
    baseVersion*: Option[string]  ## Base image version
    packages*: seq[string]   ## Additional packages to include

  ChunkReference* = object
    ## Reference to a CAS chunk
    hash*: string            ## xxh3-128 hash
    size*: int64
    chunkType*: ChunkType
    path*: string            ## Relative path in container

  ChunkType* = enum
    ## Type of chunk content
    Binary, Library, Runtime, Config, Data, Base, Tools

  ContainerNamespace* = object
    ## Container namespace isolation configuration
    isolationType*: string   ## "full", "network", "pid", "ipc", "uts"
    capabilities*: seq[string]  ## Linux capabilities
    mounts*: seq[MountSpec]
    devices*: seq[DeviceSpec]

  MountSpec* = object
    ## Filesystem mount specification
    source*: string
    target*: string
    mountType*: string       ## "bind", "tmpfs", "devtmpfs"
    readOnly*: bool
    options*: seq[string]

  DeviceSpec* = object
    ## Device access specification
    path*: string
    deviceType*: string      ## "c" (character), "b" (block)
    major*: int
    minor*: int
    permissions*: string     ## "rwm"

  StartupConfig* = object
    ## Container startup configuration
    command*: seq[string]    ## Startup command
    workingDir*: string      ## Working directory
    user*: Option[string]    ## User to run as
    entrypoint*: Option[string]  ## Entrypoint script

  SignatureInfo* = object
    ## Ed25519 signature information
    algorithm*: string       ## "ed25519"
    keyId*: string
    signature*: string       ## Base64-encoded signature

  # ============================================================================
  # Error Types
  # ============================================================================

  NEXTERError* = object of CatchableError
    code*: NEXTERErrorCode
    context*: string

  NEXTERErrorCode* = enum
    InvalidManifest,
    MissingField,
    InvalidHash,
    InvalidSignature,
    InvalidConfiguration

# ============================================================================
# KDL Parsing - Minimal implementation to expose gaps via tests
# ============================================================================

proc parseNEXTERManifest*(kdl: string): NEXTERManifest =
  ## Parse NEXTER manifest from KDL format
  ##
  ## **Requirements:**
  ## - Requirement 5.2: Parse container name, base image, packages, environment variables
  ## - Requirement 6.2: Validate chunk references by xxh3 hash
  ##
  ## **Implementation Note:**
  ## This is a simple line-based parser that extracts key values from KDL format.
  ## It handles the specific structure generated by generateNEXTERManifest().

  var name = "unknown"
  var version = SemanticVersion(major: 1, minor: 0, patch: 0)
  var buildDate = now()
  var description = "Unknown"
  var license = "Unknown"
  var homepage = none[string]()
  var author = none[string]()
  var maintainer = none[string]()
  var purpose = none[string]()
  var tags: seq[string] = @[]
  var source = "unknown"
  var sourceHash = "xxh3-0000000000000000"
  var upstream = none[string]()
  var buildTimestamp = now()
  var builder = none[string]()
  var configureFlags: seq[string] = @[]
  var compilerFlags: seq[string] = @[]
  var compilerVersion = "unknown"
  var targetArchitecture = "x86_64"
  var libc = "musl"
  var allocator = "jemalloc"
  var buildSystem = "unknown"
  var baseImage = none[string]()
  var baseVersion = none[string]()
  var basePackages: seq[string] = @[]
  var environment = initTable[string, string]()
  var casChunks: seq[ChunkReference] = @[]
  var isolationType = "full"
  var capabilities: seq[string] = @[]
  var buildHash = "xxh3-0000000000000000"
  var signatureAlgorithm = "ed25519"
  var keyId = "unknown"
  var signature = ""
  var command: seq[string] = @[]
  var workingDir = "/"
  var user = none[string]()
  var entrypoint = none[string]()

  # Parse line by line
  let lines = kdl.split('\n')
  var inSection = ""
  var inSubsection = ""

  for line in lines:
    let trimmed = line.strip()

    # Skip empty lines and comments
    if trimmed.len == 0 or trimmed.startsWith("#"):
      continue

    # Extract container name and version
    if trimmed.startsWith("container"):
      let parts = trimmed.split('"')
      if parts.len >= 2:
        name = parts[1]
      inSection = "container"
      continue

    # Track sections
    if trimmed.endsWith("{"):
      if trimmed.startsWith("metadata"):
        inSection = "metadata"
      elif trimmed.startsWith("provenance"):
        inSection = "provenance"
      elif trimmed.startsWith("build_config"):
        inSection = "build_config"
      elif trimmed.startsWith("base"):
        inSection = "base"
      elif trimmed.startsWith("environment"):
        inSection = "environment"
      elif trimmed.startsWith("cas_chunks"):
        inSection = "cas_chunks"
      elif trimmed.startsWith("namespace"):
        inSection = "namespace"
      elif trimmed.startsWith("startup"):
        inSection = "startup"
      elif trimmed.startsWith("signature"):
        inSection = "signature"
      continue

    # End of section
    if trimmed == "}":
      inSection = ""
      inSubsection = ""
      continue

    # Parse key-value pairs
    if trimmed.contains("\""):
      let parts = trimmed.split('"')
      if parts.len >= 2:
        let key = parts[0].strip()
        let value = parts[1]

        case inSection:
        of "container":
          if key == "version":
            version = parseSemanticVersion(value)
          elif key == "build_date":
            buildDate = parse(value, "yyyy-MM-dd'T'HH:mm:ss'Z'")
        of "metadata":
          if key == "description":
            description = value
          elif key == "license":
            license = value
          elif key == "homepage":
            homepage = some(value)
          elif key == "author":
            author = some(value)
          elif key == "maintainer":
            maintainer = some(value)
          elif key == "purpose":
            purpose = some(value)
          elif key == "tags":
            tags = value.split(" ")
        of "provenance":
          if key == "source":
            source = value
          elif key == "source_hash":
            sourceHash = value
          elif key == "upstream":
            upstream = some(value)
          elif key == "build_timestamp":
            buildTimestamp = parse(value, "yyyy-MM-dd'T'HH:mm:ss'Z'")
          elif key == "builder":
            builder = some(value)
        of "build_config":
          if key == "configure_flags":
            configureFlags = value.split(" ")
          elif key == "compiler_flags":
            compilerFlags = value.split(" ")
          elif key == "compiler_version":
            compilerVersion = value
          elif key == "target_architecture":
            targetArchitecture = value
          elif key == "libc":
            libc = value
          elif key == "allocator":
            allocator = value
          elif key == "build_system":
            buildSystem = value
        of "base":
          if key == "image":
            baseImage = some(value)
          elif key == "version":
            baseVersion = some(value)
          elif key == "packages":
            basePackages = value.split(" ")
        of "environment":
          environment[key] = value
        of "namespace":
          if key == "isolation":
            isolationType = value
          elif key == "capabilities":
            capabilities = value.split(" ")
        of "startup":
          if key == "command":
            command = value.split(" ")
          elif key == "working_dir":
            workingDir = value
          elif key == "user":
            user = some(value)
          elif key == "entrypoint":
            entrypoint = some(value)
        of "signature":
          if key == "algorithm":
            signatureAlgorithm = value
          elif key == "key_id":
            keyId = value
          elif key == "signature":
            signature = value
        else:
          if key == "build_hash":
            buildHash = value
          discard

  result = NEXTERManifest(
    name: name,
    version: version,
    buildDate: buildDate,
    metadata: ContainerInfo(
      description: description,
      license: license,
      homepage: homepage,
      author: author,
      maintainer: maintainer,
      purpose: purpose,
      tags: tags
    ),
    provenance: ProvenanceInfo(
      source: source,
      sourceHash: sourceHash,
      upstream: upstream,
      buildTimestamp: buildTimestamp,
      builder: builder
    ),
    buildConfig: BuildConfiguration(
      configureFlags: configureFlags,
      compilerFlags: compilerFlags,
      compilerVersion: compilerVersion,
      targetArchitecture: targetArchitecture,
      libc: libc,
      allocator: allocator,
      buildSystem: buildSystem
    ),
    base: BaseConfig(
      baseImage: baseImage,
      baseVersion: baseVersion,
      packages: basePackages
    ),
    environment: environment,
    casChunks: casChunks,
    namespace: ContainerNamespace(
      isolationType: isolationType,
      capabilities: capabilities,
      mounts: @[],
      devices: @[]
    ),
    startup: StartupConfig(
      command: command,
      workingDir: workingDir,
      user: user,
      entrypoint: entrypoint
    ),
    buildHash: buildHash,
    signature: SignatureInfo(
      algorithm: signatureAlgorithm,
      keyId: keyId,
      signature: signature
    )
  )

# ============================================================================
# KDL Generation
# ============================================================================

proc generateNEXTERManifest*(manifest: NEXTERManifest): string =
  ## Generate KDL manifest from NEXTERManifest
  ##
  ## **Requirements:**
  ## - Requirement 5.2: Generate container name, base image, packages, environment variables
  ## - Requirement 6.4: Deterministic generation (same input = same output)
  ##
  ## **Determinism:** Fields are output in a fixed order to ensure same input = same output

  result = "container \"" & manifest.name & "\" {\n"

  # Core identity
  result.add("  version \"" & $manifest.version & "\"\n")
  result.add("  build_date \"" & manifest.buildDate.format("yyyy-MM-dd'T'HH:mm:ss'Z'") & "\"\n")
  result.add("\n")

  # Metadata section
  result.add("  metadata {\n")
  result.add("    description \"" & manifest.metadata.description & "\"\n")
  result.add("    license \"" & manifest.metadata.license & "\"\n")
  if manifest.metadata.homepage.isSome:
    result.add("    homepage \"" & manifest.metadata.homepage.get() & "\"\n")
  if manifest.metadata.author.isSome:
    result.add("    author \"" & manifest.metadata.author.get() & "\"\n")
  if manifest.metadata.maintainer.isSome:
    result.add("    maintainer \"" & manifest.metadata.maintainer.get() & "\"\n")
  if manifest.metadata.purpose.isSome:
    result.add("    purpose \"" & manifest.metadata.purpose.get() & "\"\n")
  if manifest.metadata.tags.len > 0:
    result.add("    tags \"" & manifest.metadata.tags.join(" ") & "\"\n")
  result.add("  }\n\n")

  # Provenance section
  result.add("  provenance {\n")
  result.add("    source \"" & manifest.provenance.source & "\"\n")
  result.add("    source_hash \"" & manifest.provenance.sourceHash & "\"\n")
  if manifest.provenance.upstream.isSome:
    result.add("    upstream \"" & manifest.provenance.upstream.get() & "\"\n")
  result.add("    build_timestamp \"" & manifest.provenance.buildTimestamp.format("yyyy-MM-dd'T'HH:mm:ss'Z'") & "\"\n")
  if manifest.provenance.builder.isSome:
    result.add("    builder \"" & manifest.provenance.builder.get() & "\"\n")
  result.add("  }\n\n")

  # Build configuration section
  result.add("  build_config {\n")
  if manifest.buildConfig.configureFlags.len > 0:
    result.add("    configure_flags \"" & manifest.buildConfig.configureFlags.join(" ") & "\"\n")
  if manifest.buildConfig.compilerFlags.len > 0:
    result.add("    compiler_flags \"" & manifest.buildConfig.compilerFlags.join(" ") & "\"\n")
  result.add("    compiler_version \"" & manifest.buildConfig.compilerVersion & "\"\n")
  result.add("    target_architecture \"" & manifest.buildConfig.targetArchitecture & "\"\n")
  result.add("    libc \"" & manifest.buildConfig.libc & "\"\n")
  result.add("    allocator \"" & manifest.buildConfig.allocator & "\"\n")
  result.add("    build_system \"" & manifest.buildConfig.buildSystem & "\"\n")
  result.add("  }\n\n")

  # Base configuration section
  result.add("  base {\n")
  if manifest.base.baseImage.isSome:
    result.add("    image \"" & manifest.base.baseImage.get() & "\"\n")
  if manifest.base.baseVersion.isSome:
    result.add("    version \"" & manifest.base.baseVersion.get() & "\"\n")
  if manifest.base.packages.len > 0:
    result.add("    packages \"" & manifest.base.packages.join(" ") & "\"\n")
  result.add("  }\n\n")

  # Environment variables section
  if manifest.environment.len > 0:
    result.add("  environment {\n")
    # Sort keys for determinism
    var sortedKeys = newSeq[string]()
    for key in manifest.environment.keys:
      sortedKeys.add(key)
    sortedKeys.sort()
    for key in sortedKeys:
      result.add("    " & key & " \"" & manifest.environment[key] & "\"\n")
    result.add("  }\n\n")

  # CAS chunks section
  if manifest.casChunks.len > 0:
    result.add("  cas_chunks {\n")
    for chunk in manifest.casChunks:
      result.add("    chunk \"" & chunk.hash & "\" {\n")
      result.add("      size " & $chunk.size & "\n")
      result.add("      type \"" & ($chunk.chunkType).toLowerAscii() & "\"\n")
      result.add("      path \"" & chunk.path & "\"\n")
      result.add("    }\n")
    result.add("  }\n\n")

  # Namespace configuration section
  result.add("  namespace {\n")
  result.add("    isolation \"" & manifest.namespace.isolationType & "\"\n")
  if manifest.namespace.capabilities.len > 0:
    result.add("    capabilities \"" & manifest.namespace.capabilities.join(" ") & "\"\n")

  # Mounts
  if manifest.namespace.mounts.len > 0:
    result.add("\n    mounts {\n")
    for mount in manifest.namespace.mounts:
      result.add("      mount {\n")
      result.add("        source \"" & mount.source & "\"\n")
      result.add("        target \"" & mount.target & "\"\n")
      result.add("        type \"" & mount.mountType & "\"\n")
      result.add("        read_only " & $mount.readOnly & "\n")
      if mount.options.len > 0:
        result.add("        options \"" & mount.options.join(",") & "\"\n")
      result.add("      }\n")
    result.add("    }\n")

  # Devices
  if manifest.namespace.devices.len > 0:
    result.add("\n    devices {\n")
    for device in manifest.namespace.devices:
      result.add("      device {\n")
      result.add("        path \"" & device.path & "\"\n")
      result.add("        type \"" & device.deviceType & "\"\n")
      result.add("        major " & $device.major & "\n")
      result.add("        minor " & $device.minor & "\n")
      result.add("        permissions \"" & device.permissions & "\"\n")
      result.add("      }\n")
    result.add("    }\n")

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

  # Startup configuration section
  result.add("  startup {\n")
  if manifest.startup.command.len > 0:
    result.add("    command \"" & manifest.startup.command.join(" ") & "\"\n")
  result.add("    working_dir \"" & manifest.startup.workingDir & "\"\n")
  if manifest.startup.user.isSome:
    result.add("    user \"" & manifest.startup.user.get() & "\"\n")
  if manifest.startup.entrypoint.isSome:
    result.add("    entrypoint \"" & manifest.startup.entrypoint.get() & "\"\n")
  result.add("  }\n\n")

  # Build hash
  result.add("  build_hash \"" & manifest.buildHash & "\"\n\n")

  # Signature
  result.add("  signature {\n")
  result.add("    algorithm \"" & manifest.signature.algorithm & "\"\n")
  result.add("    key_id \"" & manifest.signature.keyId & "\"\n")
  result.add("    signature \"" & manifest.signature.signature & "\"\n")
  result.add("  }\n")

  result.add("}\n")

# ============================================================================
# Validation
# ============================================================================

proc validateNEXTERManifest*(manifest: NEXTERManifest): seq[string] =
  ## Validate NEXTER manifest and return list of issues
  ##
  ## **Requirements:**
  ## - Requirement 6.3: Validate all required fields and hash formats

  result = @[]

  # Validate name
  if manifest.name.len == 0:
    result.add("Container name cannot be empty")

  # Validate build hash format (xxh3-128)
  if manifest.buildHash.len > 0 and not manifest.buildHash.startsWith("xxh3-"):
    result.add("Build hash must use xxh3-128 format (xxh3-...)")

  # Validate source hash format
  if manifest.provenance.sourceHash.len > 0 and not manifest.provenance.sourceHash.startsWith("xxh3-"):
    result.add("Source hash must use xxh3-128 format (xxh3-...)")

  # Validate CAS chunks have xxh3 hashes
  for chunk in manifest.casChunks:
    if not chunk.hash.startsWith("xxh3-"):
      result.add("Chunk hash must use xxh3-128 format (xxh3-...)")
    if chunk.size <= 0:
      result.add("Chunk size must be positive")

  # Validate startup configuration
  if manifest.startup.workingDir.len == 0:
    result.add("Startup working_dir cannot be empty")

  # Validate signature
  if manifest.signature.algorithm.len > 0 and manifest.signature.algorithm != "ed25519":
    result.add("Signature algorithm must be 'ed25519'")
  if manifest.signature.keyId.len == 0:
    result.add("Signature key_id cannot be empty")
  if manifest.signature.signature.len == 0:
    result.add("Signature value cannot be empty")

# ============================================================================
# Convenience Functions
# ============================================================================

proc `$`*(manifest: NEXTERManifest): string =
  ## Convert NEXTER manifest to human-readable string
  result = "NEXTER Container: " & manifest.name & " v" & $manifest.version & "\n"
  result.add("Build Date: " & manifest.buildDate.format("yyyy-MM-dd HH:mm:ss") & "\n")
  result.add("License: " & manifest.metadata.license & "\n")
  result.add("Build Hash: " & manifest.buildHash & "\n")
  result.add("CAS Chunks: " & $manifest.casChunks.len & "\n")
  result.add("Isolation: " & manifest.namespace.isolationType & "\n")
