# 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/nexter_comm.nim
## Nippel-Nexter Communication Foundation

import std/[json, tables, options, strutils, times, asyncdispatch, random]
import utils/resultutils as nipresult

type
  MessageType* = enum
    ServiceRequest
    ServiceResponse
    ServiceDiscovery
    ServiceAnnouncement
    HealthCheck
    HealthResponse
    AuthRequest
    AuthResponse
    ErrorMessage
    
  CommChannel* = enum
    UnixSocket
    TcpSocket
    SharedMemory
    NamedPipe
    
  ServiceType* = enum
    FileSystem
    Network
    Process
    Storage
    Security
    Monitoring
    
  AuthMethod* = enum
    AuthNone
    Token
    Certificate
    Signature
    
  MessagePriority* = enum
    Low
    Normal
    High
    Critical
    
  CommErrorCode* = enum
    Success
    InvalidMessage
    AuthenticationFailed
    ServiceNotFound
    ServiceUnavailable
    NetworkError
    TimeoutError
    PermissionDenied
    ResourceExhausted
    UnknownError

  MessageHeader* = object
    messageId*: string
    messageType*: MessageType
    timestamp*: DateTime
    sender*: string
    recipient*: string
    priority*: MessagePriority
    correlationId*: string
    ttl*: int
    
  MessageBody* = object
    contentType*: string
    content*: JsonNode
    metadata*: Table[string, string]
    
  CommMessage* = object
    header*: MessageHeader
    body*: MessageBody
    signature*: string
    
  ServiceInfo* = object
    name*: string
    serviceType*: ServiceType
    version*: string
    endpoint*: string
    channel*: CommChannel
    capabilities*: seq[string]
    metadata*: Table[string, string]
    
  NexterInfo* = object
    name*: string
    id*: string
    host*: string
    services*: seq[ServiceInfo]
    status*: string
    lastSeen*: DateTime
    
  AuthContext* = object
    authMethod*: AuthMethod
    token*: string
    certificate*: string
    signature*: string
    expiresAt*: DateTime
    permissions*: seq[string]

  CommManager* = ref object
    nippelName*: string
    knownNexters*: Table[string, NexterInfo]
    authContext*: Option[AuthContext]
    messageHandlers*: Table[MessageType, proc(msg: CommMessage): Future[CommMessage]]
    discoveryInterval*: int
    lastDiscovery*: DateTime

proc newMessageId*(): string =
  let timestamp = now().toTime().toUnix()
  let randomNum = rand(1000000)
  return $timestamp & "-" & $randomNum

proc newMessageHeader*(msgType: MessageType, sender: string, recipient: string = "",
                      priority: MessagePriority = Normal): MessageHeader =
  MessageHeader(
    messageId: newMessageId(),
    messageType: msgType,
    timestamp: now(),
    sender: sender,
    recipient: recipient,
    priority: priority,
    correlationId: "",
    ttl: 300
  )

proc newMessageBody*(content: JsonNode, contentType: string = "application/json"): MessageBody =
  MessageBody(
    contentType: contentType,
    content: content,
    metadata: initTable[string, string]()
  )

proc newCommMessage*(msgType: MessageType, sender: string, content: JsonNode,
                    recipient: string = "", priority: MessagePriority = Normal): CommMessage =
  CommMessage(
    header: newMessageHeader(msgType, sender, recipient, priority),
    body: newMessageBody(content),
    signature: ""
  )

proc toJson*(msg: CommMessage): JsonNode =
  %*{
    "header": {
      "messageId": msg.header.messageId,
      "messageType": $msg.header.messageType,
      "timestamp": msg.header.timestamp.format("yyyy-MM-dd'T'HH:mm:sszzz"),
      "sender": msg.header.sender,
      "recipient": msg.header.recipient,
      "priority": ord(msg.header.priority),
      "correlationId": msg.header.correlationId,
      "ttl": msg.header.ttl
    },
    "body": {
      "contentType": msg.body.contentType,
      "content": msg.body.content,
      "metadata": msg.body.metadata
    },
    "signature": msg.signature
  }

