## nimpak/profile_manager.nim
## Profile Manager for Nippels
##
## Manages security profiles and applies appropriate settings for different system roles.
## Supports profile loading, application, and customization.
##
## Requirements: 6.1-6.8

import std/[os, strutils, json, tables, options, times]
import utils/resultutils
import nippel_types

# =============================================================================
# Profile Overrides (Requirement 6.8)
# =============================================================================

type
  ProfileOverrides* = object
    ## Per-Nippel profile customizations
    isolationLevel*: Option[IsolationLevel]
    desktopIntegration*: Option[bool]
    networkAccess*: Option[NetworkAccessLevel]
    resourceLimits*: Option[ResourceLimits]
    auditingEnabled*: Option[bool]

  ProfileError* = object of CatchableError
    ## Profile-specific errors
    profileName*: string
    context*: JsonNode

  ProfileManager* = object
    ## Manages security profiles for Nippels
    profilesDir*: string
    customProfilesDir*: string
    loadedProfiles*: Table[string, ProfileSettings]

# =============================================================================
# Profile Settings Definitions (Requirement 6.1-6.5)
# =============================================================================

proc getWorkstationProfile*(): ProfileSettings =
  ## Workstation profile: Standard isolation + desktop integration (Requirement 6.1)
  ## Suitable for desktop workstations with full GUI support
  ProfileSettings(
    isolationLevel: Standard,
    desktopIntegration: true,
    networkAccess: Full,
    resourceLimits: ResourceLimits(
      maxMemory: 8 * 1024 * 1024 * 1024,  # 8GB
      maxCpu: 0.9,                         # 90% CPU
      maxDisk: 10 * 1024 * 1024 * 1024,   # 10GB
      maxProcesses: 200,
      maxOpenFiles: 2048
    ),
    auditingEnabled: false
  )

proc getHomestationProfile*(): ProfileSettings =
  ## Homestation profile: Standard isolation + relaxed network (Requirement 6.2)
  ## Default profile for home users with balanced security and convenience
  ProfileSettings(
    isolationLevel: Standard,
    desktopIntegration: true,
    networkAccess: Relaxed,
    resourceLimits: ResourceLimits(
      maxMemory: 4 * 1024 * 1024 * 1024,  # 4GB
      maxCpu: 0.8,                         # 80% CPU
      maxDisk: 5 * 1024 * 1024 * 1024,    # 5GB
      maxProcesses: 150,
      maxOpenFiles: 1024
    ),
    auditingEnabled: false
  )

proc getSatelliteProfile*(): ProfileSettings =
  ## Satellite profile: Strict isolation + limited network (Requirement 6.3)
  ## For remote/mobile systems with enhanced security
  ProfileSettings(
    isolationLevel: Strict,
    desktopIntegration: true,
    networkAccess: Limited,
    resourceLimits: ResourceLimits(
      maxMemory: 2 * 1024 * 1024 * 1024,  # 2GB
      maxCpu: 0.7,                         # 70% CPU
      maxDisk: 3 * 1024 * 1024 * 1024,    # 3GB
      maxProcesses: 100,
      maxOpenFiles: 512
    ),
    auditingEnabled: true
  )

proc getNetworkIOTProfile*(): ProfileSettings =
  ## Network/IOT profile: Strict isolation + minimal resources (Requirement 6.4)
  ## For embedded devices and IoT systems
  ProfileSettings(
    isolationLevel: Strict,
    desktopIntegration: false,
    networkAccess: Limited,
    resourceLimits: ResourceLimits(
      maxMemory: 512 * 1024 * 1024,       # 512MB
      maxCpu: 0.5,                         # 50% CPU
      maxDisk: 1 * 1024 * 1024 * 1024,    # 1GB
      maxProcesses: 50,
      maxOpenFiles: 256
    ),
    auditingEnabled: true
  )

