"""
marcus_cli.py — Marcus CLI Client
===================================
Connect to Marcus server via WebSocket from any terminal.
Prompts for IP and port on startup, then provides a command interface.

Start: python3 Client/marcus_cli.py
   OR: python3 Client/marcus_cli.py --ip 192.168.123.164 --port 8765
"""
import asyncio
import argparse
import json
import os
import sys
import time

PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if PROJECT_DIR not in sys.path:
    sys.path.insert(0, PROJECT_DIR)

try:
    import websockets
except ImportError:
    print("Missing dependency: pip install websockets")
    sys.exit(1)

from Core.config_loader import load_config

_net = load_config("Network")
DEFAULT_IP   = _net.get("jetson_ip", "192.168.123.164")
DEFAULT_PORT = _net.get("websocket_port", 8765)


# ── COLORS ───────────────────────────────────────────────────────────────────
class C:
    RESET  = "\033[0m"
    BOLD   = "\033[1m"
    GREEN  = "\033[92m"
    RED    = "\033[91m"
    YELLOW = "\033[93m"
    CYAN   = "\033[96m"
    GRAY   = "\033[90m"
    ORANGE = "\033[38;5;208m"


def _ts():
    return time.strftime("%H:%M:%S")


# ── CONNECTION ───────────────────────────────────────────────────────────────

async def connect_and_run(ip: str, port: int):
    url = f"ws://{ip}:{port}"
    print(f"\n{C.CYAN}Connecting to {url}...{C.RESET}")

    try:
        async with websockets.connect(url, ping_interval=20, ping_timeout=10) as ws:
            print(f"{C.GREEN}Connected to Marcus server{C.RESET}\n")

            # Start receiver task
            receiver = asyncio.create_task(_receive_loop(ws))

            # Input loop
            try:
                await _input_loop(ws)
            except (EOFError, KeyboardInterrupt):
                pass
            finally:
                receiver.cancel()

    except ConnectionRefusedError:
        print(f"{C.RED}Connection refused — is the server running on {ip}:{port}?{C.RESET}")
    except OSError as e:
        print(f"{C.RED}Network error: {e}{C.RESET}")
    except Exception as e:
        print(f"{C.RED}Connection failed: {e}{C.RESET}")


async def _receive_loop(ws):
    """Background task — receives and displays server messages."""
    try:
        async for raw in ws:
            try:
                data = json.loads(raw)
                _handle_message(data)
            except json.JSONDecodeError:
                print(f"{C.GRAY}[?] {raw[:80]}{C.RESET}")
    except websockets.exceptions.ConnectionClosed:
        print(f"\n{C.RED}Disconnected from server{C.RESET}")
    except asyncio.CancelledError:
        pass


async def _input_loop(ws):
    """Main input loop — sends commands to server."""
    _print_help()

    while True:
        try:
            cmd = await asyncio.get_event_loop().run_in_executor(
                None, lambda: input(f"{C.ORANGE}Command: {C.RESET}").strip()
            )
        except (EOFError, KeyboardInterrupt):
            print(f"\n{C.GRAY}Disconnecting...{C.RESET}")
            break

        if not cmd:
            continue

        lower = cmd.lower()

        if lower in ("q", "quit", "exit"):
            break

        elif lower == "help":
            _print_help()
            continue

        elif lower == "status":
            await ws.send(json.dumps({"type": "ping"}))
            continue

        elif lower == "camera":
            await ws.send(json.dumps({"type": "get_camera"}))
            continue

        elif lower.startswith("profile "):
            profile = cmd[8:].strip()
            await ws.send(json.dumps({"type": "set_camera", "profile": profile}))
            continue

        elif lower == "capture":
            await ws.send(json.dumps({"type": "capture"}))
            continue

        elif lower == "log":
            print(f"{C.GRAY}  Server-side nav log not available from CLI{C.RESET}")
            continue

        else:
            # Send as navigation command
            await ws.send(json.dumps({"type": "command", "command": cmd}))


# ── MESSAGE DISPLAY ──────────────────────────────────────────────────────────