proc fromJson*(json: JsonNode): Result[CommMessage, string] =
  try:
    let header = MessageHeader(
      messageId: json["header"]["messageId"].getStr(),
      messageType: parseEnum[MessageType](json["header"]["messageType"].getStr()),
      timestamp: parse(json["header"]["timestamp"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz"),
      sender: json["header"]["sender"].getStr(),
      recipient: json["header"]["recipient"].getStr(),
      priority: MessagePriority(json["header"]["priority"].getInt()),
      correlationId: json["header"]["correlationId"].getStr(),
      ttl: json["header"]["ttl"].getInt()
    )
    
    var metadata = initTable[string, string]()
    for key, value in json["body"]["metadata"].pairs:
      metadata[key] = value.getStr()
    
    let body = MessageBody(
      contentType: json["body"]["contentType"].getStr(),
      content: json["body"]["content"],
      metadata: metadata
    )
    
    return ok(CommMessage(
      header: header,
      body: body,
      signature: json["signature"].getStr()
    ))
  except Exception as e:
    return err[CommMessage]("Failed to parse message: " & e.msg)

proc newCommManager*(nippelName: string): CommManager =
  CommManager(
    nippelName: nippelName,
    knownNexters: initTable[string, NexterInfo](),
    authContext: none(AuthContext),
    messageHandlers: initTable[MessageType, proc(msg: CommMessage): Future[CommMessage]](),
    discoveryInterval: 30,
    lastDiscovery: now()
  )

proc registerMessageHandler*(manager: CommManager, msgType: MessageType,
                           handler: proc(msg: CommMessage): Future[CommMessage]) =
  manager.messageHandlers[msgType] = handler

proc isMessageExpired*(msg: CommMessage): bool =
  let age = (now() - msg.header.timestamp).inSeconds
  return age > msg.header.ttl

proc validateMessage*(msg: CommMessage): Result[bool, string] =
  if msg.header.messageId.len == 0:
    return err[bool]("Message ID cannot be empty")
  if msg.header.sender.len == 0:
    return err[bool]("Sender cannot be empty")
  if msg.header.ttl <= 0:
    return err[bool]("TTL must be positive")
  if isMessageExpired(msg):
    return err[bool]("Message has expired")
  return ok(true)

proc discoverNexters*(manager: CommManager): Future[seq[NexterInfo]] {.async.} =
  result = @[]
  manager.lastDiscovery = now()

proc findService*(manager: CommManager, serviceType: ServiceType,
                 name: string = ""): Option[ServiceInfo] =
  for nexter in manager.knownNexters.values:
    for service in nexter.services:
      if service.serviceType == serviceType:
        if name.len == 0 or service.name == name:
          return some(service)
  return none(ServiceInfo)

proc createAuthContext*(authMethod: AuthMethod, token: string = "",
                       certificate: string = "", ttl: int = 3600): AuthContext =
  AuthContext(
    authMethod: authMethod,
    token: token,
    certificate: certificate,
    signature: "",
    expiresAt: now() + ttl.seconds,
    permissions: @[]
  )

proc isAuthValid*(auth: AuthContext): bool =
  return now() < auth.expiresAt

proc authenticateMessage*(manager: CommManager, msg: var CommMessage): Result[bool, string] =
  if manager.authContext.isNone:
    return ok(true)
  
  let auth = manager.authContext.get()
  if not isAuthValid(auth):
    return err[bool]("Authentication has expired")
  
  case auth.authMethod:
  of AuthNone:
    return ok(true)
  of Token:
    msg.signature = auth.token
    return ok(true)
  of Certificate:
    msg.signature = auth.certificate
    return ok(true)
  of Signature:
    msg.signature = auth.signature
    return ok(true)

proc routeMessage*(manager: CommManager, msg: CommMessage): Future[Result[CommMessage, string]] {.async.} =
  let validation = validateMessage(msg)
  if validation.isErr:
    return err[CommMessage](validation.error)
  
  if manager.messageHandlers.hasKey(msg.header.messageType):
    try:
      let handler = manager.messageHandlers[msg.header.messageType]
      let response = await handler(msg)
      return ok(response)
    except Exception as e:
      return err[CommMessage]("Handler failed: " & e.msg)
  
  if msg.header.recipient.len > 0:
    return err[CommMessage]("Remote routing not yet implemented")
  
  return err[CommMessage]("No handler found for message type: " & $msg.header.messageType)

proc sendMessage*(manager: CommManager, msg: CommMessage): Future[Result[CommMessage, string]] {.async.} =
  var authMsg = msg
  let authResult = manager.authenticateMessage(authMsg)
  if authResult.isErr:
    return err[CommMessage]("Authentication failed: " & authResult.error)
  return await manager.routeMessage(authMsg)

proc createErrorMessage*(originalMsg: CommMessage, errorCode: CommErrorCode,
                        errorMessage: string): CommMessage =
  let errorContent = %*{
    "error": {
      "code": ord(errorCode),
      "message": errorMessage,
      "originalMessageId": originalMsg.header.messageId
    }
  }
  
  var errorMsg = newCommMessage(
    ErrorMessage,
    originalMsg.header.recipient,
    errorContent,
    originalMsg.header.sender,
    High
  )
  
  errorMsg.header.correlationId = originalMsg.header.messageId
  return errorMsg

proc handleError*(manager: CommManager, error: CommMessage): Future[void] {.async.} =
  let errorInfo = error.body.content["error"]
  let errorCode = CommErrorCode(errorInfo["code"].getInt())
  let errorMessage = errorInfo["message"].getStr()
  let originalMessageId = errorInfo["originalMessageId"].getStr()
  echo "Error ", errorCode, ": ", errorMessage, " (original: ", originalMessageId, ")"

proc requestService*(manager: CommManager, serviceType: ServiceType,
                    request: JsonNode, serviceName: string = ""): Future[Result[JsonNode, string]] {.async.} =
  let serviceOpt = manager.findService(serviceType, serviceName)
  if serviceOpt.isNone:
    return err[JsonNode]("Service not found: " & $serviceType)
  
  let service = serviceOpt.get()
  let requestMsg = newCommMessage(
    ServiceRequest,
    manager.nippelName,
    %*{"service": service.name, "request": request},
    service.name
  )
  
  let responseResult = await manager.sendMessage(requestMsg)
  if responseResult.isErr:
    return err[JsonNode](responseResult.error)
  
  let response = responseResult.get()
  if response.header.messageType == ServiceResponse:
    return ok(response.body.content["response"])
  elif response.header.messageType == ErrorMessage:
    let errorInfo = response.body.content["error"]
    return err[JsonNode](errorInfo["message"].getStr())
  else:
    return err[JsonNode]("Unexpected response type: " & $response.header.messageType)

proc healthCheck*(manager: CommManager, nexterName: string): Future[Result[bool, string]] {.async.} =
  let healthMsg = newCommMessage(
    HealthCheck,
    manager.nippelName,
    %*{"timestamp": $now()},
    nexterName
  )
  
  let responseResult = await manager.sendMessage(healthMsg)
  if responseResult.isErr:
    return err[bool](responseResult.error)
  
  let response = responseResult.get()
  return ok(response.header.messageType == HealthResponse)

proc `$`*(msgType: MessageType): string =
  case msgType:
  of ServiceRequest: "ServiceRequest"
  of ServiceResponse: "ServiceResponse"
  of ServiceDiscovery: "ServiceDiscovery"
  of ServiceAnnouncement: "ServiceAnnouncement"
  of HealthCheck: "HealthCheck"
  of HealthResponse: "HealthResponse"
  of AuthRequest: "AuthRequest"
  of AuthResponse: "AuthResponse"
  of ErrorMessage: "ErrorMessage"

proc `$`*(serviceType: ServiceType): string =
  case serviceType:
  of FileSystem: "FileSystem"
  of Network: "Network"
  of Process: "Process"
  of Storage: "Storage"
  of Security: "Security"
  of Monitoring: "Monitoring"

proc `$`*(channel: CommChannel): string =
  case channel:
  of UnixSocket: "UnixSocket"
  of TcpSocket: "TcpSocket"
  of SharedMemory: "SharedMemory"
  of NamedPipe: "NamedPipe"

proc `$`*(authMethod: AuthMethod): string =
  case authMethod:
  of AuthNone: "None"
  of Token: "Token"
  of Certificate: "Certificate"
  of Signature: "Signature"
