# 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.

## UTCP (Universal Tool Communication Protocol) Implementation
##
## This module implements the Universal Tool Communication Protocol for
## AI-addressable resources in NexusOS. UTCP enables seamless communication
## between:
## - nexus (system compiler)
## - nip (package manager)
## - Janus programming language
## - n8n AI agents
## - Local LLMs
## - SystemAdmin-AIs
## - Nippels (user application environments)
## - Nexters (system containers)
##
## UTCP provides a unified addressing scheme and request/response protocol
## for distributed system management and AI-driven automation.

import std/[tables, json, strutils, times, options, uri, sequtils, random]
import utils/resultutils as nipresult
when defined(posix):
  import posix

# UTCP Protocol Types

type
  UTCPScheme* = enum
    ## UTCP protocol schemes
    UtcpPlain = "utcp"      ## Plain UTCP (no encryption)
    UtcpSecure = "utcps"    ## Secure UTCP (TLS encryption)

  UTCPResourceType* = enum
    ## Types of UTCP-addressable resources
    Nippel = "nippel"       ## User application environment
    Nexter = "nexter"       ## System container
    Package = "package"     ## Package resource
    System = "system"       ## System-level resource
    Tool = "tool"           ## Tool endpoint (nexus, nip, janus)
    Agent = "agent"         ## AI agent endpoint
    LLM = "llm"             ## Local LLM endpoint

  UTCPAddress* = object
    ## Universal address for UTCP resources
    scheme*: UTCPScheme     ## Protocol scheme (utcp/utcps)
    host*: string           ## Hostname or IP address
    port*: Option[int]      ## Optional port (default: 7777)
    resourceType*: UTCPResourceType  ## Type of resource
    resourceName*: string   ## Name of the resource
    path*: string           ## Optional sub-path
    query*: Table[string, string]  ## Query parameters

  UTCPMethod* = enum
    ## UTCP request methods
    GET = "GET"             ## Query resource state
    POST = "POST"           ## Modify resource state
    PUT = "PUT"             ## Create/replace resource
    DELETE = "DELETE"       ## Delete resource
    EXEC = "EXEC"           ## Execute command
    SUBSCRIBE = "SUBSCRIBE" ## Subscribe to events
    UNSUBSCRIBE = "UNSUBSCRIBE"  ## Unsubscribe from events

  UTCPRequest* = object
    ## UTCP request structure
    address*: UTCPAddress   ## Target address
    meth*: UTCPMethod       ## Request method (renamed from 'method' to avoid keyword)
    headers*: Table[string, string]  ## Request headers
    payload*: JsonNode      ## Request payload
    timestamp*: DateTime    ## Request timestamp
    requestId*: string      ## Unique request ID

  UTCPStatus* = enum
    ## UTCP response status codes
    Ok = 200                ## Success
    Created = 201           ## Resource created
    Accepted = 202          ## Request accepted
    NoContent = 204         ## Success, no content
    BadRequest = 400        ## Invalid request
    Unauthorized = 401      ## Authentication required
    Forbidden = 403         ## Access denied
    NotFound = 404          ## Resource not found
    MethodNotAllowed = 405  ## Method not supported
    Conflict = 409          ## Resource conflict
    InternalError = 500     ## Server error
    NotImplemented = 501    ## Method not implemented
    ServiceUnavailable = 503  ## Service unavailable

  UTCPResponse* = object
    ## UTCP response structure
    status*: UTCPStatus     ## Response status
    headers*: Table[string, string]  ## Response headers
    data*: JsonNode         ## Response data
    timestamp*: DateTime    ## Response timestamp
    requestId*: string      ## Matching request ID

  UTCPError* = object of CatchableError
    ## UTCP-specific errors
    address*: string        ## Address that caused error
    meth*: string           ## Method that failed (renamed from 'method' to avoid keyword)

  UTCPHandler* = proc(request: UTCPRequest): Result[UTCPResponse, UTCPError] {.closure.}
    ## Handler function for UTCP requests

  UTCPServer* = object
    ## UTCP server for handling requests
    host*: string
    port*: int
    handlers*: Table[string, UTCPHandler]  ## Route -> Handler mapping
    running*: bool

# Constants

const
  UTCP_DEFAULT_PORT* = 7777
  UTCP_VERSION* = "1.0"
  UTCP_USER_AGENT* = "NexusOS-UTCP/1.0"

# UTCP Address Functions

proc newUTCPAddress*(
  host: string,
  resourceType: UTCPResourceType,
  resourceName: string,
  scheme: UTCPScheme = UtcpPlain,
  port: Option[int] = none(int),
  path: string = "",
  query: Table[string, string] = initTable[string, string]()
): UTCPAddress =
  ## Create a new UTCP address
  result = UTCPAddress(
    scheme: scheme,
    host: host,
    port: port,
    resourceType: resourceType,
    resourceName: resourceName,
    path: path,
    query: query
  )

