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

## nimpak/xdg_enforcer.nim
## XDG Base Directory Enforcer for Nippels
##
## Enforces XDG Base Directory specification automatically for Nippel applications.
## Provides different strategies based on security profile:
## - Satellite/Workstation: Portable mode (user home directories)
## - Homestation/Server/NetworkIOT: System-integrated mode (XDG system structure)
##
## Requirements: 2.1-2.5

import std/[os, tables, options, posix]
import utils/resultutils
import nippel_types

# =============================================================================
# XDG Directory Strategy
# =============================================================================

type
  XDGStrategy* = enum
    ## Strategy for XDG directory placement
    Portable      ## Store in user home for portability (Satellite, Workstation)
    SystemIntegrated  ## Integrate with system XDG structure (Homestation, Server, NetworkIOT)

  XDGEnforcerConfig* = object
    ## Configuration for XDG enforcement
    strategy*: XDGStrategy
    strictMode*: bool
    redirectLegacy*: bool
    createSymlinks*: bool

  XDGError* = object of CatchableError
    ## XDG-specific errors
    nippelName*: string
    path*: string

# =============================================================================
# Strategy Selection Based on Profile
# =============================================================================

proc getXDGStrategy*(profile: SecurityProfile): XDGStrategy =
  ## Determine XDG strategy based on security profile
  ## Satellite/Workstation: Portable (survive system reinstalls)
  ## Homestation/Server/NetworkIOT: System-integrated
  case profile:
  of Satellite, Workstation:
    Portable
  of Homestation, Server, NetworkIOT:
    SystemIntegrated

proc getXDGEnforcerConfig*(profile: SecurityProfile): XDGEnforcerConfig =
  ## Get XDG enforcer configuration for a security profile
  let strategy = getXDGStrategy(profile)

  XDGEnforcerConfig(
    strategy: strategy,
    strictMode: profile in {Satellite, Server, NetworkIOT},
    redirectLegacy: true,
    createSymlinks: true
  )

# =============================================================================
# XDG Directory Structure Creation (Requirement 2.1)
# =============================================================================

proc createXDGStructure*(nippelRoot: string, profile: SecurityProfile): Result[XDGDirectories, string] =
  ## Create XDG directory structure for a Nippel (Requirement 2.1)
  ## Strategy depends on security profile:
  ## - Portable: ~/.nip/cells/<name>/{Data,Config,Cache,State,Runtime}
  ## - System-integrated: Standard XDG locations with Nippel subdirectories

  try:
    let strategy = getXDGStrategy(profile)
    var xdgDirs: XDGDirectories

    case strategy:
    of Portable:
      # Portable mode: Everything in Nippel root (suystem reinstalls)
      xdgDirs = XDGDirectories(
        dataHome: nippelRoot / "Data",
        configHome: nippelRoot / "Config",
        cacheHome: nippelRoot / "Cache",
        stateHome: nippelRoot / "State",
        runtimeDir: nippelRoot / "Runtime"
      )

      echo "📦 Portable XDG structure (survives system reinstalls)"
      echo "   Profile: ", profile
      echo "   Root: ", nippelRoot

    of SystemIntegrated:
      # System-integrated mode: Use standard XDG locations
      let nippelName = extractFilename(nippelRoot)
      let homeDir = getHomeDir()

      xdgDirs = XDGDirectories(
        dataHome: homeDir / ".local" / "share" / "nippels" / nippelName,
        configHome: homeDir / ".config" / "nippels" / nippelName,
        cacheHome: homeDir / ".cache" / "nippels" / nippelName,
        stateHome: homeDir / ".local" / "state" / "nippels" / nippelName,
        runtimeDir: "/run/user" / $getuid() / "nippels" / nippelName
      )

      echo "🔗 System-integrated XDG structure"
      echo "   Profile: ", profile
      echo "   Integrated with system XDG directories"

    # Create all XDG directories
    createDir(xdgDirs.dataHome)
    createDir(xdgDirs.dataHome / "applications")  # For .desktop files
    createDir(xdgDirs.configHome)
    createDir(xdgDirs.cacheHome)
    createDir(xdgDirs.stateHome)
    createDir(xdgDirs.runtimeDir)

    # Set appropriate permissions
    setFilePermissions(xdgDirs.dataHome, {fpUserRead, fpUserWrite, fpUserExec})
    setFilePermissions(xdgDirs.configHome, {fpUserRead, fpUserWrite, fpUserExec})
    setFilePermissions(xdgDirs.cacheHome, {fpUserRead, fpUserWrite, fpUserExec})
    setFilePermissions(xdgDirs.stateHome, {fpUserRead, fpUserWrite, fpUserExec})
    setFilePermissions(xdgDirs.runtimeDir, {fpUserRead, fpUserWrite, fpUserExec})

    echo "✅ Created XDG directory structure:"
    echo "   Data:    ", xdgDirs.dataHome
    echo "   Config:  ", xdgDirs.configHome
    echo "   Cache:   ", xdgDirs.cacheHome
    echo "   State:   ", xdgDirs.stateHome
    echo "   Runtime: ", xdgDirs.runtimeDir

    return ok(xdgDirs)

  except Exception as e:
    return err[XDGDirectories]("Failed to create XDG structure: " & e.msg)

