## NIP Launcher Tests
##
## Tests for the NIP launcher that runs applications in isolated namespaces.
## This verifies that applications can be launched with proper sandbox restrictions.

import std/[unittest, os, tempfiles, options, strutils, posix]
import nip/namespace
import nip/manifest_parser
import nip/nip_installer
import nip/cas

suite "NIP Launcher Tests":

  setup:
    let tempDir = createTempDir("nip_test_launcher_", "")
    let casRoot = tempDir / "cas"
    let installRoot = tempDir / "nips"
    
    createDir(casRoot)
    createDir(installRoot)
    discard initCasManager(casRoot, casRoot)

  teardown:
    removeDir(tempDir)

  test "Create Launcher from Manifest":
    ## Verify launcher can be created from a manifest
    let manifest = PackageManifest(
      name: "test-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher != nil
    check launcher.manifest.name == "test-app"
    check launcher.installDir == installRoot
    check launcher.casRoot == casRoot

  test "Launcher with Sandbox Configuration":
    ## Verify launcher respects sandbox configuration
    var manifest = PackageManifest(
      name: "sandboxed-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.sandbox = some(SandboxConfig(
      level: SandboxStrict,
      namespaces: @["user", "mount", "pid", "ipc"]
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isSome
    let sb = launcher.manifest.sandbox.get()
    check sb.level == SandboxStrict
    check "user" in sb.namespaces
    check "mount" in sb.namespaces

  test "Launcher with Desktop Integration":
    ## Verify launcher works with desktop-integrated applications
    var manifest = PackageManifest(
      name: "desktop-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.desktop = some(DesktopIntegration(
      displayName: "Desktop Application",
      categories: @["Utility", "Development"],
      icon: some("app-icon"),
      terminal: false
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.desktop.isSome
    let dt = launcher.manifest.desktop.get()
    check dt.displayName == "Desktop Application"
    check dt.icon.isSome
    check dt.icon.get() == "app-icon"

  test "Launcher with Seccomp Profile":
    ## Verify launcher respects seccomp configuration
    var manifest = PackageManifest(
      name: "seccomp-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.sandbox = some(SandboxConfig(
      level: SandboxStandard,
      namespaces: @["user", "mount"],
      seccompProfile: some("strict")
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isSome
    let sb = launcher.manifest.sandbox.get()
    check sb.seccompProfile.isSome
    check sb.seccompProfile.get() == "strict"

  test "Launcher with Capabilities":
    ## Verify launcher respects capability restrictions
    var manifest = PackageManifest(
      name: "cap-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.sandbox = some(SandboxConfig(
      level: SandboxStandard,
      namespaces: @["user", "mount"],
      capabilities: @["CAP_NET_ADMIN", "CAP_SYS_ADMIN"]
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isSome
    let sb = launcher.manifest.sandbox.get()
    check sb.capabilities.len == 2
    check "CAP_NET_ADMIN" in sb.capabilities
    check "CAP_SYS_ADMIN" in sb.capabilities

  test "Launcher with Pledge (BSD)":
    ## Verify launcher respects pledge configuration (OpenBSD)
    var manifest = PackageManifest(
      name: "pledge-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.sandbox = some(SandboxConfig(
      level: SandboxStandard,
      namespaces: @["user"],
      pledge: some("stdio rpath wpath inet")
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isSome
    let sb = launcher.manifest.sandbox.get()
    check sb.pledge.isSome
    check sb.pledge.get() == "stdio rpath wpath inet"

  test "Launcher Isolation Levels":
    ## Verify launcher supports different isolation levels
    for level in [SandboxStrict, SandboxStandard]:
      var manifest = PackageManifest(
        name: "isolation-app",
        version: parseSemanticVersion("1.0.0"),
        license: "MIT",
        artifactHash: "hash123"
      )
      
      manifest.sandbox = some(SandboxConfig(
        level: level,
        namespaces: @["user", "mount"]
      ))
      
      let launcher = newLauncher(manifest, installRoot, casRoot)
      check launcher.manifest.sandbox.isSome
      check launcher.manifest.sandbox.get().level == level

  test "Launcher with CAS Root":
    ## Verify launcher correctly references CAS root for read-only mounts
    let manifest = PackageManifest(
      name: "cas-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.casRoot == casRoot
    # The launcher should mount casRoot as read-only in the namespace
    # This is verified by the namespace setup code

  test "Launcher with Multiple Namespaces":
    ## Verify launcher can create multiple namespace types
    var manifest = PackageManifest(
      name: "multi-ns-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.sandbox = some(SandboxConfig(
      level: SandboxStrict,
      namespaces: @["user", "mount", "pid", "net", "ipc"]
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isSome
    let sb = launcher.manifest.sandbox.get()
    check sb.namespaces.len == 5
    check "user" in sb.namespaces
    check "mount" in sb.namespaces
    check "pid" in sb.namespaces
    check "net" in sb.namespaces
    check "ipc" in sb.namespaces

  test "Launcher Manifest Validation":
    ## Verify launcher validates manifest before launch
    let manifest = PackageManifest(
      name: "valid-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    # Launcher should have valid manifest
    check launcher.manifest.name.len > 0
    check launcher.manifest.version.major >= 0
    check launcher.manifest.license.len > 0

  test "Launcher with No Sandbox (Unrestricted)":
    ## Verify launcher can run without sandbox for trusted applications
    let manifest = PackageManifest(
      name: "unrestricted-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    # No sandbox configuration = unrestricted
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isNone
    # Launcher should still work, just without isolation

  test "Launcher with Minimal Sandbox":
    ## Verify launcher works with minimal sandbox configuration
    var manifest = PackageManifest(
      name: "minimal-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    manifest.sandbox = some(SandboxConfig(
      level: SandboxStandard,
      namespaces: @[]
    ))
    
    let launcher = newLauncher(manifest, installRoot, casRoot)
    check launcher.manifest.sandbox.isSome
    let sb = launcher.manifest.sandbox.get()
    check sb.level == SandboxStandard
    check sb.namespaces.len == 0

## Property-Based Tests

suite "NIP Launcher Property Tests":

  test "Property: Launcher preserves manifest integrity":
    ## Verify launcher doesn't modify manifest during creation
    let manifest = PackageManifest(
      name: "prop-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    let originalName = manifest.name
    let originalVersion = manifest.version
    let originalLicense = manifest.license
    
    let launcher = newLauncher(manifest, "/tmp/install", "/tmp/cas")
    
    check launcher.manifest.name == originalName
    check launcher.manifest.version == originalVersion
    check launcher.manifest.license == originalLicense

  test "Property: Launcher paths are correctly set":
    ## Verify launcher stores paths correctly
    let manifest = PackageManifest(
      name: "path-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    let installDir = "/custom/install"
    let casRoot = "/custom/cas"
    
    let launcher = newLauncher(manifest, installDir, casRoot)
    
    check launcher.installDir == installDir
    check launcher.casRoot == casRoot

  test "Property: Launcher supports all namespace combinations":
    ## Verify launcher can handle all namespace combinations
    var manifest = PackageManifest(
      name: "ns-app",
      version: parseSemanticVersion("1.0.0"),
      license: "MIT",
      artifactHash: "hash123"
    )
    
    # Test various namespace combinations
    let namespaceCombos = @[
      @["user"],
      @["user", "mount"],
      @["user", "mount", "pid"],
      @["user", "mount", "pid", "net"],
      @["user", "mount", "pid", "net", "ipc"]
    ]
    
    for namespaces in namespaceCombos:
      manifest.sandbox = some(SandboxConfig(
        level: SandboxStandard,
        namespaces: namespaces
      ))
      
      let launcher = newLauncher(manifest, "/tmp/install", "/tmp/cas")
      check launcher.manifest.sandbox.isSome
      let sb = launcher.manifest.sandbox.get()
      check sb.namespaces == namespaces