def _handle_message(data):
    t = data.get("type", "")

    if t == "frame":
        # Suppress frame data in CLI (no GUI)
        return

    elif t == "status":
        lidar = "ALIVE" if data.get("lidar") else "OFFLINE"
        model = data.get("model", "?")
        camera = data.get("camera", "?")
        yolo = data.get("yolo", False)
        odom = data.get("odometry", False)
        memory = data.get("memory", False)
        print(f"{C.GREEN}  Server ready{C.RESET}")
        print(f"  Model    : {C.CYAN}{model}{C.RESET}")
        print(f"  YOLO     : {C.GREEN if yolo else C.GRAY}{'active' if yolo else 'off'}{C.RESET}")
        print(f"  Odometry : {C.GREEN if odom else C.GRAY}{'active' if odom else 'off'}{C.RESET}")
        print(f"  Memory   : {C.GREEN if memory else C.GRAY}{'active' if memory else 'off'}{C.RESET}")
        print(f"  LiDAR    : {C.GREEN if lidar == 'ALIVE' else C.RED}{lidar}{C.RESET}")
        print(f"  Camera   : {camera}")
        msg = data.get("message", "")
        if msg:
            print(f"  {C.GRAY}{msg}{C.RESET}")
        print()

    elif t == "thinking":
        cmd = data.get("command", "")
        print(f"{C.YELLOW}  Thinking... ({cmd}){C.RESET}")

    elif t == "decision":
        action  = data.get("action", "?")
        speak   = data.get("speak", "")
        elapsed = data.get("elapsed", "?")
        cmd     = data.get("cmd", "?")
        ts      = data.get("timestamp", "")
        btype   = data.get("brain_type", "")

        color = (C.GREEN if cmd == "FORWARD"
                 else C.CYAN if cmd in ("LEFT", "RIGHT", "MULTI", "GOAL")
                 else C.ORANGE if cmd in ("GREETING", "TALK", "LOCAL")
                 else C.RED if cmd in ("STOP", "NONE")
                 else C.GRAY)

        print(f"  [{ts}] {color}{C.BOLD}{action}{C.RESET}  {C.GRAY}({elapsed}s){C.RESET}")
        if speak:
            print(f"  {C.CYAN}Sanad: {speak}{C.RESET}")

    elif t == "camera_config":
        p = data.get("profile", "?")
        w, h, f = data.get("width", "?"), data.get("height", "?"), data.get("fps", "?")
        active = data.get("pipeline_active", False)
        note = data.get("note", "")
        print(f"  Camera: {p} ({w}x{h}@{f}Hz) pipeline={'active' if active else 'stopped'}")
        if note:
            print(f"  {C.GRAY}{note}{C.RESET}")

    elif t == "capture_result":
        if data.get("ok"):
            size_kb = len(data.get("data", "")) * 3 // 4 // 1024
            ts = data.get("timestamp", "")
            print(f"{C.GREEN}  Captured frame ({size_kb}KB) at {ts}{C.RESET}")
        else:
            print(f"{C.RED}  Capture failed: {data.get('message', '?')}{C.RESET}")

    elif t == "pong":
        lidar = "OK" if data.get("lidar") else "OFFLINE"
        ts = data.get("timestamp", "")
        print(f"  [{ts}] Status — LiDAR: {lidar}")

    elif t == "error":
        print(f"{C.RED}  ERROR: {data.get('message', '?')}{C.RESET}")

    else:
        print(f"{C.GRAY}  [{t}] {json.dumps(data)[:100]}{C.RESET}")


def _print_help():
    print(f"""
{C.BOLD}  MARCUS CLI CLIENT{C.RESET}
  {'─' * 40}
  {C.CYAN}Navigation commands:{C.RESET}
    move forward    turn left       stop
    walk forward    turn right 90   halt

  {C.CYAN}System commands:{C.RESET}
    status          — ping server + LiDAR
    camera          — get camera status
    profile <name>  — switch camera (low/medium/high/full)
    capture         — take a photo
    help            — this menu
    q               — disconnect
""")


# ── MAIN ─────────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="Sanad CLI Client (talks to the Marcus brain over WebSocket)")
    parser.add_argument("--ip",   default=None, help="Server IP address")
    parser.add_argument("--port", type=int, default=None, help="Server port")
    args = parser.parse_args()

    eth_ip  = _net.get("jetson_ip", "192.168.123.164")
    wlan_ip = _net.get("jetson_wlan_ip", "10.255.254.86")

    print(f"\n{C.BOLD}{C.ORANGE}  SANAD — CLI Client{C.RESET}")
    print(f"  {'═' * 40}")
    print(f"  {C.GRAY}Connection options:{C.RESET}")
    print(f"    1) eth0  — {eth_ip}:{DEFAULT_PORT}")
    print(f"    2) wlan0 — {wlan_ip}:{DEFAULT_PORT}")
    print(f"    3) custom")
    print()

    if args.ip:
        ip = args.ip
    else:
        choice = input(f"  Choose [1/2/3] or IP [{eth_ip}]: ").strip()
        if choice == "1" or not choice:
            ip = eth_ip
        elif choice == "2":
            ip = wlan_ip
        elif choice == "3":
            ip = input(f"  Server IP: ").strip() or eth_ip
        else:
            # User typed an IP directly
            ip = choice

    if args.port:
        port = args.port
    else:
        port_str = input(f"  Port [{DEFAULT_PORT}]: ").strip()
        port = int(port_str) if port_str else DEFAULT_PORT

    try:
        asyncio.run(connect_and_run(ip, port))
    except KeyboardInterrupt:
        print(f"\n{C.GRAY}Bye.{C.RESET}")


if __name__ == "__main__":
    main()