proc parseUTCPAddress*(address: string): Result[UTCPAddress, string] =
  ## Parse a UTCP address string
  ## Format: utcp://host[:port]/resourceType/resourceName[/path][?query]
  try:
    let uri = parseUri(address)

    # Parse scheme
    let scheme = case uri.scheme:
      of "utcp": UtcpPlain
      of "utcps": UtcpSecure
      else:
        return err[UTCPAddress]("Invalid UTCP scheme: " & uri.scheme)

    # Parse host and port
    let host = uri.hostname
    let port = if uri.port != "": some(parseInt(uri.port)) else: none(int)

    # Parse path components
    let pathParts = uri.path.split('/').filterIt(it.len > 0)
    if pathParts.len < 2:
      return err[UTCPAddress]("Invalid UTCP path: must have resourceType/resourceName")

    # Parse resource type
    let resourceType = case pathParts[0]:
      of "nippel": Nippel
      of "nexter": Nexter
      of "package": Package
      of "system": System
      of "tool": Tool
      of "agent": Agent
      of "llm": LLM
      else:
        return err[UTCPAddress]("Invalid resource type: " & pathParts[0])

    let resourceName = pathParts[1]
    let subPath = if pathParts.len > 2: "/" & pathParts[2..^1].join("/") else: ""

    # Parse query parameters
    var query = initTable[string, string]()
    for (key, value) in uri.query.decodeQuery():
      query[key] = value

    return ok(UTCPAddress(
      scheme: scheme,
      host: host,
      port: port,
      resourceType: resourceType,
      resourceName: resourceName,
      path: subPath,
      query: query
    ))

  except Exception as e:
    return err[UTCPAddress]("Failed to parse UTCP address: " & e.msg)

proc formatUTCPAddress*(address: UTCPAddress): string =
  ## Format a UTCP address as a string
  result = $address.scheme & "://" & address.host

  if address.port.isSome:
    result.add(":" & $address.port.get())

  result.add("/" & $address.resourceType & "/" & address.resourceName)

  if address.path.len > 0:
    result.add(address.path)

  if address.query.len > 0:
    result.add("?")
    var first = true
    for key, value in address.query:
      if not first:
        result.add("&")
      result.add(encodeUrl(key) & "=" & encodeUrl(value))
      first = false

proc assignUTCPAddress*(
  resourceType: UTCPResourceType,
  resourceName: string,
  host: string = ""
): Result[UTCPAddress, string] =
  ## Assign a UTCP address to a resource
  ## If host is empty, uses local hostname
  try:
    let actualHost = if host.len > 0: host else:
      when defined(posix):
        var buf: array[256, char]
        if gethostname(cast[cstring](addr buf[0]), 256) == 0:
          $cast[cstring](addr buf[0])
        else:
          "localhost"
      else:
        "localhost"

    let address = newUTCPAddress(
      host = actualHost,
      resourceType = resourceType,
      resourceName = resourceName,
      scheme = UtcpPlain,
      port = none(int)  # Use default port
    )

    return ok(address)

  except Exception as e:
    return err[UTCPAddress]("Failed to assign UTCP address: " & e.msg)

# UTCP Request/Response Functions

proc newUTCPRequest*(
  address: UTCPAddress,
  meth: UTCPMethod,
  payload: JsonNode = newJNull(),
  headers: Table[string, string] = initTable[string, string]()
): UTCPRequest =
  ## Create a new UTCP request
  var actualHeaders = headers
  if not actualHeaders.hasKey("User-Agent"):
    actualHeaders["User-Agent"] = UTCP_USER_AGENT
  if not actualHeaders.hasKey("UTCP-Version"):
    actualHeaders["UTCP-Version"] = UTCP_VERSION

  result = UTCPRequest(
    address: address,
    meth: meth,
    headers: actualHeaders,
    payload: payload,
    timestamp: now(),
    requestId: $now().toTime().toUnix() & "-" & $rand(1000000)
  )

proc newUTCPResponse*(
  status: UTCPStatus,
  data: JsonNode = newJNull(),
  requestId: string = "",
  headers: Table[string, string] = initTable[string, string]()
): UTCPResponse =
  ## Create a new UTCP response
  var actualHeaders = headers
  if not actualHeaders.hasKey("UTCP-Version"):
    actualHeaders["UTCP-Version"] = UTCP_VERSION

  result = UTCPResponse(
    status: status,
    headers: actualHeaders,
    data: data,
    timestamp: now(),
    requestId: requestId
  )

# UTCP Method Handlers
# NOTE: Advanced UTCP protocol features (request routing, method handlers) are not yet implemented
# They will be added when full UTCP protocol support is needed
# For now, the CLI only uses parseUTCPAddress() and formatUTCPAddress()

# Utility Functions

proc isLocalAddress*(address: UTCPAddress): bool =
  ## Check if an address refers to the local host
  let localNames = @["localhost", "127.0.0.1", "::1"]
  when defined(posix):
    var buf: array[256, char]
    if gethostname(cast[cstring](addr buf[0]), 256) == 0:
      let hostname = $cast[cstring](addr buf[0])
      return address.host in localNames or address.host == hostname
  return address.host in localNames

proc getDefaultPort*(scheme: UTCPScheme): int =
  ## Get the default port for a UTCP scheme
  case scheme:
  of UtcpPlain: UTCP_DEFAULT_PORT
  of UtcpSecure: UTCP_DEFAULT_PORT + 1  # 7778 for secure
