from __future__ import annotations

import asyncio
import contextlib
import json
from pathlib import Path
from typing import Annotated

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

from ..config import AppConfig
from ..chat.terminal_client import WebSocketTerminalClient
from .app import app, console
from .chat_local import run_local_interactive_session
from .shared import EXIT_CODE_SESSION_ENDED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND


def _base_url() -> str:
    config = AppConfig.load()
    return f"http://127.0.0.1:{config.server.port}"


def _format_created(created_raw: object) -> str:
    created_str = str(created_raw) if created_raw else ""
    if created_str:
        from datetime import datetime  # noqa: PLC0415

        with contextlib.suppress(ValueError, TypeError):
            dt = datetime.fromisoformat(created_str)
            created_str = dt.strftime("%H:%M:%S")
    return created_str


def _parse_resume_option(resume: str | None) -> str | bool | None:
    if resume == "__picker__":
        return True
    if resume:
        return resume
    return None


def _require_server_running(console: Console) -> None:
    console.print("[red]Server not running. Start it with: claude-board serve[/red]")


def _list_chat_sessions(console: Console) -> None:
    base_url = _base_url()
    try:
        resp = httpx.get(f"{base_url}/api/sessions", timeout=10)
        data = resp.json()
        sessions = data.get("sessions", [])
    except (httpx.ConnectError, httpx.TimeoutException, OSError):
        _require_server_running(console)
        raise typer.Exit(1) from None

    if not sessions:
        console.print("[dim]No active sessions.[/dim]")
        console.print("Start a new session with: [bold]claude-board chat[/bold]")
        return

    table = Table(title="Active Chat Sessions")
    table.add_column("ID", style="cyan")
    table.add_column("Name", style="green")
    table.add_column("Project", style="blue")
    table.add_column("State", style="yellow")
    table.add_column("Clients", style="magenta")
    table.add_column("Created", style="dim")

    for session in sessions:
        created_str = _format_created(session.get("created_at", ""))
        clients = f"T:{session.get('terminal_clients', 0)} W:{session.get('web_clients', 0)}"
        table.add_row(
            str(session.get("session_id", ""))[:8],
            str(session.get("name") or "-"),
            str(session.get("project_name", "")),
            str(session.get("state", "")),
            clients,
            created_str,
        )

    console.print(table)


async def _kill_chat_session(console: Console, session_id: str) -> None:
    base_url = _base_url()
    try:
        resp = httpx.delete(f"{base_url}/api/sessions/{session_id}", timeout=10)
        resp.raise_for_status()
        console.print(f"[green]Session '{session_id}' stopped.[/green]")
    except httpx.HTTPStatusError as e:
        if e.response.status_code == HTTP_NOT_FOUND:
            console.print(f"[red]Session '{session_id}' not found.[/red]")
        else:
            console.print(f"[red]Failed to stop session: {e}[/red]")
        raise typer.Exit(1) from None
    except (httpx.ConnectError, httpx.TimeoutException, OSError):
        _require_server_running(console)
        raise typer.Exit(1) from None


async def _send_to_session(console: Console, session_id: str, prompt: str) -> None:
    base_url = _base_url()
    try:
        resp = httpx.post(
            f"{base_url}/api/sessions/{session_id}/prompt",
            json={"prompt": prompt},
            timeout=10,
        )
        resp.raise_for_status()
        console.print(f"[green]Prompt sent to session '{session_id}'.[/green]")
    except httpx.HTTPStatusError as e:
        if e.response.status_code == HTTP_NOT_FOUND:
            console.print(f"[red]Session '{session_id}' not found.[/red]")
        elif e.response.status_code == HTTP_BAD_REQUEST:
            console.print(f"[red]Session '{session_id}' is not running.[/red]")
        else:
            console.print(f"[red]Failed to send prompt: {e}[/red]")
        raise typer.Exit(1) from None
    except (httpx.ConnectError, httpx.TimeoutException, OSError):
        _require_server_running(console)
        raise typer.Exit(1) from None


def _resolve_auto_attach_target(
    console: Console, *, base_url: str
) -> tuple[str, str]:
    try:
        resp = httpx.get(f"{base_url}/api/sessions", timeout=10)
        data = resp.json()
        sessions = [s for s in data.get("sessions", []) if s.get("is_alive")]
    except (httpx.ConnectError, httpx.TimeoutException, OSError):
        _require_server_running(console)
        raise typer.Exit(1) from None

    if not sessions:
        console.print("[red]No active sessions to attach to.[/red]")
        console.print("Start a new session with: [bold]claude-board chat[/bold]")
        raise typer.Exit(1)
    if len(sessions) > 1:
        console.print("[yellow]Multiple sessions available. Please specify one:[/yellow]")
        _list_chat_sessions(console)
        raise typer.Exit(1)
    session_id = sessions[0].get("session_id", "")
    session_name = sessions[0].get("name") or session_id
    return session_id, session_name


def _resolve_named_attach_target(
    console: Console, *, base_url: str, session_id: str
) -> tuple[str, str]:
    try:
        resp = httpx.get(f"{base_url}/api/sessions/{session_id}", timeout=10)
        resp.raise_for_status()
        session_data = resp.json()
    except httpx.HTTPStatusError as e:
        if e.response.status_code == HTTP_NOT_FOUND:
            console.print(f"[red]Session '{session_id}' not found.[/red]")
        else:
            console.print(f"[red]Failed to get session: {e}[/red]")
        raise typer.Exit(1) from None
    except (httpx.ConnectError, httpx.TimeoutException, OSError):
        _require_server_running(console)
        raise typer.Exit(1) from None

    session_name = session_data.get("name") or session_id
    if not session_data.get("is_alive"):
        console.print(f"[red]Session '{session_id}' is not running.[/red]")
        raise typer.Exit(1)
    return session_id, session_name


