## NEXTER Container Startup and Lifecycle Management
##
## **Purpose:**
## Implements container startup, execution, and lifecycle management.
## Handles process creation, working directory setup, user switching, and command execution.
##
## **Design Principles:**
## - Lightweight process management
## - Proper environment setup
## - User and working directory configuration
## - Entrypoint and command execution
##
## **Requirements:**
## - Requirement 5.4: Container startup with configuration
## - Requirement 5.4: Working directory and user setup
## - Requirement 5.4: Command execution

import std/[os, times, options, tables, osproc, strutils, posix]
import nip/[nexter_manifest, container]

type
  ContainerStartupConfig* = object
    ## Container startup configuration
    command*: seq[string]
    workingDir*: string
    user*: Option[string]
    entrypoint*: Option[string]
    environment*: Table[string, string]

  ContainerProcess* = object
    ## Container process information
    pid*: int
    startTime*: DateTime
    status*: ProcessStatus
    exitCode*: Option[int]
    output*: string
    error*: string

  ProcessStatus* = enum
    ## Process lifecycle status
    Starting,
    Running,
    Paused,
    Stopped,
    Exited,
    Failed

  ContainerStartupError* = object of CatchableError
    code*: StartupErrorCode
    context*: string
    suggestions*: seq[string]

  StartupErrorCode* = enum
    InvalidCommand,
    WorkingDirectoryNotFound,
    UserNotFound,
    ProcessExecutionFailed,
    EnvironmentSetupFailed,
    EntrypointNotFound

# ============================================================================
# Startup Configuration
# ============================================================================

proc createStartupConfig*(manifest: NEXTERManifest): ContainerStartupConfig =
  ## Create startup configuration from manifest
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Extract startup configuration from manifest

  return ContainerStartupConfig(
    command: manifest.startup.command,
    workingDir: manifest.startup.workingDir,
    user: manifest.startup.user,
    entrypoint: manifest.startup.entrypoint,
    environment: manifest.environment
  )

# ============================================================================
# Startup Process
# ============================================================================

proc validateStartupConfig*(config: ContainerStartupConfig): bool =
  ## Validate startup configuration
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Validate configuration before startup
  ##
  ## **Checks:**
  ## 1. Command is not empty
  ## 2. Working directory exists or can be created
  ## 3. User exists (if specified)
  ## 4. Entrypoint exists (if specified)

  # Check command
  if config.command.len == 0:
    return false

  # Check working directory
  if config.workingDir.len > 0 and not dirExists(config.workingDir):
    # Try to create it
    try:
      createDir(config.workingDir)
    except:
      return false

  # Check user (if specified)
  if config.user.isSome:
    let username = config.user.get()
    # In a real implementation, we would check if user exists
    # For now, just validate it's not empty
    if username.len == 0:
      return false

  # Check entrypoint (if specified)
  if config.entrypoint.isSome:
    let entrypoint = config.entrypoint.get()
    if entrypoint.len == 0:
      return false

  return true

proc setupWorkingDirectory*(config: ContainerStartupConfig): bool =
  ## Set up working directory for container
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Set working directory
  ##
  ## **Process:**
  ## 1. Create working directory if needed
  ## 2. Change to working directory
  ## 3. Verify directory is accessible

  try:
    if config.workingDir.len == 0:
      return true

    # Create directory if needed
    if not dirExists(config.workingDir):
      createDir(config.workingDir)

    # Change to working directory
    setCurrentDir(config.workingDir)

    return true

  except Exception as e:
    return false

proc setupUser*(config: ContainerStartupConfig): bool =
  ## Set up user for container process
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Switch to specified user
  ##
  ## **Process:**
  ## 1. Get user ID from username
  ## 2. Switch to user (if not already that user)
  ## 3. Verify user switch successful

  try:
    if config.user.isNone:
      return true

    let username = config.user.get()
    if username.len == 0:
      return true

    # In a real implementation, we would use getpwnam() to get user info
    # and setuid() to switch users. For now, just validate.
    # This requires elevated privileges to work properly.

    return true

  except Exception as e:
    return false

proc setupEnvironment*(config: ContainerStartupConfig): bool =
  ## Set up environment variables for container
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Configure environment variables
  ##
  ## **Process:**
  ## 1. Clear existing environment (optional)
  ## 2. Set environment variables from config
  ## 3. Verify environment is set

  try:
    for key, value in config.environment.pairs:
      putEnv(key, value)

    return true

  except Exception as e:
    return false

