use crate::filesystem::normalize_container_destination;
use crate::isolation::{NamespaceConfig, UserNamespaceConfig};
use crate::network::EgressPolicy;
use crate::resources::ResourceLimits;
use crate::security::GVisorPlatform;
use std::fs::OpenOptions;
use std::os::unix::fs::FileTypeExt;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::time::Duration;

fn open_dev_urandom() -> crate::error::Result<std::fs::File> {
    let file = OpenOptions::new()
        .read(true)
        .custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC)
        .open("/dev/urandom")
        .map_err(|e| {
            crate::error::NucleusError::ConfigError(format!(
                "Failed to open /dev/urandom for container ID generation: {}",
                e
            ))
        })?;

    let metadata = file.metadata().map_err(|e| {
        crate::error::NucleusError::ConfigError(format!("Failed to stat /dev/urandom: {}", e))
    })?;
    if !metadata.file_type().is_char_device() {
        return Err(crate::error::NucleusError::ConfigError(
            "/dev/urandom is not a character device".to_string(),
        ));
    }

    Ok(file)
}

/// Generate a unique 32-hex-char container ID (128-bit) using /dev/urandom.
pub fn generate_container_id() -> crate::error::Result<String> {
    use std::io::Read;

    let mut buf = [0u8; 16];
    let mut file = open_dev_urandom()?;
    file.read_exact(&mut buf).map_err(|e| {
        crate::error::NucleusError::ConfigError(format!(
            "Failed to read secure random bytes for container ID generation: {}",
            e
        ))
    })?;
    Ok(hex::encode(buf))
}

/// Trust level for a container workload.
///
/// Determines the minimum isolation guarantees the runtime must enforce.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum TrustLevel {
    /// Native kernel isolation (namespaces + seccomp + Landlock) is acceptable.
    Trusted,
    /// Requires gVisor; refuses to start without it unless degraded mode is allowed.
    #[default]
    Untrusted,
}

/// Service mode for the container.
///
/// Determines whether the container runs as an ephemeral agent sandbox
/// or a long-running production service with stricter requirements.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum ServiceMode {
    /// Ephemeral agent workload (default). Allows degraded fallbacks.
    #[default]
    Agent,
    /// Long-running production service. Enforces strict security invariants:
    /// - Forbids degraded security, chroot fallback, and host networking
    /// - Requires cgroup resource limits
    /// - Requires pivot_root (no chroot fallback)
    /// - Requires explicit rootfs path (no host bind mounts)
    Production,
}

/// CLI-level runtime selection.
///
/// Parsed by clap at argument time – invalid values are caught immediately.
/// The variant triggers additional logic in `apply_runtime_selection`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum RuntimeSelection {
    /// gVisor sandbox runtime (default). Provides kernel-level isolation.
    #[value(name = "gvisor")]
    GVisor,
    /// Native kernel isolation (namespaces + seccomp + Landlock).
    #[value(name = "native")]
    Native,
}

/// CLI-level network mode selection.
///
/// Parsed by clap at argument time. The `bridge` variant carries additional
/// configuration that is attached after parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum NetworkModeArg {
    /// No network (default).
    #[value(name = "none")]
    None,
    /// Share host network namespace (dangerous).
    #[value(name = "host")]
    Host,
    /// Virtual bridge with veth pair.
    #[value(name = "bridge")]
    Bridge,
}

/// Required host kernel lockdown mode, when asserted by the runtime.
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum KernelLockdownMode {
    /// Integrity mode blocks kernel writes from privileged userspace.
    Integrity,
    /// Confidentiality mode additionally blocks kernel data disclosure paths.
    Confidentiality,
}

impl KernelLockdownMode {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Integrity => "integrity",
            Self::Confidentiality => "confidentiality",
        }
    }

    pub fn accepts(self, active: Self) -> bool {
        match self {
            Self::Integrity => matches!(active, Self::Integrity | Self::Confidentiality),
            Self::Confidentiality => matches!(active, Self::Confidentiality),
        }
    }
}

/// Health check configuration for long-running services.
#[derive(Debug, Clone)]
pub struct HealthCheck {
    /// Command to run inside the container to check health.
    pub command: Vec<String>,
    /// Interval between health checks.
    pub interval: Duration,
    /// Number of consecutive failures before marking unhealthy.
    pub retries: u32,
    /// Grace period after start before health checks begin.
    pub start_period: Duration,
    /// Timeout for each health check execution.
    pub timeout: Duration,
}

impl Default for HealthCheck {
    fn default() -> Self {
        Self {
            command: Vec::new(),
            interval: Duration::from_secs(30),
            retries: 3,
            start_period: Duration::from_secs(5),
            timeout: Duration::from_secs(5),
        }
    }
}

/// Secrets configuration for mounting secret files into the container.
#[derive(Debug, Clone)]
pub struct SecretMount {
    /// Source path on the host (or Nix store path).
    pub source: PathBuf,
    /// Destination path inside the container.
    pub dest: PathBuf,
    /// File mode (default: 0o400, read-only by owner).
    pub mode: u32,
}

/// Runtime identity for the workload process inside the container.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcessIdentity {
    /// Primary user ID for the workload process.
    pub uid: u32,
    /// Primary group ID for the workload process.
    pub gid: u32,
    /// Supplementary group IDs for the workload process.
    pub additional_gids: Vec<u32>,
}

impl ProcessIdentity {
    /// Root identity (the historical default).
    pub fn root() -> Self {
        Self {
            uid: 0,
            gid: 0,
            additional_gids: Vec::new(),
        }
    }

