from __future__ import annotations

import json
import platform
import secrets
import subprocess
import sys
from pathlib import Path
from typing import Any

import httpx
from InquirerPy import inquirer


DEFAULT_SERVER_URL = "https://alsoml.com"
STATE_DIR = Path.home() / ".alsogc"
STATE_FILE = STATE_DIR / "state.json"


def _load_state() -> dict[str, Any]:
    if STATE_FILE.exists():
        return json.loads(STATE_FILE.read_text())
    return {
        "server_url": DEFAULT_SERVER_URL,
        "peer_id": _generate_peer_id(),
        "groups": {},
        "children": {},
    }


def _save_state(state: dict[str, Any]) -> None:
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, indent=2))


def _generate_peer_id() -> str:
    hostname = platform.node().split(".")[0].lower()
    suffix = secrets.token_hex(3)
    return f"{hostname}-{suffix}"


def _has_claude_descendant(pid: str) -> bool:
    try:
        ps_result = subprocess.run(
            ["ps", "-eo", "pid,ppid,command"],
            capture_output=True,
            text=True,
        )
    except FileNotFoundError:
        return False

    children_of: dict[str, list[tuple[str, str]]] = {}
    for line in ps_result.stdout.strip().splitlines()[1:]:
        parts = line.strip().split(None, 2)
        if len(parts) < 3:
            continue
        child_pid, parent_pid, cmd = parts
        children_of.setdefault(parent_pid, []).append((child_pid, cmd))

    stack = [pid]
    while stack:
        current = stack.pop()
        for child_pid, cmd in children_of.get(current, []):
            if "claude" in cmd.lower():
                return True
            stack.append(child_pid)
    return False


