"""
Jama Agent CLI — interfaz de línea de comandos para el agent remoto.

Comandos:
  login      — Autenticarse contra el servidor Jama
  connect    — Conectar al servidor vía WebSocket persistente
  status     — Mostrar info de conexión actual
  disconnect — Cerrar conexión gracefully
  discover   — Descubrir hosts en la red local
  forward    — Port forwarding y SOCKS5 proxy
  version    — Mostrar versión

Flags globales:
  --json     — Output JSON estructurado (para IAs y scripts)
"""
import asyncio
import getpass
import json
import sys
from typing import Optional

import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

from jama_agent import __version__
from jama_agent.auth import (
    clear_credentials,
    load_credentials,
    login as do_login,
    register_agent,
)
from jama_agent.config import load_config, save_config, AgentConfig
from jama_agent.connection import AgentConnection
from jama_agent.discovery import discover_network
from jama_agent.logger import log

app = typer.Typer(
    name="jama-agent",
    help="Jama Security Platform — Remote Agent CLI",
    no_args_is_help=True,
    add_completion=False,
)
console = Console()


def _json_output(data: dict) -> None:
    """Imprime JSON a stdout y sale."""
    print(json.dumps(data, indent=2, default=str))


@app.command()
def login(
    server: str = typer.Option(
        "http://localhost:8000",
        "--server",
        "-s",
        help="URL del servidor Jama",
    ),
    email: str = typer.Option(
        ...,
        "--email",
        "-e",
        help="Email del usuario",
    ),
    password: Optional[str] = typer.Option(
        None,
        "--password",
        "-p",
        help="Password (non-interactive mode). Omitir para prompt interactivo.",
    ),
    stdin_password: bool = typer.Option(
        False,
        "--stdin-password",
        help="Leer password desde stdin (para pipes).",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output JSON estructurado.",
    ),
) -> None:
    """Autenticarse contra el servidor Jama y registrar el agent."""
    # Resolver password: flag > stdin > prompt interactivo
    if password:
        pwd = password
    elif stdin_password:
        pwd = sys.stdin.readline().strip()
        if not pwd:
            if json_output:
                _json_output({"error": "No password received from stdin"})
            else:
                console.print("[bold red]Error:[/] No se recibió password desde stdin")
            raise typer.Exit(code=1)
    else:
        pwd = getpass.getpass("Password: ")

    if not json_output:
        console.status("[bold green]Autenticando...")

    try:
        creds = do_login(server, email, pwd)
    except Exception as exc:
        if json_output:
            _json_output({"error": str(exc), "command": "login"})
        else:
            console.print(f"[bold red]Error de login:[/] {exc}")
        raise typer.Exit(code=1)

    if not json_output:
        console.print("[bold green]Login exitoso!")

    # Registrar el agent si no tiene agent_id
    if not creds.get("agent_id"):
        try:
            agent_id = register_agent(
                server, creds.get("access_token", "")
            )
            creds["agent_id"] = agent_id
            if not json_output:
                console.print(f"[bold green]Agent registrado:[/] {agent_id}")
        except Exception as exc:
            if not json_output:
                console.print(
                    f"[bold yellow]Warning:[/] No se pudo registrar el agent: {exc}"
                )
                console.print("Puedes registrarlo después con 'jama-agent connect'")
    elif not json_output:
        console.print(f"Agent ID: {creds.get('agent_id')}")

    # Guardar server en config
    config = load_config()
    config.server_url = server
    save_config(config)

    if json_output:
        _json_output({
            "status": "ok",
            "server": server,
            "email": email,
            "agent_id": creds.get("agent_id", ""),
            "token_preview": creds.get("access_token", "")[:20] + "...",
        })
    else:
        console.print(
            Panel(
                f"Server: {server}\n"
                f"Email: {email}\n"
                f"Agent ID: {creds.get('agent_id', 'pendiente')}",
                title="Credenciales guardadas",
                border_style="green",
            )
        )