    /// Returns true when the workload keeps the default root identity.
    pub fn is_root(&self) -> bool {
        self.uid == 0 && self.gid == 0 && self.additional_gids.is_empty()
    }
}

impl Default for ProcessIdentity {
    fn default() -> Self {
        Self::root()
    }
}

/// Source backing for a volume mount.
#[derive(Debug, Clone)]
pub enum VolumeSource {
    /// Bind mount a host path into the container.
    Bind { source: PathBuf },
    /// Mount a fresh tmpfs at the destination.
    Tmpfs { size: Option<String> },
}

/// Volume configuration for mounting persistent or ephemeral storage.
#[derive(Debug, Clone)]
pub struct VolumeMount {
    /// Backing storage for the volume.
    pub source: VolumeSource,
    /// Destination path inside the container.
    pub dest: PathBuf,
    /// Whether the volume is mounted read-only.
    pub read_only: bool,
}

/// Readiness probe configuration.
#[derive(Debug, Clone)]
pub enum ReadinessProbe {
    /// Run a command; ready when it exits 0.
    Exec { command: Vec<String> },
    /// Check TCP port connectivity.
    TcpPort(u16),
    /// Use sd_notify protocol (service sends READY=1).
    SdNotify,
}

/// Container configuration
#[derive(Debug, Clone)]
pub struct ContainerConfig {
    /// Unique container ID (auto-generated 32 hex chars, 128-bit)
    pub id: String,

    /// User-supplied container name (optional, defaults to ID)
    pub name: String,

    /// Command to execute in the container
    pub command: Vec<String>,

    /// Context directory to pre-populate (optional)
    pub context_dir: Option<PathBuf>,

    /// Resource limits
    pub limits: ResourceLimits,

    /// Namespace configuration
    pub namespaces: NamespaceConfig,

    /// User namespace configuration (for rootless mode)
    pub user_ns_config: Option<UserNamespaceConfig>,

    /// Hostname to set in UTS namespace (optional)
    pub hostname: Option<String>,

    /// Whether to use gVisor runtime
    pub use_gvisor: bool,

    /// Trust level for this workload
    pub trust_level: TrustLevel,

    /// Network mode
    pub network: crate::network::NetworkMode,

    /// Context mode (copy or bind mount)
    pub context_mode: crate::filesystem::ContextMode,

    /// Allow degraded security behavior if a hardening layer cannot be applied
    pub allow_degraded_security: bool,

    /// Allow chroot fallback when pivot_root fails (weaker isolation)
    pub allow_chroot_fallback: bool,

    /// Require explicit opt-in for host networking
    pub allow_host_network: bool,

    /// Mount /proc read-only inside the container
    pub proc_readonly: bool,

    /// Service mode (agent vs production)
    pub service_mode: ServiceMode,

    /// Pre-built rootfs path (Nix store path). When set, this is bind-mounted
    /// as the container root instead of bind-mounting host /bin, /usr, /lib, etc.
    pub rootfs_path: Option<PathBuf>,

    /// Egress policy for audited outbound network access.
    pub egress_policy: Option<EgressPolicy>,

    /// Health check configuration for long-running services.
    pub health_check: Option<HealthCheck>,

    /// Readiness probe for service startup detection.
    pub readiness_probe: Option<ReadinessProbe>,

    /// Secret files to mount into the container.
    pub secrets: Vec<SecretMount>,

    /// Volume mounts to attach to the container filesystem.
    pub volumes: Vec<VolumeMount>,

    /// Environment variables to pass to the container process.
    pub environment: Vec<(String, String)>,

    /// Runtime uid/gid and supplementary groups for the workload process.
    pub process_identity: ProcessIdentity,

    /// Desired topology config hash for reconciliation change detection.
    pub config_hash: Option<u64>,

    /// Enable sd_notify integration (pass NOTIFY_SOCKET into container).
    pub sd_notify: bool,

    /// Require the host kernel to be in at least this lockdown mode.
    pub required_kernel_lockdown: Option<KernelLockdownMode>,

    /// Verify context contents before executing the workload.
    pub verify_context_integrity: bool,

    /// Verify rootfs attestation manifest before mounting it.
    pub verify_rootfs_attestation: bool,

    /// Request kernel logging for denied seccomp decisions when supported.
    pub seccomp_log_denied: bool,

    /// Select the gVisor platform backend.
    pub gvisor_platform: GVisorPlatform,

    /// Path to a per-service seccomp profile (JSON, OCI subset format).
    /// When set, this profile is used instead of the built-in allowlist.
    pub seccomp_profile: Option<PathBuf>,

    /// Expected SHA-256 hash of the seccomp profile file for integrity verification.
    pub seccomp_profile_sha256: Option<String>,

    /// Seccomp operating mode.
    pub seccomp_mode: SeccompMode,

    /// Path to write seccomp trace log (NDJSON) when seccomp_mode == Trace.
    pub seccomp_trace_log: Option<PathBuf>,

    /// Additional syscalls to allow beyond the built-in default allowlist.
    /// Each entry is a syscall name (e.g. "io_uring_setup", "sysinfo").
    /// These are merged into the built-in filter; they do NOT replace it.
    pub seccomp_allow_syscalls: Vec<String>,

    /// Path to capability policy file (TOML).
    pub caps_policy: Option<PathBuf>,

    /// Expected SHA-256 hash of the capability policy file.
    pub caps_policy_sha256: Option<String>,

    /// Path to Landlock policy file (TOML).
    pub landlock_policy: Option<PathBuf>,

