## nimpak/decentralized.nim
## Decentralized Architecture Foundation for Nippels
##
## This module provides peer-to-peer discovery, distributed UTCP addressing,
## and merkle tree synchronization for building decentralized systems.
##
## Requirements: 13.1-13.5

import std/[tables, options, strutils, times, asyncdispatch, net, json]
import utils/resultutils as nipresult
import nippel_types, nexter_comm, merkle_tree

# =============================================================================
# Peer-to-Peer Discovery Types (Requirement 13.1)
# =============================================================================

type
  # Service Discovery Protocol
  DiscoveryProtocol* = enum
    mDNS          ## Multicast DNS (local network)
    DNSSD         ## DNS Service Discovery
    DHT           ## Distributed Hash Table
    Gossip        ## Gossip protocol

  # Service Type for Discovery
  ServiceKind* = enum
    NippelService    ## Nippel application service
    NexterService    ## Nexter container service
    CASService       ## Content-Addressable Storage service
    MerkleService    ## Merkle tree synchronization service

  # Discovered Service Information
  DiscoveredService* = object
    name*: string              ## Service name
    kind*: ServiceKind         ## Type of service
    host*: string              ## Host address
    port*: int                 ## Service port
    protocol*: DiscoveryProtocol ## How it was discovered
    utcpAddress*: string       ## UTCP address for AI-addressability
    metadata*: Table[string, string] ## Additional service metadata
    discoveredAt*: DateTime    ## When service was discovered
    lastSeen*: DateTime        ## Last time service was seen

  # Service Announcement
  ServiceAnnouncement* = object
    name*: string              ## Service name
    kind*: ServiceKind         ## Type of service
    port*: int                 ## Service port
    utcpAddress*: string       ## UTCP address
    metadata*: Table[string, string] ## Service metadata
    ttl*: int                  ## Time to live (seconds)

  # Discovery Manager
  DiscoveryManager* = ref object
    localServices*: Table[string, ServiceAnnouncement] ## Services we're announcing
    discoveredServices*: Table[string, DiscoveredService] ## Services we've found
    protocol*: DiscoveryProtocol ## Discovery protocol to use
    announceInterval*: int     ## How often to announce (seconds)
    lastAnnounce*: DateTime    ## Last announcement time
    isRunning*: bool           ## Whether discovery is active

# =============================================================================
# Distributed UTCP Addressing (Requirement 13.2)
# =============================================================================

type
  # Distributed UTCP Address
  DistributedUTCPAddress* = object
    scheme*: string            ## Protocol scheme (utcp)
    host*: string              ## Host address (can be hostname or IP)
    port*: int                 ## Port number
    resource*: string          ## Resource path
    query*: Table[string, string] ## Query parameters

  # UTCP Route
  UTCPRoute* = object
    address*: DistributedUTCPAddress ## Destination address
    nextHop*: string           ## Next hop in routing
    metric*: int               ## Routing metric (lower is better)

  # UTCP Router
  UTCPRouter* = ref object
    routes*: Table[string, UTCPRoute] ## Routing table
    localAddress*: DistributedUTCPAddress ## Our local address
    neighbors*: seq[string]    ## Known neighbors

# =============================================================================
# Merkle Tree Synchronization (Requirement 13.3)
# =============================================================================

type
  # Sync Status
  SyncStatus* = enum
    InSync        ## Trees archronized
    OutOfSync     ## Trees differ
    Syncing       ## Synchronization in progress
    SyncFailed    ## Synchronization failed

  # Merkle Diff Entry
  MerkleDiff* = object
    path*: string              ## File path
    localHash*: string         ## Hash in local tree
    remoteHash*: string        ## Hash in remote tree
    action*: DiffAction        ## What action to take

  DiffAction* = enum
    Add           ## File exists remotely, not locally
    Remove        ## File exists locally, not remotely
    Update        ## File exists in both but differs
    Conflict      ## Both changed, needs resolution

  # Sync Session
  SyncSession* = object
    localTree*: MerkleTree     ## Local merkle tree
    remoteAddress*: string     ## Remote peer address
    status*: SyncStatus        ## Current sync status
    diffs*: seq[MerkleDiff]    ## Differences found
    startedAt*: DateTime       ## When sync started
    completedAt*: Option[DateTime] ## When sync completed

  # Sync Manager
  SyncManager* = ref object
    activeSessions*: Table[string, SyncSession] ## Active sync sessions
    syncInterval*: int         ## How often to sync (seconds)
    lastSync*: DateTime        ## Last sync time