async def _attach_to_session(console: Console, session_id: str) -> None:
    base_url = _base_url()

    if session_id == "__auto__":
        target_session_id, session_name = _resolve_auto_attach_target(
            console, base_url=base_url
        )
    else:
        target_session_id, session_name = _resolve_named_attach_target(
            console, base_url=base_url, session_id=session_id
        )

    console.print(f"[green]Attaching to session '{session_name}'...[/green]")
    console.print("[dim]Press Ctrl+Q to detach[/dim]\n")

    client = WebSocketTerminalClient(base_url, target_session_id)
    exit_code = await client.attach()
    if exit_code == 0:
        console.print("\n[green]Detached from session.[/green]")
    elif exit_code == EXIT_CODE_SESSION_ENDED:
        console.print("\n[yellow]Session ended.[/yellow]")
    else:
        console.print(f"\n[red]Error during session. Exit Code: {exit_code}[/red]")


async def _run_headless_session(
    console: Console,
    *,
    resume: str | bool | None,
    name: str | None,
    project_path: str,
) -> None:
    server_url = _base_url()
    console.print(
        Panel.fit(
            "[bold green]Starting Claude Code Session (Headless)[/bold green]\n\n"
            f"[dim]Project:[/dim]  {project_path}\n"
            f"[dim]Resume:[/dim]   {resume or 'new session'}",
            title="Claude Board Chat",
            border_style="green",
        )
    )

    try:
        resp = httpx.post(
            f"{server_url}/api/sessions",
            json={
                "name": name,
                "project_path": project_path,
                "resume": resume,
                "rows": 24,
                "cols": 80,
            },
            timeout=30,
        )
        result = resp.json()
    except (httpx.ConnectError, httpx.TimeoutException, OSError) as e:
        console.print("[red]Cannot connect to server. Is 'claude-board serve' running?[/red]")
        console.print(f"[dim]Error: {e}[/dim]")
        raise typer.Exit(1) from None
    except (httpx.HTTPStatusError, json.JSONDecodeError, KeyError, TypeError) as e:
        console.print(f"[red]Failed to create session: {e}[/red]")
        raise typer.Exit(1) from None

    if result.get("status") != "ok":
        console.print(f"[red]Failed to create session: {result}[/red]")
        raise typer.Exit(1)

    session_info = result["session"]
    session_id = session_info["session_id"]
    console.print(f"\n[green]Session started: {session_id}[/green]")
    console.print("\n[green]Session running in background.[/green]")
    console.print(f"Attach with: [bold]claude-board chat --attach {session_id}[/bold]")
    console.print(
        "Send prompt: [bold]claude-board chat"
        f" --send 'your prompt' --session {session_id}[/bold]"
    )
    console.print(f"Kill: [bold]claude-board chat --kill {session_id}[/bold]")


async def _start_chat_session(
    console: Console,
    *,
    mode: str,
    resume: str | None,
    name: str | None,
    project_path: str,
) -> None:
    claude_resume = _parse_resume_option(resume)
    if mode == "interactive":
        await run_local_interactive_session(
            console,
            resume=claude_resume,
            project_path=project_path,
        )
    else:
        await _run_headless_session(
            console,
            resume=claude_resume,
            name=name,
            project_path=project_path,
        )


@app.command(name="chat")
def chat_command(
    mode: str = typer.Option(
        "interactive",
        "--mode",
        "-m",
        help=(
            "Session mode: 'interactive' (with terminal UI)"
            " or 'headless' (background only)."
        ),
    ),
    resume: str | None = typer.Option(
        None,
        "--resume",
        "-r",
        help=(
            "Resume a session. Use without value to show"
            " picker, or specify session ID/name."
        ),
        is_flag=False,
        flag_value="__picker__",
    ),
    name: str | None = typer.Option(
        None,
        "--name",
        "-n",
        help="Name for the new session (for easier identification).",
    ),
    attach_session: str | None = typer.Option(
        None,
        "--attach",
        "-a",
        help="Attach to an existing claude-board session by ID or name.",
        is_flag=False,
        flag_value="__auto__",
    ),
    kill_session: str | None = typer.Option(
        None, "--kill", "-k", help="Kill a session by ID or name."
    ),
    send_prompt: str | None = typer.Option(
        None,
        "--send",
        "-s",
        help="Send a prompt to a session without attaching.",
    ),
    session_target: str | None = typer.Option(
        None,
        "--session",
        help="Target session for --send or --kill (ID or name).",
    ),
    project_path: str | None = typer.Option(
        None,
        "--project",
        "-p",
        help="Project directory (defaults to current directory).",
    ),
    *,
    list_sessions: Annotated[
        bool, typer.Option("--list", "-l", help="List all active sessions.")
    ] = False,
) -> None:
    if list_sessions:
        _list_chat_sessions(console)
        return

    if kill_session:
        asyncio.run(_kill_chat_session(console, kill_session))
        return

    if send_prompt:
        if not session_target:
            console.print(
                "[red]Error: --send requires --session to specify target session[/red]"
            )
            raise typer.Exit(1)
        asyncio.run(_send_to_session(console, session_target, send_prompt))
        return

    if attach_session:
        asyncio.run(_attach_to_session(console, attach_session))
        return

    asyncio.run(
        _start_chat_session(
            console,
            mode=mode,
            resume=resume,
            name=name,
            project_path=project_path or str(Path.cwd()),
        )
    )