    /// Expected SHA-256 hash of the Landlock policy file.
    pub landlock_policy_sha256: Option<String>,

    /// OCI lifecycle hooks to execute at various container lifecycle points.
    pub hooks: Option<crate::security::OciHooks>,

    /// Path to write the container PID (OCI --pid-file).
    pub pid_file: Option<PathBuf>,

    /// Path to AF_UNIX socket for console pseudo-terminal master (OCI --console-socket).
    pub console_socket: Option<PathBuf>,

    /// Override OCI bundle directory path (OCI --bundle).
    pub bundle_dir: Option<PathBuf>,

    /// Override root directory for state storage (--root).
    /// When set, ContainerStateManager uses this instead of the default.
    pub state_root: Option<PathBuf>,
}

/// Seccomp operating mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum SeccompMode {
    /// Normal enforcement – deny unlisted syscalls.
    #[default]
    Enforce,
    /// Trace mode – allow all syscalls but log them for profile generation.
    /// Development only; rejected in production mode.
    Trace,
}

impl ContainerConfig {
    /// Create a new container config with a random ID.
    ///
    /// # Panics
    /// Panics if secure random bytes cannot be read from `/dev/urandom`.
    pub fn try_new(name: Option<String>, command: Vec<String>) -> crate::error::Result<Self> {
        Self::try_new_with_id(None, name, command)
    }

    /// Create a new container config, optionally using a pre-generated ID.
    ///
    /// When `preset_id` is `Some`, it is used as the container ID instead of
    /// generating a new one. This is used by `--detach` to ensure the outer
    /// CLI process and the systemd-managed inner process share the same ID.
    pub fn try_new_with_id(
        preset_id: Option<String>,
        name: Option<String>,
        command: Vec<String>,
    ) -> crate::error::Result<Self> {
        let id = match preset_id {
            Some(id) => {
                // Validate preset ID: must be exactly 32 hex chars
                if id.len() != 32 || !id.chars().all(|c| c.is_ascii_hexdigit()) {
                    return Err(crate::error::NucleusError::ConfigError(format!(
                        "Invalid preset container ID '{}': must be 32 hex characters",
                        id
                    )));
                }
                id
            }
            None => generate_container_id()?,
        };
        let name = name.unwrap_or_else(|| id.clone());
        Ok(Self {
            id,
            name: name.clone(),
            command,
            context_dir: None,
            limits: ResourceLimits::default(),
            namespaces: NamespaceConfig::default(),
            user_ns_config: None,
            hostname: Some(name),
            use_gvisor: true,
            trust_level: TrustLevel::default(),
            network: crate::network::NetworkMode::None,
            context_mode: crate::filesystem::ContextMode::Copy,
            allow_degraded_security: false,
            allow_chroot_fallback: false,
            allow_host_network: false,
            proc_readonly: true,
            service_mode: ServiceMode::default(),
            rootfs_path: None,
            egress_policy: None,
            health_check: None,
            readiness_probe: None,
            secrets: Vec::new(),
            volumes: Vec::new(),
            environment: Vec::new(),
            process_identity: ProcessIdentity::default(),
            config_hash: None,
            sd_notify: false,
            required_kernel_lockdown: None,
            verify_context_integrity: false,
            verify_rootfs_attestation: false,
            seccomp_log_denied: false,
            gvisor_platform: GVisorPlatform::default(),
            seccomp_profile: None,
            seccomp_profile_sha256: None,
            seccomp_mode: SeccompMode::default(),
            seccomp_trace_log: None,
            seccomp_allow_syscalls: Vec::new(),
            caps_policy: None,
            caps_policy_sha256: None,
            landlock_policy: None,
            landlock_policy_sha256: None,
            hooks: None,
            pid_file: None,
            console_socket: None,
            bundle_dir: None,
            state_root: None,
        })
    }

    /// Enable rootless mode with user namespace mapping
    #[must_use]
    pub fn with_rootless(mut self) -> Self {
        self.namespaces.user = true;
        self.user_ns_config = Some(UserNamespaceConfig::rootless());
        self
    }

    /// Configure custom user namespace mapping
    #[must_use]
    pub fn with_user_namespace(mut self, config: UserNamespaceConfig) -> Self {
        self.namespaces.user = true;
        self.user_ns_config = Some(config);
        self
    }

    #[must_use]
    pub fn with_context(mut self, dir: PathBuf) -> Self {
        self.context_dir = Some(dir);
        self
    }