proc getServerProfile*(): ProfileSettings =
  ## Server profile: Strict isolation + no desktop + enhanced auditing (Requirement 6.5)
  ## For server environments with maximum security
  ProfileSettings(
    isolationLevel: Strict,
    desktopIntegration: false,
    networkAccess: Full,
    resourceLimits: ResourceLimits(
      maxMemory: 16 * 1024 * 1024 * 1024, # 16GB
      maxCpu: 1.0,                         # 100% CPU
      maxDisk: 50 * 1024 * 1024 * 1024,   # 50GB
      maxProcesses: 500,
      maxOpenFiles: 4096
    ),
    auditingEnabled: true
  )

# =============================================================================
# Profile Manager Initialization
# =============================================================================

proc newProfileManager*(profilesDir: string = "", customProfilesDir: string = ""): ProfileManager =
  ## Create a new ProfileManager
  let defaultProfilesDir = if profilesDir.len > 0: profilesDir
                          else: "/etc/nip/profiles/security"
  let defaultCustomDir = if customProfilesDir.len > 0: customProfilesDir
                        else: getHomeDir() / ".config" / "nip" / "profiles" / "security"

  ProfileManager(
    profilesDir: defaultProfilesDir,
    customProfilesDir: defaultCustomDir,
    loadedProfiles: initTable[string, ProfileSettings]()
  )

# =============================================================================
# Profile Loading (Requirement 6.7)
# =============================================================================

proc loadProfile*(profile: SecurityProfile): ProfileSettings =
  ## Load profile settings for a security profile (Requirement 6.7)
  case profile:
  of Workstation:
    getWorkstationProfile()
  of Homestation:
    getHomestationProfile()
  of Satellite:
    getSatelliteProfile()
  of NetworkIOT:
    getNetworkIOTProfile()
  of Server:
    getServerProfile()

proc loadProfile*(manager: var ProfileManager, profile: SecurityProfile): ProfileSettings =
  ## Load profile settings through ProfileManager
  let profileName = $profile
  if profileName in manager.loadedProfiles:
    return manager.loadedProfiles[profileName]

  let settings = loadProfile(profile)
  manager.loadedProfiles[profileName] = settings
  return settings

proc loadProfileFromFile*(path: string): Result[ProfileSettings, string] =
  ## Load profile settings from a custom file
  try:
    if not fileExists(path):
      return err[ProfileSettings]("Profile file not found: " & path)

    let config = parseJson(readFile(path))

    # Parse isolation level
    let isolationStr = config["isolation"].getStr("Standard")
    let isolation = parseEnum[IsolationLevel](isolationStr)

    # Parse network access
    let networkStr = config["networkAccess"].getStr("Relaxed")
    let networkAccess = parseEnum[NetworkAccessLevel](networkStr)

    # Parse resource limits
    let limits = config["resourceLimits"]
    let resourceLimits = ResourceLimits(
      maxMemory: limits["maxMemory"].getInt(4 * 1024 * 1024 * 1024),
      maxCpu: limits["maxCpu"].getFloat(0.8),
      maxDisk: limits["maxDisk"].getInt(5 * 1024 * 1024 * 1024),
      maxProcesses: limits["maxProcesses"].getInt(150),
      maxOpenFiles: limits["maxOpenFiles"].getInt(1024)
    )

    let settings = ProfileSettings(
      isolationLevel: isolation,
      desktopIntegration: config["desktopIntegration"].getBool(true),
      networkAccess: networkAccess,
      resourceLimits: resourceLimits,
      auditingEnabled: config["auditingEnabled"].getBool(false)
    )

    return ok[ProfileSettings](settings)

  except Exception as e:
    return err[ProfileSettings]("Failed to load profile: " & e.msg)

proc loadProfileFromFile*(manager: var ProfileManager, path: string): Result[ProfileSettings, string] =
  ## Load profile settings from file through ProfileManager
  loadProfileFromFile(path)

# =============================================================================
# Profile Application (Requirement 6.7)
# =============================================================================