# =============================================================================
# Environment Variable Setting (Requirement 2.2)
# =============================================================================

proc setXDGEnvironment*(dirs: XDGDirectories): Result[bool, string] =
  ## Set XDG environment variables (Requirement 2.2)
  ## These variables enforce XDG compliance for applications running in the Nippel

  try:
    putEnv("XDG_DATA_HOME", dirs.dataHome)
    putEnv("XDG_CONFIG_HOME", dirs.configHome)
    putEnv("XDG_CACHE_HOME", dirs.cacheHome)
    putEnv("XDG_STATE_HOME", dirs.stateHome)
    putEnv("XDG_RUNTIME_DIR", dirs.runtimeDir)

    # Also set XDG_DATA_DIRS and XDG_CONFIG_DIRS for system-wide resources
    let existingDataDirs = getEnv("XDG_DATA_DIRS", "/usr/local/share:/usr/share")
    let existingConfigDirs = getEnv("XDG_CONFIG_DIRS", "/etc/xdg")

    putEnv("XDG_DATA_DIRS", dirs.dataHome & ":" & existingDataDirs)
    putEnv("XDG_CONFIG_DIRS", dirs.configHome & ":" & existingConfigDirs)

    echo "✅ Set XDG environment variables:"
    echo "   XDG_DATA_HOME=", dirs.dataHome
    echo "   XDG_CONFIG_HOME=", dirs.configHome
    echo "   XDG_CACHE_HOME=", dirs.cacheHome
    echo "   XDG_STATE_HOME=", dirs.stateHome
    echo "   XDG_RUNTIME_DIR=", dirs.runtimeDir

    return ok(true)

  except Exception as e:
    return err[bool]("Failed to set XDG environment: " & e.msg)

# =============================================================================
# Legacy Path Redirection (Requirement 2.3, 2.4)
# =============================================================================

proc redirectLegacyPaths*(dirs: XDGDirectories, nippelRoot: string): Result[bool, string] =
  ## Create symlinks from legacy paths to XDG directories (Requirement 2.3, 2.4)
  ## This ensures applications that don't follow XDG still work correctly

  try:
    # Define legacy path redirections
    let redirections = {
      ".config": dirs.configHome,
      ".local/share": dirs.dataHome,
      ".cache": dirs.cacheHome,
      ".local/state": dirs.stateHome
    }.toTable()

    var redirectedCount = 0

    for legacyPath, xdgPath in redirections:
      let legacyFullPath = nippelRoot / legacyPath

      # Only create symlink if legacy path doesn't exist
      if not fileExists(legacyFullPath) and not dirExists(legacyFullPath):
        # Create parent directory if needed
        let parentDir = parentDir(legacyFullPath)
        if not dirExists(parentDir):
          createDir(parentDir)

        # Create symlink
        createSymlink(xdgPath, legacyFullPath)
        redirectedCount.inc
        echo "   🔗 ", legacyPath, " → ", xdgPath

    if redirectedCount > 0:
      echo "✅ Created ", redirectedCount, " legacy path redirections"
    else:
      echo "ℹ️  No legacy path redirections needed"

    return ok(true)

  except Exception as e:
    return err[bool]("Failed to redirect legacy paths: " & e.msg)

# =============================================================================
# XDG Compliance Enforcement (Requirement 2.5)
# =============================================================================