    #[must_use]
    pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
        self.limits = limits;
        self
    }

    #[must_use]
    pub fn with_namespaces(mut self, namespaces: NamespaceConfig) -> Self {
        self.namespaces = namespaces;
        self
    }

    #[must_use]
    pub fn with_hostname(mut self, hostname: Option<String>) -> Self {
        self.hostname = hostname;
        self
    }

    #[must_use]
    pub fn with_gvisor(mut self, enabled: bool) -> Self {
        self.use_gvisor = enabled;
        self
    }

    #[must_use]
    pub fn with_trust_level(mut self, level: TrustLevel) -> Self {
        self.trust_level = level;
        self
    }

    /// Enable OCI bundle runtime path (always OCI for gVisor).
    #[must_use]
    pub fn with_oci_bundle(mut self) -> Self {
        self.use_gvisor = true;
        self
    }

    #[must_use]
    pub fn with_network(mut self, mode: crate::network::NetworkMode) -> Self {
        self.network = mode;
        self
    }

    #[must_use]
    pub fn with_context_mode(mut self, mode: crate::filesystem::ContextMode) -> Self {
        self.context_mode = mode;
        self
    }

    #[must_use]
    pub fn with_allow_degraded_security(mut self, allow: bool) -> Self {
        self.allow_degraded_security = allow;
        self
    }

    #[must_use]
    pub fn with_allow_chroot_fallback(mut self, allow: bool) -> Self {
        self.allow_chroot_fallback = allow;
        self
    }

    #[must_use]
    pub fn with_allow_host_network(mut self, allow: bool) -> Self {
        self.allow_host_network = allow;
        self
    }

    #[must_use]
    pub fn with_proc_readonly(mut self, proc_readonly: bool) -> Self {
        self.proc_readonly = proc_readonly;
        self
    }

    #[must_use]
    pub fn with_service_mode(mut self, mode: ServiceMode) -> Self {
        self.service_mode = mode;
        self
    }

    #[must_use]
    pub fn with_rootfs_path(mut self, path: PathBuf) -> Self {
        self.rootfs_path = Some(path);
        self
    }

    #[must_use]
    pub fn with_egress_policy(mut self, policy: EgressPolicy) -> Self {
        self.egress_policy = Some(policy);
        self
    }

    #[must_use]
    pub fn with_health_check(mut self, hc: HealthCheck) -> Self {
        self.health_check = Some(hc);
        self
    }

    #[must_use]
    pub fn with_readiness_probe(mut self, probe: ReadinessProbe) -> Self {
        self.readiness_probe = Some(probe);
        self
    }

    #[must_use]
    pub fn with_secret(mut self, secret: SecretMount) -> Self {
        self.secrets.push(secret);
        self
    }

    #[must_use]
    pub fn with_volume(mut self, volume: VolumeMount) -> Self {
        self.volumes.push(volume);
        self
    }

    #[must_use]
    pub fn with_env(mut self, key: String, value: String) -> Self {
        self.environment.push((key, value));
        self
    }

    #[must_use]
    pub fn with_process_identity(mut self, identity: ProcessIdentity) -> Self {
        self.process_identity = identity;
        self
    }

    #[must_use]
    pub fn with_config_hash(mut self, hash: u64) -> Self {
        self.config_hash = Some(hash);
        self
    }

    #[must_use]
    pub fn with_sd_notify(mut self, enabled: bool) -> Self {
        self.sd_notify = enabled;
        self
    }

    #[must_use]
    pub fn with_required_kernel_lockdown(mut self, mode: KernelLockdownMode) -> Self {
        self.required_kernel_lockdown = Some(mode);
        self
    }

    #[must_use]
    pub fn with_verify_context_integrity(mut self, enabled: bool) -> Self {
        self.verify_context_integrity = enabled;
        self
    }

    #[must_use]
    pub fn with_verify_rootfs_attestation(mut self, enabled: bool) -> Self {
        self.verify_rootfs_attestation = enabled;
        self
    }

    #[must_use]
    pub fn with_seccomp_log_denied(mut self, enabled: bool) -> Self {
        self.seccomp_log_denied = enabled;
        self
    }

    #[must_use]
    pub fn with_gvisor_platform(mut self, platform: GVisorPlatform) -> Self {
        self.gvisor_platform = platform;
        self
    }

    #[must_use]
    pub fn with_seccomp_profile(mut self, path: PathBuf) -> Self {
        self.seccomp_profile = Some(path);
        self
    }

    #[must_use]
    pub fn with_seccomp_profile_sha256(mut self, hash: String) -> Self {
        self.seccomp_profile_sha256 = Some(hash);
        self
    }

    #[must_use]
    pub fn with_seccomp_mode(mut self, mode: SeccompMode) -> Self {
        self.seccomp_mode = mode;
        self
    }

    #[must_use]
    pub fn with_seccomp_trace_log(mut self, path: PathBuf) -> Self {
        self.seccomp_trace_log = Some(path);
        self
    }

    #[must_use]
    pub fn with_seccomp_allow_syscalls(mut self, syscalls: Vec<String>) -> Self {
        self.seccomp_allow_syscalls = syscalls;
        self
    }

    #[must_use]
    pub fn with_caps_policy(mut self, path: PathBuf) -> Self {
        self.caps_policy = Some(path);
        self
    }

    #[must_use]
    pub fn with_caps_policy_sha256(mut self, hash: String) -> Self {
        self.caps_policy_sha256 = Some(hash);
        self
    }

    #[must_use]
    pub fn with_landlock_policy(mut self, path: PathBuf) -> Self {
        self.landlock_policy = Some(path);
        self
    }

    #[must_use]
    pub fn with_landlock_policy_sha256(mut self, hash: String) -> Self {
        self.landlock_policy_sha256 = Some(hash);
        self
    }

    #[must_use]
    pub fn with_pid_file(mut self, path: PathBuf) -> Self {
        self.pid_file = Some(path);
        self
    }

    #[must_use]
    pub fn with_console_socket(mut self, path: PathBuf) -> Self {
        self.console_socket = Some(path);
        self
    }

    #[must_use]
    pub fn with_bundle_dir(mut self, path: PathBuf) -> Self {
        self.bundle_dir = Some(path);
        self
    }

    pub fn with_state_root(mut self, root: PathBuf) -> Self {
        self.state_root = Some(root);
        self
    }

    /// Validate that production mode invariants are satisfied.
    /// Called before container startup when service_mode == Production.
    pub fn validate_production_mode(&self) -> crate::error::Result<()> {
        if self.service_mode != ServiceMode::Production {
            return Ok(());
        }

        if self.allow_degraded_security {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode forbids --allow-degraded-security".to_string(),
            ));
        }

        if self.allow_chroot_fallback {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode forbids --allow-chroot-fallback".to_string(),
            ));
        }

        if self.allow_host_network {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode forbids --allow-host-network".to_string(),
            ));
        }

        if matches!(self.network, crate::network::NetworkMode::Host) {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode forbids host network mode".to_string(),
            ));
        }

        // Production mode requires explicit rootfs (no host bind mount fallback)
        let Some(rootfs_path) = self.rootfs_path.as_ref() else {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires explicit --rootfs path (no host bind mounts)".to_string(),
            ));
        };

        // Canonicalize to resolve symlinks before validating the prefix,
        // preventing symlink-based bypasses (e.g. /nix/store/evil -> /etc).
        let rootfs_path = std::fs::canonicalize(rootfs_path).map_err(|e| {
            crate::error::NucleusError::ConfigError(format!(
                "Failed to canonicalize rootfs path '{}': {}",
                rootfs_path.display(),
                e
            ))
        })?;

        // Allow test rootfs paths under /tmp that simulate /nix/store structure
        let is_test_rootfs = rootfs_path
            .to_string_lossy()
            .contains("nucleus-test-nix-store");
        if !rootfs_path.starts_with("/nix/store") && !is_test_rootfs {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires a /nix/store rootfs path".to_string(),
            ));
        }

        if self.seccomp_mode == SeccompMode::Trace {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode forbids --seccomp-mode trace".to_string(),
            ));
        }

        // L6: Policy files must have SHA-256 verification in production
        if self.caps_policy.is_some() && self.caps_policy_sha256.is_none() {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires --caps-policy-sha256 when using --caps-policy"
                    .to_string(),
            ));
        }
        if self.landlock_policy.is_some() && self.landlock_policy_sha256.is_none() {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires --landlock-policy-sha256 when using --landlock-policy"
                    .to_string(),
            ));
        }
        if self.seccomp_profile.is_some() && self.seccomp_profile_sha256.is_none() {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires --seccomp-profile-sha256 when using --seccomp-profile"
                    .to_string(),
            ));
        }

        // Production mode requires explicit resource limits
        if self.limits.memory_bytes.is_none() {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires explicit --memory limit".to_string(),
            ));
        }

        if self.limits.cpu_quota_us.is_none() {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires explicit --cpus limit".to_string(),
            ));
        }

        if !self.verify_rootfs_attestation {
            return Err(crate::error::NucleusError::ConfigError(
                "Production mode requires --verify-rootfs-attestation".to_string(),
            ));
        }

        // Verify rootfs exists (checked last, after config invariants)
        if !rootfs_path.exists() {
            return Err(crate::error::NucleusError::ConfigError(format!(
                "Production mode rootfs path does not exist: {:?}",
                rootfs_path
            )));
        }

        Ok(())
    }

    /// Validate runtime-specific feature support.
    pub fn validate_runtime_support(&self) -> crate::error::Result<()> {
        if let Some(user_ns_config) = &self.user_ns_config {
            if !self.process_identity.additional_gids.is_empty() {
                return Err(crate::error::NucleusError::ConfigError(
                    "Supplementary groups are unsupported with user namespaces because \
                     /proc/self/setgroups is denied"
                        .to_string(),
                ));
            }

            let uid_mapped = user_ns_config.uid_mappings.iter().any(|mapping| {
                self.process_identity.uid >= mapping.container_id
                    && self.process_identity.uid
                        < mapping.container_id.saturating_add(mapping.count)
            });
            if !uid_mapped {
                return Err(crate::error::NucleusError::ConfigError(format!(
                    "Process uid {} is not mapped in the configured user namespace",
                    self.process_identity.uid
                )));
            }

            let gid_mapped = user_ns_config.gid_mappings.iter().any(|mapping| {
                self.process_identity.gid >= mapping.container_id
                    && self.process_identity.gid
                        < mapping.container_id.saturating_add(mapping.count)
            });
            if !gid_mapped {
                return Err(crate::error::NucleusError::ConfigError(format!(
                    "Process gid {} is not mapped in the configured user namespace",
                    self.process_identity.gid
                )));
            }
        }

        if self.seccomp_mode == SeccompMode::Trace && self.seccomp_trace_log.is_none() {
            return Err(crate::error::NucleusError::ConfigError(
                "Seccomp trace mode requires --seccomp-log / seccomp_trace_log".to_string(),
            ));
        }

        for secret in &self.secrets {
            normalize_container_destination(&secret.dest)?;
        }

        for volume in &self.volumes {
            normalize_container_destination(&volume.dest)?;
            match &volume.source {
                VolumeSource::Bind { source } => {
                    if !source.is_absolute() {
                        return Err(crate::error::NucleusError::ConfigError(format!(
                            "Volume source must be absolute: {:?}",
                            source
                        )));
                    }
                    if !source.exists() {
                        return Err(crate::error::NucleusError::ConfigError(format!(
                            "Volume source does not exist: {:?}",
                            source
                        )));
                    }
                    crate::filesystem::validate_bind_mount_source(source)?;
                }
                VolumeSource::Tmpfs { .. } => {}
            }
        }

        if !self.use_gvisor {
            return Ok(());
        }

        if self.seccomp_mode == SeccompMode::Trace {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime does not support --seccomp-mode trace; use --runtime native"
                    .to_string(),
            ));
        }

        if self.seccomp_profile.is_some() || self.seccomp_log_denied {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime does not support custom seccomp profiles or seccomp deny logging; use --runtime native"
                    .to_string(),
            ));
        }

        if self.caps_policy.is_some() {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime does not support capability policy files; use --runtime native"
                    .to_string(),
            ));
        }

        if self.landlock_policy.is_some() {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime does not support Landlock policy files; use --runtime native"
                    .to_string(),
            ));
        }

        if self.health_check.is_some() {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime does not support exec health checks; use --runtime native or remove --health-cmd"
                    .to_string(),
            ));
        }

        if matches!(
            self.readiness_probe.as_ref(),
            Some(ReadinessProbe::Exec { .. }) | Some(ReadinessProbe::TcpPort(_))
        ) {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime does not support exec/TCP readiness probes; use --runtime native or --readiness-sd-notify"
                    .to_string(),
            ));
        }

        if self.verify_context_integrity
            && self.context_dir.is_some()
            && matches!(self.context_mode, crate::filesystem::ContextMode::BindMount)
        {
            return Err(crate::error::NucleusError::ConfigError(
                "gVisor runtime cannot verify bind-mounted context integrity; use --context-mode copy or disable --verify-context-integrity"
                    .to_string(),
            ));
        }

        Ok(())
    }

    /// Apply runtime selection (native vs gVisor) and OCI bundle mode.
    pub fn apply_runtime_selection(
        mut self,
        runtime: RuntimeSelection,
        oci: bool,
    ) -> crate::error::Result<Self> {
        match runtime {
            RuntimeSelection::Native => {
                if oci {
                    return Err(crate::error::NucleusError::ConfigError(
                        "--bundle requires gVisor runtime; use --runtime gvisor".to_string(),
                    ));
                }
                self = self
                    .with_gvisor(false)
                    .with_trust_level(TrustLevel::Trusted);
            }
            RuntimeSelection::GVisor => {
                self = self.with_gvisor(true);
                if !oci {
                    tracing::info!(
                        "Security hardening: enabling OCI bundle mode for gVisor runtime"
                    );
                }
                self = self.with_oci_bundle();
            }
        }
        Ok(self)
    }
}

