## NIP Session Management
##
## Handles persistent session state with track, channel, and policy management

import std/[os, json, times, tables, strutils, options, sequtils]
import shell_types, types_fixed

# =============================================================================
# Session Storage
# =============================================================================

const
  SESSION_DIR = ".nip_sessions"
  DEFAULT_SESSION = "default"

proc getSessionDir(): string =
  ## Get the session storage directory
  result = getHomeDir() / SESSION_DIR
  if not dirExists(result):
    createDir(result)

proc getSessionPath(name: string): string =
  ## Get the path for a named session
  getSessionDir() / (name & ".json")

# =============================================================================
# Session Serialization
# =============================================================================

proc toJson*(context: SessionContext): JsonNode =
  ## Convert session context to JSON
  result = %*{
    "track": context.track,
    "channels": context.channels,
    "flavor": context.flavor,
    "toolchain": context.toolchain,
    "policy": context.policy,
    "workingDir": context.workingDir,
    "created": context.created.toTime().toUnix(),
    "lastUsed": context.lastUsed.toTime().toUnix()
  }

  if context.transactionId.isSome():
    result["transactionId"] = %context.transactionId.get()

proc fromJson*(node: JsonNode): SessionContext =
  ## Convert JSON to session context
  result = SessionContext(
    track: node["track"].getStr("stable"),
    channels: node["channels"].getElems().mapIt(it.getStr()),
    flavor: node["flavor"].getStr("nexusos"),
    toolchain: node["toolchain"].getStr("latest"),
    workingDir: node["workingDir"].getStr("/"),
    created: fromUnix(node["created"].getInt()).local(),
    lastUsed: fromUnix(node["lastUsed"].getInt()).local()
  )

  # Parse policy table
  if node.hasKey("policy"):
    for key, value in node["policy"].pairs:
      result.policy[key] = value.getStr()

  # Parse optional transaction ID
  if node.hasKey("transactionId"):
    result.transactionId = some(node["transactionId"].getStr())

proc toJson*(metadata: SessionMetadata): JsonNode =
  ## Convert session metadata to JSON
  %*{
    "name": metadata.name,
    "description": metadata.description,
    "created": metadata.created.toTime().toUnix(),
    "lastUsed": metadata.lastUsed.toTime().toUnix(),
    "size": metadata.size
  }

proc fromJsonMetadata*(node: JsonNode): SessionMetadata =
  ## Convert JSON to session metadata
  SessionMetadata(
    name: node["name"].getStr(),
    description: node["description"].getStr(""),
    created: fromUnix(node["created"].getInt()).local(),
    lastUsed: fromUnix(node["lastUsed"].getInt()).local(),
    size: node["size"].getInt()
  )

# =============================================================================
# Session Management Functions
# =============================================================================

proc saveSession*(context: SessionContext, name: string, description: string = ""): Result[string, string] =
  ## Save a session with the given name
  try:
    let sessionPath = getSessionPath(name)
    let sessionData = %*{
      "metadata": %*{
        "name": name,
        "description": description,
        "created": context.created.toTime().toUnix(),
        "lastUsed": now().toTime().toUnix(),
        "size": 0  # Will be calculated after writing
      },
      "context": context.toJson()
    }

    writeFile(sessionPath, sessionData.pretty())

    # Update size in metadata
    let fileSize = getFileSize(sessionPath)
    sessionData["metadata"]["size"] = %fileSize
    writeFile(sessionPath, sessionData.pretty())

    return ok[string, string]("Session saved successfully")
  except Exception as e:
    return err[string, string]("Failed to save session: " & e.msg)

proc loadSession*(name: string): Result[SessionContext, string] =
  ## Load a session by name
  try:
    let sessionPath = getSessionPath(name)
    if not fileExists(sessionPath):
      return err("Session not found: " & name)

    let content = readFile(sessionPath)
    let data = parseJson(content)

    var context = fromJson(data["context"])
    context.lastUsed = now()

    # Update last used time in file
    data["metadata"]["lastUsed"] = %context.lastUsed.toTime().toUnix()
    writeFile(sessionPath, data.pretty())

    return ok(context)
  except Exception as e:
    return err[SessionContext, string]("Failed to load session: " & e.msg)