proc enforceXDGCompliance*(nippel: var Nippel): Result[bool, string] =
  ## Enforce XDG compliance for a Nippel (Requirement 2.5)
  ## This is the main entry point that orchestrates all XDG enforcement

  try:
    echo "🔧 Enforcing XDG compliance for Nippel: ", nippel.name
    echo "   Profile: ", nippel.profile
    echo "   Strategy: ", getXDGStrategy(nippel.profile)

    # 1. Create XDG directory structure
    let xdgResult = createXDGStructure(nippel.cellRoot, nippel.profile)
    if xdgResult.isErr:
      return err[bool]("Failed to create XDG structure: " & xdgResult.error)

    nippel.xdgDirs = xdgResult.get()

    # 2. Set XDG environment variables
    let envResult = setXDGEnvironment(nippel.xdgDirs)
    if envResult.isErr:
      return err[bool]("Failed to set XDG environment: " & envResult.error)

    # 3. Redirect legacy paths (only in portable mode)
    if getXDGStrategy(nippel.profile) == Portable:
      let redirectResult = redirectLegacyPaths(nippel.xdgDirs, nippel.cellRoot)
      if redirectResult.isErr:
        return err[bool]("Failed to redirect legacy paths: " & redirectResult.error)

    echo "✅ XDG compliance enforced successfully"
    return ok(true)

  except Exception as e:
    return err[bool]("Failed to enforce XDG compliance: " & e.msg)

# =============================================================================
# XDG Verification
# =============================================================================

proc verifyXDGStructure*(dirs: XDGDirectories): Result[bool, string] =
  ## Verify that XDG directory structure is valid and accessible

  try:
    let requiredDirs = @[
      dirs.dataHome,
      dirs.configHome,
      dirs.cacheHome,
      dirs.stateHome,
      dirs.runtimeDir
    ]

    for dir in requiredDirs:
      if not dirExists(dir):
        return err[bool]("XDG directory does not exist: " & dir)

      # Check if directory is writable
      let testFile = dir / ".xdg-test"
      try:
        writeFile(testFile, "test")
        removeFile(testFile)
      except:
        return err[bool]("XDG directory is not writable: " & dir)

    echo "✅ XDG structure verified successfully"
    return ok(true)

  except Exception as e:
    return err[bool]("Failed to verify XDG structure: " & e.msg)

proc getXDGInfo*(dirs: XDGDirectories): string =
  ## Get human-readable information about XDG directories
  result = "XDG Directory Structure:\n"
  result.add("  Data:    " & dirs.dataHome & "\n")
  result.add("  Config:  " & dirs.configHome & "\n")
  result.add("  Cache:   " & dirs.cacheHome & "\n")
  result.add("  State:   " & dirs.stateHome & "\n")
  result.add("  Runtime: " & dirs.runtimeDir)

# =============================================================================
# XDG Cleanup
# =============================================================================

proc cleanupXDGStructure*(dirs: XDGDirectories, strategy: XDGStrategy): Result[bool, string] =
  ## Clean up XDG directory structure when removing a Nippel
  ## In portable mode: Remove everything
  ## In system-integrated mode: Only remove Nippel-specific subdirectories

  try:
    case strategy:
    of Portable:
      # Remove all directories (they're in the Nippel root)
      if dirExists(dirs.dataHome):
        removeDir(dirs.dataHome)
      if dirExists(dirs.configHome):
        removeDir(dirs.configHome)
      if dirExists(dirs.cacheHome):
        removeDir(dirs.cacheHome)
      if dirExists(dirs.stateHome):
        removeDir(dirs.stateHome)
      if dirExists(dirs.runtimeDir):
        removeDir(dirs.runtimeDir)

      echo "✅ Cleaned up portable XDG structure"

    of SystemIntegrated:
      # Only remove Nippel-specific subdirectories
      # Leave parent directories intact for other Nippels
      if dirExists(dirs.dataHome):
        removeDir(dirs.dataHome)
      if dirExists(dirs.configHome):
        removeDir(dirs.configHome)
      if dirExists(dirs.cacheHome):
        removeDir(dirs.cacheHome)
      if dirExists(dirs.stateHome):
        removeDir(dirs.stateHome)
      if dirExists(dirs.runtimeDir):
        removeDir(dirs.runtimeDir)

      echo "✅ Cleaned up system-integrated XDG structure"

    return ok(true)

  except Exception as e:
    return err[bool]("Failed to cleanup XDG structure: " & e.msg)

# =============================================================================
# Exports
# =============================================================================

export XDGStrategy, XDGEnforcerConfig, XDGError
export getXDGStrategy, getXDGEnforcerConfig
export createXDGStructure, setXDGEnvironment, redirectLegacyPaths
export enforceXDGCompliance, verifyXDGStructure, getXDGInfo
export cleanupXDGStructure