# =============================================================================
# Peer-to-Peer Discovery Implementation
# =============================================================================

proc newDiscoveryManager*(protocol: DiscoveryProtocol = mDNS): DiscoveryManager =
  ## Create a new discovery manager
  DiscoveryManager(
    localServices: initTable[string, ServiceAnnouncement](),
    discoveredServices: initTable[string, DiscoveredService](),
    protocol: protocol,
    announceInterval: 30,  # 30 seconds
    lastAnnounce: now(),
    isRunning: false
  )

proc announceService*(manager: DiscoveryManager, announcement: ServiceAnnouncement): Future[Result[bool, string]] {.async.} =
  ## Announce a service for peer-to-peer discovery
  try:
    # Store the announcement
    manager.localServices[announcement.name] = announcement

    # TODO: Implement actual mDNS/DNS-SD announcement
    # For now, this is a placeholder that would:
    # 1. Create mDNS multicast packet
    # 2. Send to 224.0.0.251:5353 (mDNS multicast address)
    # 3. Include service type, name, port, and TXT records

    manager.lastAnnounce = now()
    return ok(true)
  except Exception as e:
    return err[bool]("Failed to announce service: " & e.msg)

proc discoverServices*(manager: DiscoveryManager, serviceKind: ServiceKind): Future[Result[seq[DiscoveredService], string]] {.async.} =
  ## Discover services of a specific kind
  try:
    var services: seq[DiscoveredService] = @[]

    # TODO: Implement actual mDNS/DNS-SD discovery
    # For now, return services from our discovered cache
    for service in manager.discoveredServices.values:
      if service.kind == serviceKind:
        services.add(service)

    return ok(services)
  except Exception as e:
    return err[seq[DiscoveredService]]("Failed to discover services: " & e.msg)

proc startDiscovery*(manager: DiscoveryManager): Future[Result[bool, string]] {.async.} =
  ## Start the discovery process
  try:
    manager.isRunning = true

    # TODO: Implement actual discovery loop
    # This would:
    # 1. Listen for mDNS multicast packets
    # 2. Parse service announcements
    # 3. Update discoveredServices table
    # 4. Periodically announce our own services

    return ok(true)
  except Exception as e:
    manager.isRunning = false
    return err[bool]("Failed to start discovery: " & e.msg)

proc stopDiscovery*(manager: DiscoveryManager): Future[Result[bool, string]] {.async.} =
  ## Stop the discovery process
  try:
    manager.isRunning = false

    # TODO: Send goodbye packets for our services
    # Clean up discovery resources

    return ok(true)
  except Exception as e:
    return err[bool]("Failed to stop discovery: " & e.msg)

proc findService*(manager: DiscoveryManager, name: string): Option[DiscoveredService] =
  ## Find a specific service by name
  if manager.discoveredServices.hasKey(name):
    return some(manager.discoveredServices[name])
  return none(DiscoveredService)

proc updateServiceLastSeen*(manager: DiscoveryManager, name: string) =
  ## Update the last seen time for a service
  if manager.discoveredServices.hasKey(name):
    manager.discoveredServices[name].lastSeen = now()

proc removeStaleServices*(manager: DiscoveryManager, maxAge: int = 300) =
  ## Remove services that haven't been seen recently
  let cutoff = now() - maxAge.seconds
  var toRemove: seq[string] = @[]

  for name, service in manager.discoveredServices.pairs:
    if service.lastSeen < cutoff:
      toRemove.add(name)

  for name in toRemove:
    manager.discoveredServices.del(name)

