#!/usr/bin/env python3
"""
Genesis Voice Relay v2 — Multi-Terminal Redis Edition
Polls Elestio Redis queue for voice commands from Telnyx Qwen3.
Injects commands into target tmux pane via tmux send-keys.
Registers itself so the routing layer knows which terminals are active.

Usage:
    python3 tools/genesis_voice_relay.py --target genesis:claude --desc "Claude Code"
    python3 tools/genesis_voice_relay.py --list           # show active terminals
    python3 tools/genesis_voice_relay.py --test "ls -la"  # direct inject test
    python3 tools/genesis_voice_relay.py --push "show me the handoff"  # push to queue

Install deps:
    pip install redis rich
"""
# /// script
# requires-python = ">=3.10"
# dependencies = ["redis", "rich"]
# ///

import argparse
import json
import subprocess
import sys
import threading
import time
from datetime import datetime, timezone

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

# Redis config (Elestio)
REDIS_HOST = "redis-genesis-u50607.vm.elestio.app"
REDIS_PORT = 26379
REDIS_PASSWORD = "e2ZyYYr4oWRdASI2CaLc-"

# Key schema
TERMINALS_KEY = "genesis:voice:terminals"       # HASH: target → JSON metadata
ACTIVE_KEY = "genesis:voice:active_terminal"    # STRING: current default target
RESULT_KEY_PREFIX = "genesis:voice:result:"     # STRING: result per cmd_id

console = Console()


def queue_key(target: str) -> str:
    """Per-terminal queue key."""
    return f"genesis:voice:queue:{target}"


def get_redis_client():
    """Connect to Elestio Redis. Returns connected client or None."""
    try:
        import redis
        r = redis.Redis(
            host=REDIS_HOST,
            port=REDIS_PORT,
            password=REDIS_PASSWORD,
            ssl=False,
            socket_connect_timeout=10,
            socket_timeout=10,
            decode_responses=True,
        )
        r.ping()
        return r
    except Exception as e:
        console.print(f"[red]Redis connection failed: {e}[/red]")
        return None