@app.command()
def connect(
    server: Optional[str] = typer.Option(
        None,
        "--server",
        "-s",
        help="URL del servidor (override de config)",
    ),
    insecure: bool = typer.Option(
        False,
        "--insecure",
        help="Permitir conexión ws:// sin TLS (solo para desarrollo)",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output JSON estructurado.",
    ),
) -> None:
    """Conectar al servidor vía WebSocket persistente."""
    creds = load_credentials()
    config = load_config()

    # Determinar servidor
    srv = server or (creds.get("server") if creds else None) or config.server_url

    # Determinar token (API Key o JWT)
    token = ""
    agent_id = ""

    if creds:
        token = creds.get("access_token", "")
        agent_id = creds.get("agent_id", "")
    else:
        if json_output:
            _json_output({"error": "No credentials found. Run 'jama-agent login' first."})
        else:
            console.print(
                "[bold yellow]Sin credenciales.[/] "
                "Ejecuta 'jama-agent login' primero."
            )
        raise typer.Exit(code=1)

    if not agent_id:
        if not json_output:
            console.print(
                "[bold yellow]Sin agent_id.[/] "
                "Registrando agent automáticamente..."
            )
        try:
            agent_id = register_agent(srv, token)
            if not json_output:
                console.print(f"[bold green]Agent registrado:[/] {agent_id}")
        except Exception as exc:
            if json_output:
                _json_output({"error": f"Failed to register agent: {exc}"})
            else:
                console.print(f"[bold red]Error registrando agent:[/] {exc}")
            raise typer.Exit(code=1)

    if not json_output:
        console.print(
            Panel(
                f"Server: {srv}\nAgent ID: {agent_id}\n"
                f"Heartbeat: cada {config.heartbeat_interval}s\n"
                "Presiona Ctrl+C para desconectar",
                title="Conectando...",
                border_style="blue",
            )
        )

    conn = AgentConnection(
        server=srv,
        agent_id=agent_id,
        token=token,
        heartbeat_interval=config.heartbeat_interval,
        reconnect_max_delay=config.reconnect_max_delay,
    )
    conn._allow_insecure = insecure

    try:
        asyncio.run(conn.connect())
    except KeyboardInterrupt:
        if not json_output:
            console.print("\n[bold yellow]Desconectando...")
        asyncio.run(conn.disconnect())
        if json_output:
            _json_output({"status": "disconnected", "agent_id": agent_id})
        else:
            console.print("[bold green]Desconectado limpiamente.")


@app.command()
def status(
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output JSON estructurado.",
    ),
) -> None:
    """Mostrar información de la conexión actual."""
    creds = load_credentials()
    config = load_config()

    if not creds:
        if json_output:
            _json_output({"error": "No credentials stored. Run 'jama-agent login'."})
        else:
            console.print(
                "[bold yellow]Sin credenciales almacenadas.[/]\n"
                "Ejecuta 'jama-agent login' para autenticarte."
            )
        raise typer.Exit(code=1)

    if json_output:
        _json_output({
            "server": creds.get("server", ""),
            "email": creds.get("email", ""),
            "agent_id": creds.get("agent_id", ""),
            "agent_name": config.agent_name,
            "os_type": config.os_type,
            "heartbeat_interval": config.heartbeat_interval,
            "has_token": bool(creds.get("access_token")),
        })
        return

    table = Table(title="Estado del Agent", show_header=False)
    table.add_column("Campo", style="cyan")
    table.add_column("Valor", style="white")

    table.add_row("Server", creds.get("server", "N/A"))
    table.add_row("Email", creds.get("email", "N/A"))
    table.add_row("Agent ID", creds.get("agent_id", "No registrado"))
    table.add_row("Agent Name", config.agent_name)
    table.add_row("OS Type", config.os_type)
    table.add_row("Heartbeat", f"{config.heartbeat_interval}s")
    table.add_row(
        "Token",
        (creds.get("access_token", "")[:20] + "...")
        if creds.get("access_token")
        else "N/A",
    )

    console.print(table)


@app.command()
def disconnect(
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output JSON estructurado.",
    ),
) -> None:
    """Limpiar credenciales y estado de conexión."""
    clear_credentials()
    if json_output:
        _json_output({"status": "disconnected", "credentials_cleared": True})
    else:
        console.print("[bold green]Credenciales eliminadas. Agent desconectado.")


@app.command()
def discover(
    subnet: Optional[str] = typer.Option(
        None,
        "--subnet",
        help="Subnet a escanear (ej: 192.168.1). Auto-detecta si no se especifica.",
    ),
    ping: bool = typer.Option(
        False,
        "--ping",
        help="Hacer ping sweep completo (más lento pero exhaustivo)",
    ),
    send: bool = typer.Option(
        False,
        "--send",
        help="Enviar resultado al servidor vía WebSocket",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output JSON estructurado.",
    ),
) -> None:
    """Descubrir hosts en la red local (ARP + ping sweep opcional)."""
    if not json_output:
        with console.status("[bold green]Descubriendo red..."):
            result = discover_network(subnet=subnet, use_ping=ping)
    else:
        result = discover_network(subnet=subnet, use_ping=ping)

    if json_output:
        _json_output(result)
        return

    hosts = result.get("hosts", [])

    if not hosts:
        console.print("[bold yellow]No se encontraron hosts.")
        return

    table = Table(title=f"Red descubierta ({result.get('subnet', '?')}.0/24)")
    table.add_column("IP", style="cyan")
    table.add_column("MAC", style="green")
    table.add_column("Hostname", style="white")

    for host in hosts:
        table.add_row(
            host.get("ip", ""),
            host.get("mac", ""),
            host.get("hostname", ""),
        )

    console.print(table)
    console.print(
        f"\n[bold]Total:[/] {result.get('total', 0)} hosts | "
        f"Método: {result.get('method', 'arp')}"
    )

    if send:
        console.print("[bold yellow]--send requiere conexión activa (use 'connect').")