# =============================================================================
# Distributed UTCP Addressing Implementation
# =============================================================================

proc parseDistributedUTCPAddress*(address: string): Result[DistributedUTCPAddress, string] =
  ## Parse a distributed UTCP address string
  try:
    # Format: utcp://host:port/resource?query
    if not address.startsWith("utcp://"):
      return err[DistributedUTCPAddress]("Invalid UTCP address: must start with utcp://")

    let withoutScheme = address[7..^1]  # Remove "utcp://"

    # Split into host:port and resource
    let parts = withoutScheme.split('/', maxsplit=1)
    if parts.len == 0:
      return err[DistributedUTCPAddress]("Invalid UTCP address: missing host")

    # Parse host and port
    let hostPort = parts[0].split(':')
    let host = hostPort[0]
    let port = if hostPort.len > 1: parseInt(hostPort[1]) else: 8080

    # Parse resource and query
    let resource = if parts.len > 1: "/" & parts[1] else: "/"

    # TODO: Parse query parameters
    let query = initTable[string, string]()

    return ok(DistributedUTCPAddress(
      scheme: "utcp",
      host: host,
      port: port,
      resource: resource,
      query: query
    ))
  except Exception as e:
    return err[DistributedUTCPAddress]("Failed to parse UTCP address: " & e.msg)

proc formatDistributedUTCPAddress*(address: DistributedUTCPAddress): string =
  ## Format a distributed UTCP address as a string
  result = address.scheme & "://" & address.host
  if address.port != 8080:
    result.add(":" & $address.port)
  result.add(address.resource)

  # TODO: Add query parameters if present

proc newUTCPRouter*(localHost: string, localPort: int = 8080): UTCPRouter =
  ## Create a new UTCP router
  UTCPRouter(
    routes: initTable[string, UTCPRoute](),
    localAddress: DistributedUTCPAddress(
      scheme: "utcp",
      host: localHost,
      port: localPort,
      resource: "/",
      query: initTable[string, string]()
    ),
    neighbors: @[]
  )

proc addRoute*(router: UTCPRouter, destination: string, nextHop: string, metric: int = 1): Result[bool, string] =
  ## Add a route to the routing table
  try:
    let addressResult = parseDistributedUTCPAddress(destination)
    if addressResult.isErr:
      return err[bool](addressResult.error)

    let address = addressResult.value
    let route = UTCPRoute(
      address: address,
      nextHop: nextHop,
      metric: metric
    )

    router.routes[destination] = route
    return ok(true)
  except Exception as e:
    return err[bool]("Failed to add route: " & e.msg)

proc findRoute*(router: UTCPRouter, destination: string): Option[UTCPRoute] =
  ## Find a route to a destination
  if router.routes.hasKey(destination):
    return some(router.routes[destination])
  return none(UTCPRoute)

proc addNeighbor*(router: UTCPRouter, neighbor: string) =
  ## Add a neighbor to the router
  if neighbor notin router.neighbors:
    router.neighbors.add(neighbor)

# =============================================================================
# Merkle Tree Synchronization Implementation
# =============================================================================

proc newSyncManager*(): SyncManager =
  ## Create a new sync manager
  SyncManager(
    activeSessions: initTable[string, SyncSession](),
    syncInterval: 60,  # 60 seconds
    lastSync: now()
  )

proc computeDiff*(localTree: MerkleTree, remoteTree: MerkleTree): seq[MerkleDiff] =
  ## Compute differences between local and remote merkle trees
  var diffs: seq[MerkleDiff] = @[]

  # TODO: Implement actual tree diffing
  # This would:
  # 1. Compare root hashes
  # 2. If different, recursively compare subtrees
  # 3. Identify files that are added, removed, updated, or in conflict

  return diffs

