# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Licensed under the MIT License

"""
Mesh CLI — familiar-mesh entry point

Run a standalone mesh gateway or connect as a node.

Usage:
    familiar-mesh gateway              # Start gateway on default port 18789
    familiar-mesh gateway --port 9000  # Custom port
    familiar-mesh gateway --host 0.0.0.0  # Public-facing (use with firewall)
    familiar-mesh node ws://host:18789 --key SECRET  # Join as a node
    familiar-mesh status               # Print config and connection info
    familiar-mesh genkey               # Generate a fresh secret key

Environment:
    ANTHROPIC_API_KEY   Required for gateway (LLM calls)
    FAMILIAR_MESH_KEY   Override secret key
    FAMILIAR_MESH_HOST  Override gateway host
    FAMILIAR_MESH_PORT  Override gateway port
"""

from __future__ import annotations

import argparse
import asyncio
import logging
import os
import secrets
import sys

logger = logging.getLogger(__name__)


def create_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="familiar-mesh",
        description="Familiar mesh networking — gateway and node commands",
    )
    sub = p.add_subparsers(dest="command", required=True)

    # ── gateway ──────────────────────────────────────────────────────────────
    gw = sub.add_parser("gateway", help="Start a mesh gateway (server)")
    gw.add_argument(
        "--host",
        default=os.environ.get("FAMILIAR_MESH_HOST", "127.0.0.1"),
        help="Bind address (default: 127.0.0.1)",
    )
    gw.add_argument(
        "--port",
        type=int,
        default=int(os.environ.get("FAMILIAR_MESH_PORT", "18789")),
        help="Listen port (default: 18789)",
    )
    gw.add_argument(
        "--discovery", action="store_true", default=False, help="Enable mDNS peer discovery"
    )
    gw.add_argument(
        "--public",
        action="store_true",
        default=False,
        help="Bind to 0.0.0.0 (all interfaces) — use with a firewall",
    )
    gw.add_argument(
        "--name",
        default=os.environ.get("FAMILIAR_NODE_NAME", "familiar"),
        help="Node name advertised to peers",
    )

    # ── node ─────────────────────────────────────────────────────────────────
    nd = sub.add_parser("node", help="Connect to a gateway as a leaf node")
    nd.add_argument("gateway_url", help="WebSocket URL, e.g. ws://192.168.1.10:18789")
    nd.add_argument("--key", required=True, help="Gateway secret key")
    nd.add_argument("--name", default="node", help="This node's name")

    # ── status ───────────────────────────────────────────────────────────────
    sub.add_parser("status", help="Show mesh config and connection string")

    # ── genkey ───────────────────────────────────────────────────────────────
    sub.add_parser("genkey", help="Generate a fresh 32-byte hex secret key")

    return p


def cmd_status():
    """Print mesh config and connection string."""
    from .mesh.gateway import MeshConfig, generate_connection_string

    cfg = MeshConfig()
    print("Mesh configuration:")
    print(f"  node_id    : {cfg.get('node_id', 'n/a')}")
    print(f"  node_name  : {cfg.get('node_name', 'familiar')}")
    print(f"  gateway    : {cfg.get('gateway_host', '127.0.0.1')}:{cfg.get('gateway_port', 18789)}")
    print(f"  secret_key : {cfg.get('secret_key', '')[:8]}... (first 8 chars shown)")
    print(f"  discovery  : {'enabled' if cfg.get('discovery_enabled') else 'disabled'}")
    peer_gws = cfg.get("peer_gateways", [])
    if peer_gws:
        print(f"  peers      : {', '.join(peer_gws)}")
    print()
    print("Connection string for nodes:")
    print(f"  {generate_connection_string()}")


def cmd_genkey():
    key = secrets.token_hex(32)
    print(f"Generated key: {key}")
    print("Set in .env:   FAMILIAR_MESH_KEY=" + key)


async def cmd_gateway(args):
    """Start the mesh gateway with a minimal agent."""
    host = "0.0.0.0" if args.public else args.host
    port = args.port

    if host == "0.0.0.0":
        print(
            f"⚠️  Gateway bound to all interfaces (0.0.0.0:{port}).\n"
            f"   Ensure port {port} is protected by a firewall."
        )

    # Create a minimal agent — requires ANTHROPIC_API_KEY or Ollama
    try:
        from .agent import Agent

        agent = Agent()
    except Exception as e:
        print(f"Error creating agent: {e}")
        sys.exit(1)

    # Override discovery if requested
    if args.discovery:
        if agent.mesh_gateway:
            agent.mesh_gateway.config.set("discovery_enabled", True)
        else:
            print("Mesh not enabled on agent — run with FAMILIAR_MESH_ENABLED=true")
            sys.exit(1)

    if not agent.mesh_gateway:
        # Create one on demand for CLI usage
        from .mesh.gateway import MeshGateway

        agent.mesh_gateway = MeshGateway(agent, host=host, port=port)
        agent.mesh_gateway.set_orchestrator(agent.orchestrator)
        if agent.memory:
            agent.mesh_gateway.init_memory_bridge(agent.memory)

    print(f"Starting mesh gateway on {host}:{port}")
    print(f"Node name : {args.name}")
    print("Press Ctrl-C to stop.")
    print()

    await agent.start_mesh(host=host, port=port)


async def cmd_node(args):
    """Connect as a leaf node."""
    from .mesh.gateway import MeshNode

    node = MeshNode(
        gateway_url=args.gateway_url,
        secret_key=args.key,
        name=args.name,
    )

    print(f"Connecting to {args.gateway_url} as '{args.name}'...")
    print("Press Ctrl-C to disconnect.")
    await node.connect()


def cli_main():
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    )
    parser = create_parser()
    args = parser.parse_args()

    if args.command == "status":
        cmd_status()
    elif args.command == "genkey":
        cmd_genkey()
    elif args.command == "gateway":
        try:
            asyncio.run(cmd_gateway(args))
        except KeyboardInterrupt:
            print("\nGateway stopped.")
    elif args.command == "node":
        try:
            asyncio.run(cmd_node(args))
        except KeyboardInterrupt:
            print("\nNode disconnected.")


if __name__ == "__main__":
    cli_main()