@app.command()
def forward(
    remote: str = typer.Option(
        ..., "--remote", "-r", help="HOST:PORT remoto (ej: 10.0.0.5:3306)"
    ),
    local: int = typer.Option(
        0, "--local", "-l", help="Puerto local (0 = auto)"
    ),
    socks: bool = typer.Option(
        False, "--socks", help="Levantar SOCKS5 proxy en vez de forward directo"
    ),
    socks_port: int = typer.Option(
        1080, "--socks-port", help="Puerto para SOCKS5 proxy"
    ),
    server: Optional[str] = typer.Option(
        None, "--server", "-s", help="URL del servidor (override)"
    ),
    insecure: bool = typer.Option(
        False, "--insecure", help="Permitir ws:// sin TLS"
    ),
) -> None:
    """Port forwarding y SOCKS5 proxy vía reverse tunnel."""
    from jama_agent.tunnel import Socks5Server

    creds = load_credentials()
    config = load_config()

    if not creds or not creds.get("agent_id"):
        console.print(
            "[bold red]Debes autenticarte primero:[/] jama-agent login"
        )
        raise typer.Exit(code=1)

    srv = server or (creds.get("server") if creds else None) or config.server_url
    token = creds.get("access_token", "")
    agent_id = creds.get("agent_id", "")

    conn = AgentConnection(
        server=srv,
        agent_id=agent_id,
        token=token,
        heartbeat_interval=config.heartbeat_interval,
        reconnect_max_delay=config.reconnect_max_delay,
    )
    conn._allow_insecure = insecure

    async def run_forward() -> None:
        """Ejecuta forward o SOCKS5 sobre la conexión WS."""
        # Iniciar conexión WS en background
        ws_task = asyncio.create_task(conn.connect())

        # Esperar a que conecte
        for _ in range(50):
            if conn.is_connected:
                break
            await asyncio.sleep(0.1)

        if not conn.is_connected:
            console.print("[bold red]No se pudo conectar al servidor")
            return

        tunnel_mgr = conn.get_tunnel_manager()

        if socks:
            # Modo SOCKS5 proxy
            socks_server = Socks5Server(
                tunnel_manager=tunnel_mgr,
                host="127.0.0.1",
                port=socks_port,
            )
            await socks_server.start()
            console.print(
                Panel(
                    f"SOCKS5 proxy: 127.0.0.1:{socks_port}\n"
                    f"Server: {srv}\nAgent: {agent_id}\n\n"
                    "Configura tu herramienta:\n"
                    f"  export ALL_PROXY=socks5://127.0.0.1:{socks_port}\n"
                    f"  proxychains4 nmap {remote.split(':')[0] if ':' in remote else 'TARGET'}\n\n"
                    "Presiona Ctrl+C para detener",
                    title="SOCKS5 Tunnel Activo",
                    border_style="green",
                )
            )
            try:
                await ws_task
            except asyncio.CancelledError:
                pass
            finally:
                await socks_server.stop()
        else:
            # Modo port forward directo
            if ":" not in remote:
                console.print("[bold red]Formato: HOST:PORT (ej: 10.0.0.5:3306)")
                return

            target_host, target_port_str = remote.rsplit(":", 1)
            try:
                target_port = int(target_port_str)
            except ValueError:
                console.print(f"[bold red]Puerto inválido: {target_port_str}")
                return

            # Abrir listener local
            async def handle_fwd_client(
                reader: asyncio.StreamReader,
                writer: asyncio.StreamWriter,
            ) -> None:
                """Cada conexión al puerto local abre un canal tunnel."""
                channel = await tunnel_mgr.open_channel(target_host, target_port)
                if not channel:
                    writer.close()
                    return

                channel.reader = reader
                channel.writer = writer

                # Relay bidireccional usando el Socks5Server relay logic
                socks_srv = Socks5Server(tunnel_manager=tunnel_mgr)
                await socks_srv._relay(channel, reader, writer)

            fwd_server = await asyncio.start_server(
                handle_fwd_client, "127.0.0.1", local
            )
            actual_port = fwd_server.sockets[0].getsockname()[1]

            console.print(
                Panel(
                    f"127.0.0.1:{actual_port} → {target_host}:{target_port}\n"
                    f"Server: {srv}\nAgent: {agent_id}\n\n"
                    "Presiona Ctrl+C para detener",
                    title="Port Forward Activo",
                    border_style="green",
                )
            )

            try:
                await ws_task
            except asyncio.CancelledError:
                pass
            finally:
                fwd_server.close()
                await fwd_server.wait_closed()

    try:
        asyncio.run(run_forward())
    except KeyboardInterrupt:
        console.print("\n[bold yellow]Túnel detenido.")
        asyncio.run(conn.disconnect())


@app.command()
def tunnels() -> None:
    """Listar túneles activos (requiere conexión activa)."""
    console.print(
        Panel(
            "Los túneles se gestionan durante una sesión 'forward' activa.\n"
            "Usa 'jama-agent forward --socks' para iniciar un SOCKS5 proxy,\n"
            "o 'jama-agent forward --remote HOST:PORT' para un forward directo.",
            title="Túneles",
            border_style="blue",
        )
    )


@app.command()
def version(
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output JSON estructurado.",
    ),
) -> None:
    """Mostrar la versión del Agent CLI."""
    if json_output:
        _json_output({"version": __version__, "name": "jama-agent"})
    else:
        console.print(f"[bold]jama-agent[/] v{__version__}")


if __name__ == "__main__":
    app()