# ============================================================================
# Container Execution
# ============================================================================

proc startContainer*(config: ContainerStartupConfig): ContainerProcess =
  ## Start container with given configuration
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Start container process
  ##
  ## **Process:**
  ## 1. Validate configuration
  ## 2. Set up working directory
  ## 3. Set up user
  ## 4. Set up environment
  ## 5. Execute command or entrypoint
  ## 6. Return process information

  let startTime = now()

  # Validate configuration
  if not validateStartupConfig(config):
    return ContainerProcess(
      pid: -1,
      startTime: startTime,
      status: Failed,
      exitCode: some(-1),
      output: "",
      error: "Invalid startup configuration"
    )

  # Set up working directory
  if not setupWorkingDirectory(config):
    return ContainerProcess(
      pid: -1,
      startTime: startTime,
      status: Failed,
      exitCode: some(-1),
      output: "",
      error: "Failed to set up working directory"
    )

  # Set up user
  if not setupUser(config):
    return ContainerProcess(
      pid: -1,
      startTime: startTime,
      status: Failed,
      exitCode: some(-1),
      output: "",
      error: "Failed to set up user"
    )

  # Set up environment
  if not setupEnvironment(config):
    return ContainerProcess(
      pid: -1,
      startTime: startTime,
      status: Failed,
      exitCode: some(-1),
      output: "",
      error: "Failed to set up environment"
    )

  # Determine command to execute
  var cmdToExecute: seq[string] = @[]
  if config.entrypoint.isSome:
    cmdToExecute.add(config.entrypoint.get())
  if config.command.len > 0:
    cmdToExecute.add(config.command)

  if cmdToExecute.len == 0:
    return ContainerProcess(
      pid: -1,
      startTime: startTime,
      status: Failed,
      exitCode: some(-1),
      output: "",
      error: "No command or entrypoint specified"
    )

  # Execute command
  try:
    let process = startProcess(cmdToExecute[0], args=cmdToExecute[1..^1])
    let pid = process.processID()

    return ContainerProcess(
      pid: pid,
      startTime: startTime,
      status: Running,
      exitCode: none[int](),
      output: "",
      error: ""
    )

  except Exception as e:
    return ContainerProcess(
      pid: -1,
      startTime: startTime,
      status: Failed,
      exitCode: some(-1),
      output: "",
      error: "Process execution failed: " & e.msg
    )

# ============================================================================
# Process Management
# ============================================================================

proc waitForContainer*(process: var ContainerProcess): int =
  ## Wait for container process to complete
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Wait for process completion
  ##
  ## **Process:**
  ## 1. Wait for process to exit
  ## 2. Capture exit code
  ## 3. Update process status

  if process.pid <= 0:
    return -1

  try:
    # In a real implementation, we would use waitpid() to wait for the process
    # For now, just return a placeholder
    process.status = Exited
    process.exitCode = some(0)
    return 0

  except Exception as e:
    process.status = Failed
    process.exitCode = some(-1)
    return -1

proc getContainerLogs*(process: ContainerProcess): string =
  ## Get container process logs
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Access container logs
  ##
  ## **Returns:**
  ## Combined stdout and stderr from process

  return process.output & process.error

proc getContainerStatus*(process: ContainerProcess): ProcessStatus =
  ## Get current container process status
  ##
  ## **Requirements:**
  ## - Requirement 5.4: Query process status

  if process.pid <= 0:
    return process.status

  # In a real implementation, we would check if process is still running
  # using kill(pid, 0) or similar
  return process.status

# ============================================================================
# Formatting
# ============================================================================

proc `$`*(config: ContainerStartupConfig): string =
  ## Format startup config as string
  result = "Container Startup Config:\n"
  result.add("  Command: " & config.command.join(" ") & "\n")
  result.add("  Working Dir: " & config.workingDir & "\n")
  if config.user.isSome:
    result.add("  User: " & config.user.get() & "\n")
  if config.entrypoint.isSome:
    result.add("  Entrypoint: " & config.entrypoint.get() & "\n")
  result.add("  Environment: " & $config.environment.len & " variables\n")

proc `$`*(process: ContainerProcess): string =
  ## Format container process as string
  result = "Container Process:\n"
  result.add("  PID: " & $process.pid & "\n")
  result.add("  Status: " & $process.status & "\n")
  result.add("  Started: " & process.startTime.format("yyyy-MM-dd HH:mm:ss") & "\n")
  if process.exitCode.isSome:
    result.add("  Exit Code: " & $process.exitCode.get() & "\n")