proc listSessions*(): Result[seq[SessionMetadata], string] =
  ## List all saved sessions
  try:
    var sessions: seq[SessionMetadata] = @[]
    let sessionDir = getSessionDir()

    for file in walkFiles(sessionDir / "*.json"):
      try:
        let content = readFile(file)
        let data = parseJson(content)
        let metadata = fromJsonMetadata(data["metadata"])
        sessions.add(metadata)
      except:
        # Skip corrupted session files
        continue

    # Sort by last used time (most recent first)
    sessions.sort(proc(a, b: SessionMetadata): int =
      cmp(b.lastUsed.toTime().toUnix(), a.lastUsed.toTime().toUnix()))

    return ok(sessions)
  except Exception as e:
    return err("Failed to list sessions: " & e.msg)

proc deleteSession*(name: string): VoidResult[string] =
  ## Delete a session by name
  try:
    let sessionPath = getSessionPath(name)
    if not fileExists(sessionPath):
      return err("Session not found: " & name)

    removeFile(sessionPath)
    return ok(string)
  except Exception as e:
    return err("Failed to delete session: " & e.msg)

proc sessionExists*(name: string): bool =
  ## Check if a session exists
  fileExists(getSessionPath(name))

# =============================================================================
# Session Validation
# =============================================================================

proc validateSession*(context: SessionContext): VoidResult[string] =
  ## Validate session context for consistency

  # Validate track
  if context.track notin ["stable", "testing", "dev", "lts"]:
    return err("Invalid track: " & context.track)

  # Validate channelbasic check)
  if context.channels.len == 0:
    return err("At least one channel must be specified")

  # Validate flavor
  if context.flavor notin ["nexusos", "nexusbsd", "nexussafecore", "nexusunikernel"]:
    return err("Invalid flavor: " & context.flavor)

  # Validate working directory exists
  if not dirExists(context.workingDir):
    return err("Working directory does not exist: " & context.workingDir)

  return ok(string)

# =============================================================================
# Default Session Management
# =============================================================================

proc loadDefaultSession*(): SessionContext =
  ## Load the default session, creating it if it doesn't exist
  let result = loadSession(DEFAULT_SESSION)
  if result.isOk:
    return result.get()
  else:
    # Create and save default session
    let defaultContext = newSessionContext()
    discard saveSession(defaultContext, DEFAULT_SESSION, "Default NIP session")
    return defaultContext

proc saveAsDefault*(context: SessionContext): VoidResult[string] =
  ## Save the current context as the default session
  saveSession(context, DEFAULT_SESSION, "Default NIP session")

# =============================================================================
# Session Import/Export
# =============================================================================

proc exportSession*(name: string, exportPath: string): VoidResult[string] =
  ## Export a session to a file
  try:
    let sessionPath = getSessionPath(name)
    if not fileExists(sessionPath):
      return err("Session not found: " & name)

    copyFile(sessionPath, exportPath)
    return ok(string)
  except Exception as e:
    return err("Failed to export session: " & e.msg)

proc importSession*(importPath: string, name: string): VoidResult[string] =
  ## Import a session from a file
  try:
    if not fileExists(importPath):
      return err("Import file not found: " & importPath)

    # Validate the import file
    let content = readFile(importPath)
    let data = parseJson(content)

    # Ensure it has the required structure
    if not data.hasKey("context") or not data.hasKey("metadata"):
      return err("Invalid session file format")

    # Test parsing
    discard fromJson(data["context"])

    # Copy to session directory with new name
    let sessionPath = getSessionPath(name)

    # Update metadata with new name
    data["metadata"]["name"] = %name
    data["metadata"]["lastUsed"] = %now().toTime().toUnix()

    writeFile(sessionPath, data.pretty())
    return ok(string)
  except Exception as e:
    return err("Failed to import session: " & e.msg)