/// Validate a container name for safe use.
pub fn validate_container_name(name: &str) -> crate::error::Result<()> {
    if name.is_empty() || name.len() > 128 {
        return Err(crate::error::NucleusError::ConfigError(
            "Invalid container name: must be 1-128 characters".to_string(),
        ));
    }
    if !name
        .chars()
        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
    {
        return Err(crate::error::NucleusError::ConfigError(
            "Invalid container name: allowed characters are a-zA-Z0-9, '-', '_', '.'".to_string(),
        ));
    }
    Ok(())
}

/// Validate a hostname according to RFC 1123.
pub fn validate_hostname(hostname: &str) -> crate::error::Result<()> {
    if hostname.is_empty() || hostname.len() > 253 {
        return Err(crate::error::NucleusError::ConfigError(
            "Invalid hostname: must be 1-253 characters".to_string(),
        ));
    }

    for label in hostname.split('.') {
        if label.is_empty() || label.len() > 63 {
            return Err(crate::error::NucleusError::ConfigError(format!(
                "Invalid hostname label: '{}'",
                label
            )));
        }
        if label.starts_with('-') || label.ends_with('-') {
            return Err(crate::error::NucleusError::ConfigError(format!(
                "Invalid hostname label '{}': cannot start or end with '-'",
                label
            )));
        }
        if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
            return Err(crate::error::NucleusError::ConfigError(format!(
                "Invalid hostname label '{}': allowed characters are a-zA-Z0-9 and '-'",
                label
            )));
        }
    }

    Ok(())
}