def _discover_claude_sessions() -> list[dict[str, str]]:
    result = subprocess.run(
        [
            "tmux", "list-panes", "-a",
            "-F", "#{session_name}:#{window_index}.#{pane_index} #{pane_pid} #{pane_current_command}",
        ],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        return []

    sessions: list[dict[str, str]] = []
    for line in result.stdout.strip().splitlines():
        parts = line.split(" ", 2)
        if len(parts) < 3:
            continue
        pane_id, pid, command = parts
        if "claude" not in command.lower() and not _has_claude_descendant(pid):
            continue
        session_name, rest = pane_id.split(":", 1)
        sessions.append({
            "pane_id": pane_id,
            "session_name": session_name,
            "pid": pid,
            "command": command,
        })
    return sessions


def _fetch_groups(server_url: str) -> list[str]:
    try:
        resp = httpx.get(f"{server_url}/auth/groups", timeout=10)
        resp.raise_for_status()
        return resp.json()
    except (httpx.HTTPError, json.JSONDecodeError):
        return []


def _create_group(server_url: str, group_id: str) -> str | None:
    try:
        resp = httpx.post(
            f"{server_url}/auth/groups",
            json={"group_id": group_id},
            timeout=10,
        )
        if resp.status_code == 409:
            print(f"  Group '{group_id}' already exists.", file=sys.stderr)
            return None
        resp.raise_for_status()
        return resp.json()["password"]
    except httpx.HTTPError as e:
        print(f"  Failed to create group: {e}", file=sys.stderr)
        return None


def _verify_group(server_url: str, group_id: str, password: str) -> bool:
    try:
        resp = httpx.post(
            f"{server_url}/auth/groups/{group_id}/verify",
            json={"password": password},
            timeout=10,
        )
        resp.raise_for_status()
        return resp.json().get("ok", False)
    except httpx.HTTPError:
        return False


def _setup_tui_sidebar(
    target_pane: str,
    server_url: str,
    group_id: str,
    peer_id: str,
    groups_header: str,
) -> str | None:
    split_result = subprocess.run(
        [
            "tmux", "split-window", "-h",
            "-t", target_pane,
            "-l", "40%",
            "-P", "-F", "#{session_name}:#{window_index}.#{pane_index}",
        ],
        capture_output=True,
        text=True,
    )
    if split_result.returncode != 0:
        print(f"  Failed to split tmux window: {split_result.stderr}", file=sys.stderr)
        return None

    new_pane = split_result.stdout.strip()
    cmd = (
        f"relay-tui "
        f"--server-url {server_url} "
        f"--group-id {group_id} "
        f"--peer-id {peer_id} "
        f"--target-pane {target_pane} "
        f"--groups-header '{groups_header}'"
    )
    subprocess.run(["tmux", "send-keys", "-t", new_pane, cmd, "Enter"], check=True)
    subprocess.run(["tmux", "select-pane", "-t", target_pane], check=True)
    return new_pane


def _build_groups_header(state: dict[str, Any], group_ids: list[str]) -> str:
    pairs: list[str] = []
    for gid in group_ids:
        pw = state["groups"].get(gid, {}).get("password", "")
        if pw:
            pairs.append(f"{gid}:{pw}")
    return ",".join(pairs)


def _update_claude_mcp_config(state: dict[str, Any], pane_id: str) -> None:
    claude_config_path = Path.home() / ".claude.json"
    if not claude_config_path.exists():
        print("  ~/.claude.json not found. Configure MCP manually.", file=sys.stderr)
        return

    config = json.loads(claude_config_path.read_text())
    child = state["children"].get(pane_id, {})
    child_groups = child.get("groups", [])

    if not child_groups:
        return

    groups_header = _build_groups_header(state, child_groups)

    cwd = Path.cwd().as_posix()
    projects = config.setdefault("projects", {})
    project = projects.setdefault(cwd, {})
    mcp_servers = project.setdefault("mcpServers", {})

    mcp_servers["relay-server"] = {
        "type": "http",
        "url": f"{state['server_url']}/mcp",
        "headers": {
            "X-Peer-Id": state["peer_id"],
            "X-Groups": groups_header,
        },
    }

    claude_config_path.write_text(json.dumps(config, indent=2))


def _action_join_group(state: dict[str, Any]) -> None:
    server_url = state["server_url"]
    remote_groups = _fetch_groups(server_url)

    choices = [*remote_groups, "-- Create new group --"]
    selected = inquirer.fuzzy(
        message="Select or search for a group:",
        choices=choices,
    ).execute()

    if selected == "-- Create new group --":
        group_id = inquirer.text(message="New group name:").execute()
        if not group_id:
            return
        password = _create_group(server_url, group_id)
        if password is None:
            return
        state["groups"][group_id] = {"password": password}
        _save_state(state)
        print(f"\n  Created group '{group_id}'")
        print(f"  Password: {password}")
        print("  Share this password with your teammates.\n")
        return

    group_id = selected
    if group_id in state["groups"]:
        print(f"  Already joined '{group_id}'.\n")
        return

    password = inquirer.secret(message=f"Password for '{group_id}':").execute()
    if not _verify_group(server_url, group_id, password):
        print("  Invalid password.\n", file=sys.stderr)
        return

    state["groups"][group_id] = {"password": password}
    _save_state(state)
    print(f"  Joined '{group_id}'.\n")


def _action_manage_children(state: dict[str, Any]) -> None:
    sessions = _discover_claude_sessions()
    if not sessions:
        print("  No Claude Code sessions found in tmux.\n", file=sys.stderr)
        return

    joined_groups = list(state["groups"].keys())
    if not joined_groups:
        print("  Join a group first.\n", file=sys.stderr)
        return

    session_choices = [
        {"name": f"{s['pane_id']}  (pid {s['pid']})", "value": s["pane_id"]}
        for s in sessions
    ]
    pane_id = inquirer.fuzzy(
        message="Select a Claude Code session:",
        choices=session_choices,
    ).execute()

    child = state["children"].setdefault(pane_id, {"groups": []})
    current_groups = set(child["groups"])

    group_choices = [
        {"name": gid, "value": gid, "enabled": gid in current_groups}
        for gid in joined_groups
    ]

    new_groups: list[str] = inquirer.checkbox(
        message=f"Select groups for {pane_id}:",
        choices=group_choices,
    ).execute()

    child["groups"] = new_groups
    _save_state(state)
    _update_claude_mcp_config(state, pane_id)
    print(f"  Updated {pane_id}: groups = {new_groups}")
    print("  MCP config updated. Restart Claude Code to pick up changes.")

    if new_groups and not child.get("tui_pane"):
        launch_tui = inquirer.confirm(message="Launch TUI sidebar?", default=True).execute()
        if launch_tui:
            tui_pane = _setup_tui_sidebar(
                target_pane=pane_id,
                server_url=state["server_url"],
                group_id=new_groups[0],
                peer_id=state["peer_id"],
                groups_header=_build_groups_header(state, new_groups),
            )
            if tui_pane:
                child["tui_pane"] = tui_pane
                _save_state(state)
                print(f"  TUI running in pane: {tui_pane}")
    print()


def _action_show_status(state: dict[str, Any]) -> None:
    print(f"\n  Server:  {state['server_url']}")
    print(f"  Groups:  {', '.join(state['groups'].keys()) or '(none)'}")
    if state["children"]:
        print("  Sessions:")
        for pane_id, child in state["children"].items():
            groups = ", ".join(child["groups"]) or "(none)"
            print(f"    {pane_id} -> {groups}")
    print()


def main() -> None:
    state = _load_state()

    server_url = inquirer.text(
        message="Server URL:",
        default=state.get("server_url", DEFAULT_SERVER_URL),
    ).execute()
    state["server_url"] = server_url
    _save_state(state)

    while True:
        action = inquirer.select(
            message="What would you like to do?",
            choices=[
                "Join / Create a group",
                "Connect sessions (assign groups to Claude Code)",
                "Show status",
                "Exit",
            ],
        ).execute()

        if action.startswith("Join"):
            _action_join_group(state)
        elif action.startswith("Connect"):
            _action_manage_children(state)
        elif action.startswith("Show"):
            _action_show_status(state)
        else:
            break


if __name__ == "__main__":
    main()