proc applyProfile*(nippel: var Nippel, settings: ProfileSettings): Result[bool, string] =
  ## Apply profile settings to a Nippel (Requirement 6.7)
  ## Returns true on success
  try:
    # Apply isolation level
    nippel.isolationLevel = settings.isolationLevel
    nippel.profileSettings.isolationLevel = settings.isolationLevel

    # Apply desktop integration
    nippel.profileSettings.desktopIntegration = settings.desktopIntegration

    # Apply network access settings
    nippel.profileSettings.networkAccess = settings.networkAccess

    # Apply resource limits
    nippel.profileSettings.resourceLimits = settings.resourceLimits

    # Apply auditing settings
    nippel.profileSettings.auditingEnabled = settings.auditingEnabled

    # Update last used timestamp
    nippel.lastUsed = now()

    # Save updated configuration
    let cellConfig = %*{
      "nippel": {
        "name": nippel.name,
        "id": nippel.id,
        "version": nippel.version,
        "created": $nippel.created,
        "lastUsed": $nippel.lastUsed
      },
      "profile": {
        "type": $nippel.profile,
        "isolation": $nippel.isolationLevel,
        "desktopIntegration": nippel.profileSettings.desktopIntegration,
        "networkAccess": $nippel.profileSettings.networkAccess,
        "auditingEnabled": nippel.profileSettings.auditingEnabled
      },
      "resourceLimits": {
        "maxMemory": nippel.profileSettings.resourceLimits.maxMemory,
        "maxCpu": nippel.profileSettings.resourceLimits.maxCpu,
        "maxDisk": nippel.profileSettings.resourceLimits.maxDisk,
        "maxProcesses": nippel.profileSettings.resourceLimits.maxProcesses,
        "maxOpenFiles": nippel.profileSettings.resourceLimits.maxOpenFiles
      },
      "paths": {
        "root": nippel.cellRoot,
        "data": nippel.xdgDirs.dataHome,
        "config": nippel.xdgDirs.configHome,
        "cache": nippel.xdgDirs.cacheHome,
        "state": nippel.xdgDirs.stateHome,
        "runtime": nippel.xdgDirs.runtimeDir
      },
      "storage": {
        "merkle_root": nippel.merkleRoot,
        "cas_entries": nippel.casEntries.len,
        "total_size": 0
      },
      "network": {
        "utcp_address": formatUTCPAddress(nippel.utcpAddress)
      },
      "packages": newJArray()
    }

    writeFile(nippel.cellRoot / "cell.json", cellConfig.pretty())

    echo "✅ Applied profile settings to Nippel: ", nippel.name
    echo "   Isolation: ", nippel.isolationLevel
    echo "   Desktop Integration: ", nippel.profileSettings.desktopIntegration
    echo "   Network Access: ", nippel.profileSettings.networkAccess
    echo "   Auditing: ", nippel.profileSettings.auditingEnabled

    return ok(true)

  except Exception as e:
    return err[bool]("Failed to apply profile: " & e.msg)

# =============================================================================
# Profile Customization (Requirement 6.8)
# =============================================================================