def tmux_send(target: str, text: str) -> bool:
    """Send text to tmux pane as if typed. Returns True on success."""
    try:
        result = subprocess.run(
            ["tmux", "send-keys", "-t", target, text, "Enter"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        return result.returncode == 0
    except Exception as e:
        console.print(f"[red]tmux error: {e}[/red]")
        return False


def verify_target(target: str) -> bool:
    """Check that the tmux target session exists."""
    try:
        session = target.split(".")[0]  # strip pane index if present
        result = subprocess.run(
            ["tmux", "has-session", "-t", session],
            capture_output=True,
            timeout=3,
        )
        return result.returncode == 0
    except Exception:
        return False


def register_terminal(r, target: str, description: str) -> None:
    """Register this relay in the Redis terminals hash."""
    now = datetime.now(timezone.utc).isoformat()
    metadata = json.dumps({
        "target": target,
        "description": description or f"Terminal: {target}",
        "started_at": now,
        "last_seen": now,
        "queue": queue_key(target),
    })
    r.hset(TERMINALS_KEY, target, metadata)

    # If no active terminal yet, promote ourselves
    if not r.exists(ACTIVE_KEY):
        r.set(ACTIVE_KEY, target)
        console.print(f"[green]Set as active terminal (no previous active)[/green]")

    console.print(f"[green]Registered terminal: [bold]{target}[/bold][/green]")


def deregister_terminal(r, target: str) -> None:
    """Remove this relay from Redis on clean shutdown."""
    r.hdel(TERMINALS_KEY, target)
    # If we were the active, clear it
    if r.get(ACTIVE_KEY) == target:
        r.delete(ACTIVE_KEY)
    console.print(f"[yellow]Deregistered terminal: {target}[/yellow]")


def keepalive_loop(target: str, description: str, stop_event: threading.Event) -> None:
    """Background thread: refresh last_seen every 60s so stale entries can be detected."""
    while not stop_event.is_set():
        stop_event.wait(60)
        if stop_event.is_set():
            break
        r = get_redis_client()
        if r:
            try:
                existing = r.hget(TERMINALS_KEY, target)
                if existing:
                    data = json.loads(existing)
                    data["last_seen"] = datetime.now(timezone.utc).isoformat()
                    r.hset(TERMINALS_KEY, target, json.dumps(data))
            except Exception:
                pass


def process_command(r, record: dict, target: str) -> None:
    """Inject a command into the target tmux pane."""
    cmd_id = record.get("id", "unknown")
    command = record.get("command", "").strip()
    caller = record.get("caller", "")

    if not command:
        return

    console.print(f"\n[bold green]▶ VOICE COMMAND[/bold green] [{cmd_id}]")
    console.print(f"   [dim]From: {caller or 'WebRTC'} → Target: {target}[/dim]")
    console.print(Panel(command, border_style="green", title=f"Injecting → {target}"))

    success = tmux_send(target, command)

    result_msg = (
        f"Command sent to {target}. Claude is processing: {command[:100]}"
        if success
        else f"Failed to inject into {target}. Check tmux session."
    )
    status = "done" if success else "error"

    r.setex(
        f"{RESULT_KEY_PREFIX}{cmd_id}",
        60,
        json.dumps({"id": cmd_id, "result": result_msg, "status": status}),
    )

    if success:
        console.print(f"[bold green]✓ Injected into {target}[/bold green]")
    else:
        console.print(f"[bold red]✗ Failed to inject into {target}[/bold red]")


def watch_redis(target: str, description: str) -> None:
    """Main loop: register, then poll per-terminal Redis queue and inject commands."""
    q_key = queue_key(target)

    console.print(f"\n[bold cyan]╔═══════════════════════════════════════════╗[/bold cyan]")
    console.print(f"[bold cyan]║   GENESIS VOICE RELAY v2 — MULTI-TERMINAL ║[/bold cyan]")
    console.print(f"[bold cyan]╚═══════════════════════════════════════════╝[/bold cyan]\n")
    console.print(f"  Target : [bold]{target}[/bold]")
    console.print(f"  Queue  : [dim]{q_key}[/dim]")
    console.print(f"  Desc   : {description or 'unset'}\n")

    # Verify tmux target
    if verify_target(target):
        console.print(f"[green]✓ tmux target '{target}' verified[/green]")
    else:
        console.print(f"[yellow]⚠ tmux target '{target}' not found — injections will fail until session starts[/yellow]")

    # Connect Redis
    r = None
    while r is None:
        console.print("[yellow]Connecting to Elestio Redis...[/yellow]")
        r = get_redis_client()
        if r is None:
            console.print("[red]Retrying in 5s...[/red]")
            time.sleep(5)
    console.print("[bold green]✓ Redis connected[/bold green]")

    # Register this terminal
    register_terminal(r, target, description)

    # Start keepalive thread
    stop_event = threading.Event()
    kthread = threading.Thread(
        target=keepalive_loop,
        args=(target, description, stop_event),
        daemon=True,
    )
    kthread.start()

    console.print(f"\n[bold]Ready. Listening on [cyan]{q_key}[/cyan]...[/bold]")
    console.print("[dim]Voice → Telnyx Qwen3 → VPS webhook → Redis → here → tmux → Claude Code[/dim]\n")

    try:
        while True:
            try:
                item = r.blpop(q_key, timeout=5)

                if item is None:
                    continue

                _, payload = item
                try:
                    record = json.loads(payload)
                except json.JSONDecodeError:
                    console.print(f"[red]Invalid JSON in queue: {payload[:100]}[/red]")
                    continue

                process_command(r, record, target)

            except KeyboardInterrupt:
                raise
            except Exception as e:
                console.print(f"[red]Error: {e} — reconnecting...[/red]")
                time.sleep(2)
                r = None
                while r is None:
                    r = get_redis_client()
                    if r is None:
                        time.sleep(5)
                # Re-register after reconnect
                register_terminal(r, target, description)

    except KeyboardInterrupt:
        console.print("\n[yellow]Relay stopping cleanly...[/yellow]")
    finally:
        stop_event.set()
        r_final = get_redis_client()
        if r_final:
            deregister_terminal(r_final, target)
        console.print("[yellow]Relay stopped.[/yellow]")


# ─── CLI ──────────────────────────────────────────────────────────────────────

def cmd_list() -> None:
    """List all active terminals registered in Redis."""
    r = get_redis_client()
    if not r:
        return
    terminals = r.hgetall(TERMINALS_KEY)
    active = r.get(ACTIVE_KEY)

    if not terminals:
        console.print("[yellow]No active terminals registered in Redis.[/yellow]")
        console.print("[dim]Start a relay with: python3 tools/genesis_voice_relay.py --target genesis:claude[/dim]")
        return

    table = Table(title="Active Voice Terminals", border_style="cyan")
    table.add_column("Target", style="bold")
    table.add_column("Description")
    table.add_column("Queue Key", style="dim")
    table.add_column("Last Seen", style="dim")
    table.add_column("Active")

    for target_name, meta_json in terminals.items():
        try:
            meta = json.loads(meta_json)
        except Exception:
            meta = {}
        is_active = "★ YES" if target_name == active else ""
        table.add_row(
            target_name,
            meta.get("description", ""),
            meta.get("queue", queue_key(target_name)),
            meta.get("last_seen", "")[:19].replace("T", " "),
            is_active,
        )

    console.print(table)
    console.print(f"\n[dim]Active terminal (default for new voice commands): [bold]{active or 'none'}[/bold][/dim]")


def cmd_set_active(target: str) -> None:
    """Set a terminal as the active (default) target."""
    r = get_redis_client()
    if not r:
        return
    r.set(ACTIVE_KEY, target)
    console.print(f"[green]Active terminal set to: [bold]{target}[/bold][/green]")


def cmd_test(target: str, command: str) -> None:
    """Directly inject a command without going through Redis."""
    console.print(f"[yellow]TEST: Injecting '{command}' → {target}[/yellow]")
    success = tmux_send(target, command)
    console.print("[green]✓ Success[/green]" if success else "[red]✗ Failed[/red]")


def cmd_push(target: str, command: str) -> None:
    """Manually push a command to a terminal's queue (dev/debug)."""
    r = get_redis_client()
    if not r:
        return
    import uuid
    record = {
        "id": str(uuid.uuid4())[:8],
        "command": command,
        "caller": "manual-push",
        "target": target,
    }
    r.rpush(queue_key(target), json.dumps(record))
    console.print(f"[green]Pushed to {queue_key(target)}: {command}[/green]")


def main():
    parser = argparse.ArgumentParser(
        description="Genesis Voice Relay v2 — Multi-Terminal",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  Start relay for THIS terminal:
    python3 tools/genesis_voice_relay.py --target genesis:claude --desc "Claude Code"

  Start relay for a Gemini session:
    python3 tools/genesis_voice_relay.py --target genesis:gemini --desc "Gemini CLI"

  List all active terminals:
    python3 tools/genesis_voice_relay.py --list

  Switch active terminal:
    python3 tools/genesis_voice_relay.py --activate genesis:gemini

  Test inject (no Redis):
    python3 tools/genesis_voice_relay.py --target genesis:claude --test "ls -la"

  Push command to queue:
    python3 tools/genesis_voice_relay.py --target genesis:claude --push "check the handoff file"
        """,
    )
    parser.add_argument("--target", default="genesis:claude", help="tmux target pane (default: genesis:claude)")
    parser.add_argument("--desc", default="", help="Human description for this terminal (shown in list)")
    parser.add_argument("--list", action="store_true", help="List all active terminals and exit")
    parser.add_argument("--activate", default="", metavar="TARGET", help="Set a terminal as active default and exit")
    parser.add_argument("--test", default="", metavar="CMD", help="Direct-inject CMD without Redis then exit")
    parser.add_argument("--push", default="", metavar="CMD", help="Push CMD to queue then exit (uses --target)")
    args = parser.parse_args()

    if args.list:
        cmd_list()
    elif args.activate:
        cmd_set_active(args.activate)
    elif args.test:
        cmd_test(args.target, args.test)
    elif args.push:
        cmd_push(args.target, args.push)
    else:
        watch_redis(args.target, args.desc)


if __name__ == "__main__":
    main()