#[cfg(test)]
#[allow(deprecated)]
mod tests {
    use super::*;
    use crate::network::NetworkMode;

    #[test]
    fn test_generate_container_id_is_32_hex_chars() {
        let id = generate_container_id().unwrap();
        assert_eq!(
            id.len(),
            32,
            "Container ID must be full 128-bit (32 hex chars), got {}",
            id.len()
        );
        assert!(
            id.chars().all(|c| c.is_ascii_hexdigit()),
            "Container ID must be hex: {}",
            id
        );
    }

    #[test]
    fn test_generate_container_id_is_unique() {
        let id1 = generate_container_id().unwrap();
        let id2 = generate_container_id().unwrap();
        assert_ne!(id1, id2, "Two consecutive IDs must differ");
    }

    #[test]
    fn test_config_security_defaults_are_hardened() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()]).unwrap();
        assert!(!cfg.allow_degraded_security);
        assert!(!cfg.allow_chroot_fallback);
        assert!(!cfg.allow_host_network);
        assert!(cfg.proc_readonly);
        assert_eq!(cfg.service_mode, ServiceMode::Agent);
        assert!(cfg.rootfs_path.is_none());
        assert!(cfg.egress_policy.is_none());
        assert!(cfg.secrets.is_empty());
        assert!(cfg.volumes.is_empty());
        assert!(!cfg.sd_notify);
        assert!(cfg.required_kernel_lockdown.is_none());
        assert!(!cfg.verify_context_integrity);
        assert!(!cfg.verify_rootfs_attestation);
        assert!(!cfg.seccomp_log_denied);
        assert_eq!(cfg.gvisor_platform, GVisorPlatform::Systrap);
    }

    #[test]
    fn test_production_mode_rejects_degraded_flags() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_allow_degraded_security(true)
            .with_rootfs_path(std::path::PathBuf::from("/nix/store/fake-rootfs"))
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap()
                    .with_cpu_cores(2.0)
                    .unwrap(),
            );
        assert!(cfg.validate_production_mode().is_err());
    }

    #[test]
    fn test_production_mode_rejects_chroot_fallback() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_allow_chroot_fallback(true)
            .with_rootfs_path(std::path::PathBuf::from("/nix/store/fake-rootfs"))
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap()
                    .with_cpu_cores(2.0)
                    .unwrap(),
            );
        let err = cfg.validate_production_mode().unwrap_err();
        assert!(
            err.to_string().contains("chroot"),
            "Production mode must reject chroot fallback"
        );
    }

    #[test]
    fn test_production_mode_requires_rootfs() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap(),
            );
        let err = cfg.validate_production_mode().unwrap_err();
        assert!(err.to_string().contains("--rootfs"));
    }

    fn test_rootfs_path() -> std::path::PathBuf {
        use std::sync::atomic::{AtomicU64, Ordering};
        static COUNTER: AtomicU64 = AtomicU64::new(0);
        let id = COUNTER.fetch_add(1, Ordering::SeqCst);

        // Create a real directory (not a symlink) whose path contains the
        // test marker so it survives canonicalization.
        let rootfs = std::env::temp_dir().join(format!(
            "nucleus-test-nix-store-{}-{}/rootfs",
            std::process::id(),
            id
        ));
        std::fs::create_dir_all(&rootfs).unwrap();

        rootfs
    }

    #[test]
    fn test_production_mode_requires_memory_limit() {
        let rootfs = test_rootfs_path();
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_rootfs_path(rootfs);
        let err = cfg.validate_production_mode().unwrap_err();
        let _ = std::fs::remove_dir_all(&cfg.rootfs_path.as_ref().unwrap());
        assert!(err.to_string().contains("--memory"));
    }

    #[test]
    fn test_production_mode_valid_config() {
        let rootfs = test_rootfs_path();
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_rootfs_path(rootfs.clone())
            .with_verify_rootfs_attestation(true)
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap()
                    .with_cpu_cores(2.0)
                    .unwrap(),
            );
        let result = cfg.validate_production_mode();
        let _ = std::fs::remove_dir_all(&rootfs);
        assert!(result.is_ok());
    }

    #[test]
    fn test_production_mode_requires_rootfs_attestation() {
        let rootfs = test_rootfs_path();
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_rootfs_path(rootfs.clone())
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap()
                    .with_cpu_cores(2.0)
                    .unwrap(),
            );
        let err = cfg.validate_production_mode().unwrap_err();
        let _ = std::fs::remove_dir_all(&rootfs);
        assert!(err.to_string().contains("attestation"));
    }

    #[test]
    fn test_production_mode_rejects_seccomp_trace() {
        let rootfs = test_rootfs_path();
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_rootfs_path(rootfs.clone())
            .with_seccomp_mode(SeccompMode::Trace)
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap()
                    .with_cpu_cores(2.0)
                    .unwrap(),
            );
        let err = cfg.validate_production_mode().unwrap_err();
        let _ = std::fs::remove_dir_all(&rootfs);
        assert!(
            err.to_string().contains("trace"),
            "Production mode must reject seccomp trace mode"
        );
    }

    #[test]
    fn test_production_mode_requires_cpu_limit() {
        let rootfs = test_rootfs_path();
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_service_mode(ServiceMode::Production)
            .with_rootfs_path(rootfs.clone())
            .with_limits(
                crate::resources::ResourceLimits::default()
                    .with_memory("512M")
                    .unwrap(),
            );
        let err = cfg.validate_production_mode().unwrap_err();
        let _ = std::fs::remove_dir_all(&rootfs);
        assert!(err.to_string().contains("--cpus"));
    }

    #[test]
    fn test_config_security_builders_override_defaults() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_allow_degraded_security(true)
            .with_allow_chroot_fallback(true)
            .with_allow_host_network(true)
            .with_proc_readonly(false)
            .with_network(NetworkMode::Host);

        assert!(cfg.allow_degraded_security);
        assert!(cfg.allow_chroot_fallback);
        assert!(cfg.allow_host_network);
        assert!(!cfg.proc_readonly);
        assert!(matches!(cfg.network, NetworkMode::Host));
    }

    #[test]
    fn test_hardening_builders_override_defaults() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_required_kernel_lockdown(KernelLockdownMode::Confidentiality)
            .with_verify_context_integrity(true)
            .with_verify_rootfs_attestation(true)
            .with_seccomp_log_denied(true)
            .with_gvisor_platform(GVisorPlatform::Kvm);

        assert_eq!(
            cfg.required_kernel_lockdown,
            Some(KernelLockdownMode::Confidentiality)
        );
        assert!(cfg.verify_context_integrity);
        assert!(cfg.verify_rootfs_attestation);
        assert!(cfg.seccomp_log_denied);
        assert_eq!(cfg.gvisor_platform, GVisorPlatform::Kvm);
    }

    #[test]
    fn test_seccomp_trace_requires_log_path() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_gvisor(false)
            .with_seccomp_mode(SeccompMode::Trace);

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("seccomp-log"));
    }

    #[test]
    fn test_gvisor_rejects_native_security_policy_files() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_seccomp_profile(PathBuf::from("/tmp/seccomp.json"))
            .with_caps_policy(PathBuf::from("/tmp/caps.toml"));

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("gVisor runtime"));
    }

    #[test]
    fn test_gvisor_rejects_landlock_policy_file() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_landlock_policy(PathBuf::from("/tmp/landlock.toml"));

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("Landlock"));
    }

    #[test]
    fn test_gvisor_rejects_trace_mode_even_with_log_path() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_seccomp_mode(SeccompMode::Trace)
            .with_seccomp_trace_log(PathBuf::from("/tmp/trace.ndjson"));

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("gVisor runtime"));
    }

    #[test]
    fn test_secret_dest_must_be_absolute() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_secret(crate::container::SecretMount {
                source: PathBuf::from("/run/secrets/api-key"),
                dest: PathBuf::from("secrets/api-key"),
                mode: 0o400,
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("absolute"));
    }

    #[test]
    fn test_secret_dest_rejects_parent_traversal() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_secret(crate::container::SecretMount {
                source: PathBuf::from("/run/secrets/api-key"),
                dest: PathBuf::from("/../../etc/passwd"),
                mode: 0o400,
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("parent traversal"));
    }

    #[test]
    fn test_bind_volume_source_must_exist() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_volume(VolumeMount {
                source: VolumeSource::Bind {
                    source: PathBuf::from("/tmp/definitely-missing-nucleus-volume"),
                },
                dest: PathBuf::from("/var/lib/app"),
                read_only: false,
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("Volume source does not exist"));
    }

    #[test]
    fn test_bind_volume_source_rejects_sensitive_host_subtrees() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_volume(VolumeMount {
                source: VolumeSource::Bind {
                    source: PathBuf::from("/proc/sys"),
                },
                dest: PathBuf::from("/host-proc"),
                read_only: true,
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("sensitive host path"));
    }

    #[test]
    fn test_bind_volume_dest_must_be_absolute() {
        let dir = tempfile::TempDir::new().unwrap();
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_volume(VolumeMount {
                source: VolumeSource::Bind {
                    source: dir.path().to_path_buf(),
                },
                dest: PathBuf::from("var/lib/app"),
                read_only: false,
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("absolute"));
    }

    #[test]
    fn test_tmpfs_volume_rejects_parent_traversal() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_volume(VolumeMount {
                source: VolumeSource::Tmpfs {
                    size: Some("64M".to_string()),
                },
                dest: PathBuf::from("/../../var/lib/app"),
                read_only: false,
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("parent traversal"));
    }

    #[test]
    fn test_gvisor_rejects_bind_mount_context_integrity_verification() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_context(PathBuf::from("/tmp/context"))
            .with_context_mode(crate::filesystem::ContextMode::BindMount)
            .with_verify_context_integrity(true);

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("context integrity"));
    }

    #[test]
    fn test_gvisor_rejects_exec_health_checks() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_health_check(HealthCheck {
                command: vec!["/bin/sh".to_string(), "-c".to_string(), "true".to_string()],
                interval: Duration::from_secs(30),
                retries: 3,
                start_period: Duration::from_secs(1),
                timeout: Duration::from_secs(5),
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("health checks"));
    }

    #[test]
    fn test_gvisor_rejects_exec_readiness_probes() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_readiness_probe(ReadinessProbe::Exec {
                command: vec!["/bin/sh".to_string(), "-c".to_string(), "true".to_string()],
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("readiness"));
    }

    #[test]
    fn test_gvisor_allows_copy_mode_context_integrity_verification() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_context(PathBuf::from("/tmp/context"))
            .with_context_mode(crate::filesystem::ContextMode::Copy)
            .with_verify_context_integrity(true);

        assert!(cfg.validate_runtime_support().is_ok());
    }

    #[test]
    fn test_user_namespace_rejects_unmapped_process_identity() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_rootless()
            .with_process_identity(ProcessIdentity {
                uid: 1000,
                gid: 1000,
                additional_gids: Vec::new(),
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("not mapped"));
    }

    #[test]
    fn test_user_namespace_rejects_supplementary_groups() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_rootless()
            .with_process_identity(ProcessIdentity {
                uid: 0,
                gid: 0,
                additional_gids: vec![1],
            });

        let err = cfg.validate_runtime_support().unwrap_err();
        assert!(err.to_string().contains("Supplementary groups"));
    }

    #[test]
    fn test_native_runtime_disables_gvisor() {
        // --runtime native must explicitly disable gVisor and set Trusted trust level
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()])
            .unwrap()
            .with_gvisor(false)
            .with_trust_level(TrustLevel::Trusted);
        assert!(!cfg.use_gvisor, "native runtime must disable gVisor");
        assert_eq!(
            cfg.trust_level,
            TrustLevel::Trusted,
            "native runtime must set Trusted trust level"
        );
    }

    #[test]
    fn test_default_config_has_gvisor_enabled() {
        let cfg = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()]).unwrap();
        assert!(cfg.use_gvisor, "default must have gVisor enabled");
        assert_eq!(
            cfg.trust_level,
            TrustLevel::Untrusted,
            "default must be Untrusted"
        );
    }

    #[test]
    fn test_generate_container_id_returns_result() {
        // BUG-07: generate_container_id must return Result, not panic.
        // Verify by calling it and checking the Ok value is valid hex.
        let id: crate::error::Result<String> = generate_container_id();
        let id = id.expect("generate_container_id must return Ok, not panic");
        assert_eq!(id.len(), 32, "container ID must be 32 hex chars");
        assert!(
            id.chars().all(|c| c.is_ascii_hexdigit()),
            "container ID must be valid hex: {}",
            id
        );
    }
}