proc customizeProfile*(nippel: var Nippel, overrides: ProfileOverrides): Result[bool, string] =
  ## Apply per-Nippel profile overrides (Requirement 6.8)
  ## Returns true on success
  try:
    var modified = false

    # Apply isolation level override
    if overrides.isolationLevel.isSome:
      nippel.isolationLevel = overrides.isolationLevel.get()
      nippel.profileSettings.isolationLevel = overrides.isolationLevel.get()
      modified = true
      echo "   Override: Isolation level -> ", nippel.isolationLevel

    # Apply desktop integration override
    if overrides.desktopIntegration.isSome:
      nippel.profileSettings.desktopIntegration = overrides.desktopIntegration.get()
      modified = true
      echo "   Override: Desktop integration -> ", nippel.profileSettings.desktopIntegration

    # Apply network access override
    if overrides.networkAccess.isSome:
      nippel.profileSettings.networkAccess = overrides.networkAccess.get()
      modified = true
      echo "   Override: Network access -> ", nippel.profileSettings.networkAccess

    # Apply resource limits override
    if overrides.resourceLimits.isSome:
      nippel.profileSettings.resourceLimits = overrides.resourceLimits.get()
      modified = true
      echo "   Override: Resource limits updated"

    # Apply auditing override
    if overrides.auditingEnabled.isSome:
      nippel.profileSettings.auditingEnabled = overrides.auditingEnabled.get()
      modified = true
      echo "   Override: Auditing -> ", nippel.profileSettings.auditingEnabled

    if not modified:
      echo "   No overrides applied"
      return ok(true)

    # Update last used timestamp
    nippel.lastUsed = now()

    # Save updated configuration
    let cellConfig = %*{
      "nippel": {
        "name": nippel.name,
        "id": nippel.id,
        "version": nippel.version,
        "created": $nippel.created,
        "lastUsed": $nippel.lastUsed
      },
      "profile": {
        "type": $nippel.profile,
        "isolation": $nippel.isolationLevel,
        "desktopIntegration": nippel.profileSettings.desktopIntegration,
        "networkAccess": $nippel.profileSettings.networkAccess,
        "auditingEnabled": nippel.profileSettings.auditingEnabled
      },
      "resourceLimits": {
        "maxMemory": nippel.profileSettings.resourceLimits.maxMemory,
        "maxCpu": nippel.profileSettings.resourceLimits.maxCpu,
        "maxDisk": nippel.profileSettings.resourceLimits.maxDisk,
        "maxProcesses": nippel.profileSettings.resourceLimits.maxProcesses,
        "maxOpenFiles": nippel.profileSettings.resourceLimits.maxOpenFiles
      },
      "paths": {
        "root": nippel.cellRoot,
        "data": nippel.xdgDirs.dataHome,
        "config": nippel.xdgDirs.configHome,
        "cache": nippel.xdgDirs.cacheHome,
        "state": nippel.xdgDirs.stateHome,
        "runtime": nippel.xdgDirs.runtimeDir
      },
      "storage": {
        "merkle_root": nippel.merkleRoot,
        "cas_entries": nippel.casEntries.len,
        "total_size": 0
      },
      "network": {
        "utcp_address": formatUTCPAddress(nippel.utcpAddress)
      },
      "packages": newJArray()
    }

    writeFile(nippel.cellRoot / "cell.json", cellConfig.pretty())

    echo "✅ Applied profile customizations to Nippel: ", nippel.name

    return ok(true)

  except Exception as e:
    return err[bool]("Failed to customize profile: " & e.msg)

# =============================================================================
# Profile Information
# =============================================================================

proc getProfileInfo*(profile: SecurityProfile): string =
  ## Get human-readable information about a profile
  let settings = loadProfile(profile)

  result = "Profile: " & $profile & "\n"
  result.add("  Isolation: " & $settings.isolationLevel & "\n")
  result.add("  Desktop Integration: " & $settings.desktopIntegration & "\n")
  result.add("  Network Access: " & $settings.networkAccess & "\n")
  result.add("  Auditing: " & $settings.auditingEnabled & "\n")
  result.add("  Resource Limits:\n")
  result.add("    Max Memory: " & $(settings.resourceLimits.maxMemory div (1024 * 1024)) & " MB\n")
  result.add("    Max CPU: " & $(settings.resourceLimits.maxCpu * 100) & "%\n")
  result.add("    Max Disk: " & $(settings.resourceLimits.maxDisk div (1024 * 1024)) & " MB\n")
  result.add("    Max Processes: " & $settings.resourceLimits.maxProcesses & "\n")
  result.add("    Max Open Files: " & $settings.resourceLimits.maxOpenFiles)

proc listAvailableProfiles*(): seq[string] =
  ## List all available security profiles
  result = @[
    "Workstation - Standard isolation + desktop integration",
    "Homestation - Standard isolation + relaxed network (default)",
    "Satellite - Strict isolation + limited network (remote/mobile)",
    "NetworkIOT - Strict isolation + minimal resources (embedded)",
    "Server - Strict isolation + no desktop + enhanced auditing"
  ]

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

export ProfileOverrides, ProfileError, ProfileManager
export getWorkstationProfile, getHomestationProfile, getSatelliteProfile
export getNetworkIOTProfile, getServerProfile
export newProfileManager, loadProfile, loadProfileFromFile
export applyProfile, customizeProfile
export getProfileInfo, listAvailableProfiles