proc startSync*(manager: SyncManager, localTree: MerkleTree, remoteAddress: string): Future[Result[string, string]] {.async.} =
  ## Start a synchronization session
  try:
    let sessionId = $now().toTime().toUnix() & "-" & remoteAddress

    let session = SyncSession(
      localTree: localTree,
      remoteAddress: remoteAddress,
      status: Syncing,
      diffs: @[],
      startedAt: now(),
      completedAt: none(DateTime)
    )

    manager.activeSessions[sessionId] = session

    # TODO: Implement actual synchronization
    # This would:
    # 1. Request remote merkle tree
    # 2. Compute differences
    # 3. Transfer files as needed
    # 4. Update local tree
    # 5. Mark session as complete

    return ok(sessionId)
  except Exception as e:
    return err[string]("Failed to start sync: " & e.msg)

proc getSyncStatus*(manager: SyncManager, sessionId: string): Option[SyncSession] =
  ## Get the status of a sync session
  if manager.activeSessions.hasKey(sessionId):
    return some(manager.activeSessions[sessionId])
  return none(SyncSession)

proc cancelSync*(manager: SyncManager, sessionId: string): Result[bool, string] =
  ## Cancel an active sync session
  if not manager.activeSessions.hasKey(sessionId):
    return err[bool]("Sync session not found: " & sessionId)

  manager.activeSessions.del(sessionId)
  return ok(true)

# =============================================================================
# High-Level Decentralized Operations
# =============================================================================

proc buildDecentralizedCluster*(nippels: seq[string], nexters: seq[string]): Future[Result[DiscoveryManager, string]] {.async.} =
  ## Build a decentralized cluster of Nippels and Nexters
  try:
    let manager = newDiscoveryManager(mDNS)

    # Announce all Nippels
    for nippel in nippels:
      let announcement = ServiceAnnouncement(
        name: nippel,
        kind: NippelService,
        port: 8080,
        utcpAddress: "utcp://localhost/nippel/" & nippel,
        metadata: initTable[string, string](),
        ttl: 300
      )

      let result = await manager.announceService(announcement)
      if result.isErr:
        return err[DiscoveryManager](result.error)

    # Announce all Nexters
    for nexter in nexters:
      let announcement = ServiceAnnouncement(
        name: nexter,
        kind: NexterService,
        port: 8081,
        utcpAddress: "utcp://localhost/nexter/" & nexter,
        metadata: initTable[string, string](),
        ttl: 300
      )

      let result = await manager.announceService(announcement)
      if result.isErr:
        return err[DiscoveryManager](result.error)

    # Start discovery
    let startResult = await manager.startDiscovery()
    if startResult.isErr:
      return err[DiscoveryManager](startResult.error)

    return ok(manager)
  except Exception as e:
    return err[DiscoveryManager]("Failed to build cluster: " & e.msg)

proc synchronizeCluster*(manager: SyncManager, trees: Table[string, MerkleTree]): Future[Result[int, string]] {.async.} =
  ## Synchronize merkle trees across a cluster
  try:
    var syncCount = 0

    # TODO: Implement cluster-wide synchronization
    # This would:
    # 1. Discover all peers
    # 2. Exchange merkle tree roots
    # 3. Identify peers that need synchronization
    # 4. Perform synchronization with each peer
    # 5. Return count of successful syncs

    return ok(syncCount)
  except Exception as e:
    return err[int]("Failed to synchronize cluster: " & e.msg)

# =============================================================================
# Utility Functions
# =============================================================================

proc `$`*(protocol: DiscoveryProtocol): string =
  case protocol:
  of mDNS: "mDNS"
  of DNSSD: "DNS-SD"
  of DHT: "DHT"
  of Gossip: "Gossip"

proc `$`*(kind: ServiceKind): string =
  case kind:
  of NippelService: "Nippel"
  of NexterService: "Nexter"
  of CASService: "CAS"
  of MerkleService: "Merkle"

proc `$`*(status: SyncStatus): string =
  case status:
  of InSync: "InSync"
  of OutOfSync: "OutOfSync"
  of Syncing: "Syncing"
  of SyncFailed: "SyncFailed"

proc `$`*(action: DiffAction): string =
  case action:
  of Add: "Add"
  of Remove: "Remove"
  of Update: "Update"
  of Conflict: "Conflict"
