from __future__ import annotations

import asyncio
import json
from datetime import datetime, timezone

from fastapi import APIRouter, HTTPException, Request, WebSocket, WebSocketDisconnect

from ..chat.session import ChatSessionManager
from ..config import AppConfig
from ..models import DecisionStatus, PermissionRequest
from ..state import INTERACTIVE_TOOLS, StateManager
from ..types import HookInput, TodoInput
from .constants import logger

router = APIRouter()


def _interactive_pretool_response(tool_name: str) -> dict[str, object]:
    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "ask",
            "permissionDecisionReason": (
                "Interactive tool requires TUI" f" input: {tool_name}"
            ),
        }
    }


async def _handle_permission_event(
    state_manager: StateManager, config: AppConfig, hook_input: HookInput
) -> dict[str, object]:
    event = hook_input.hook_event_name
    project_path = hook_input.project_path or hook_input.cwd or ""

    if hook_input.tool_name in INTERACTIVE_TOOLS:
        if event == "PreToolUse":
            return _interactive_pretool_response(hook_input.tool_name)
        raise HTTPException(
            status_code=422,
            detail=(
                f"Interactive tool {hook_input.tool_name}"
                " should be handled by Claude Code TUI"
            ),
        )

    if (
        hook_input.session_id
        and project_path
        and hook_input.session_id not in state_manager.state.claude_sessions
    ):
        await state_manager.register_claude_session(
            session_id=hook_input.session_id,
            project_path=project_path,
            is_external=True,
        )

    perm_request = PermissionRequest(
        tool_name=hook_input.tool_name,
        tool_input=dict(hook_input.tool_input),
        tool_use_id=hook_input.tool_use_id,
        session_id=hook_input.session_id,
        project_path=project_path,
    )

    decision_future = await state_manager.add_permission_request(perm_request)
    try:
        decision = await asyncio.wait_for(
            decision_future, timeout=config.server.hook_timeout
        )
    except asyncio.TimeoutError:
        decision = DecisionStatus.TIMEOUT
        await state_manager.make_decision(perm_request.id, DecisionStatus.TIMEOUT)

    if event == "PreToolUse":
        return _pretool_decision_response(decision)
    return _permission_request_decision_response(decision)


def _pretool_decision_response(decision: DecisionStatus) -> dict[str, object]:
    if decision == DecisionStatus.APPROVED:
        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "allow",
                "permissionDecisionReason": "Approved by Claude Board",
            }
        }
    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": f"Denied by Claude Board ({decision.value})",
        }
    }


def _permission_request_decision_response(decision: DecisionStatus) -> dict[str, object]:
    if decision == DecisionStatus.APPROVED:
        return {
            "hookSpecificOutput": {
                "hookEventName": "PermissionRequest",
                "decision": {"behavior": "allow"},
            }
        }
    return {
        "hookSpecificOutput": {
            "hookEventName": "PermissionRequest",
            "decision": {
                "behavior": "deny",
                "message": f"Denied by Claude Board ({decision.value})",
            },
        }
    }


async def _broadcast_stop_event(
    connected_clients: list[WebSocket], hook_input: HookInput, project_path: str
) -> None:
    stop_message = json.dumps(
        {
            "type": "stop_notification",
            "session_id": hook_input.session_id,
            "project_path": project_path,
            "timestamp": datetime.now(tz=timezone.utc).isoformat(),
        }
    )
    disconnected: list[WebSocket] = []
    for client in connected_clients:
        try:
            await client.send_text(stop_message)
        except (OSError, WebSocketDisconnect):
            disconnected.append(client)
    for client in disconnected:
        if client in connected_clients:
            connected_clients.remove(client)


@router.post("/api/hook")
async def handle_hook(request: Request, hook_input: HookInput) -> dict[str, object]:
    state_manager: StateManager = request.app.state.state_manager
    connected_clients: list[WebSocket] = request.app.state.connected_clients
    config: AppConfig = request.app.state.config
    event = hook_input.hook_event_name
    project_path = hook_input.project_path or hook_input.cwd or ""

    if event in ["PreToolUse", "PermissionRequest"]:
        return await _handle_permission_event(state_manager, config, hook_input)

    if event == "Notification":
        return {"status": "ok"}

    if event == "Stop":
        logger.info(
            "Stop event received for session %s "
            "(Claude finished responding, session still active)",
            hook_input.session_id,
        )
        await _broadcast_stop_event(connected_clients, hook_input, project_path)
        return {"status": "ok"}

    if event == "SessionEnd":
        await state_manager.handle_session_stop(
            session_id=hook_input.session_id,
            project_path=project_path,
        )
        return {"status": "ok"}

    return {"status": "ok"}


@router.post("/api/todos")
async def update_todos(request: Request, todo_input: TodoInput) -> dict[str, object]:
    state_manager: StateManager = request.app.state.state_manager
    if (
        todo_input.session_id
        and todo_input.project_path
        and todo_input.session_id not in state_manager.state.claude_sessions
    ):
        await state_manager.register_claude_session(
            session_id=todo_input.session_id,
            project_path=todo_input.project_path,
            is_external=True,
        )

    todos_list: list[dict[str, object]] = [
        {"content": t.content, "status": t.status, "activeForm": t.activeForm}
        for t in todo_input.todos
    ]
    await state_manager.update_todos(
        todos_list,
        project_path=todo_input.project_path,
        session_id=todo_input.session_id,
    )
    return {"status": "ok", "count": len(todo_input.todos)}


@router.post("/api/todos/task")
async def handle_task_operation(
    request: Request, data: dict[str, object]
) -> dict[str, str]:
    state_manager: StateManager = request.app.state.state_manager
    action = str(data.get("action", ""))
    session_id = str(data.get("session_id", ""))
    project_path = str(data.get("project_path", ""))

    if (
        session_id
        and project_path
        and session_id not in state_manager.state.claude_sessions
    ):
        await state_manager.register_claude_session(
            session_id=session_id,
            project_path=project_path,
            is_external=True,
        )

    if action == "create":
        await state_manager.add_todo(
            subject=str(data.get("subject", "")),
            description=str(data.get("description", "")),
            active_form=str(data.get("activeForm", "")),
            project_path=project_path,
            session_id=session_id,
        )
    elif action == "update":
        await state_manager.update_todo(
            task_id=str(data.get("taskId", "")),
            status=str(data.get("status", "")) or None,
            subject=str(data.get("subject", "")) or None,
            active_form=str(data.get("activeForm", "")) or None,
            project_path=project_path,
            session_id=session_id,
        )

    return {"status": "ok"}


@router.post("/api/session/notify")
async def session_notify(
    request: Request, data: dict[str, str | None]
) -> dict[str, str | None]:
    state_manager: StateManager = request.app.state.state_manager
    session_manager: ChatSessionManager = request.app.state.session_manager

    event = data.get("event", "unknown")
    session_id = data.get("session_id", "unknown")
    project_path = data.get("project_path", "")
    chat_session_id = data.get("chat_session_id")

    if event == "session_start":
        is_external = True
        chat_session = None

        if chat_session_id:
            for attempt in range(3):
                chat_session = session_manager.get_session(chat_session_id)
                if chat_session:
                    break
                await asyncio.sleep(0.1 * (2**attempt))

            if chat_session:
                chat_session.claude_session_id = session_id
                is_external = False
            else:
                stub = session_manager.get_stub_session(chat_session_id)
                if stub:
                    is_external = False

                if is_external and project_path:
                    fallback = session_manager.find_recent_session_by_project(
                        project_path, within_seconds=30
                    )
                    if fallback:
                        fallback.claude_session_id = session_id
                        chat_session_id = fallback.session_id
                        is_external = False

        if session_id and project_path:
            await state_manager.register_claude_session(
                session_id=session_id,
                project_path=project_path,
                chat_session_id=chat_session_id,
                is_external=is_external,
            )
        elif project_path:
            await state_manager.set_active_project(project_path)

    return {"status": "ok", "event": event, "session_id": session_id}


@router.post("/api/session/active")
async def set_active_session(
    request: Request, data: dict[str, str]
) -> dict[str, str | None]:
    state_manager: StateManager = request.app.state.state_manager
    session_id = data.get("session_id")
    await state_manager.set_active_session(session_id)
    return {"status": "ok", "active_session_id": session_id}


@router.delete("/api/session/{session_id}")
async def remove_session(request: Request, session_id: str) -> dict[str, str]:
    state_manager: StateManager = request.app.state.state_manager
    success = await state_manager.remove_claude_session(session_id)
    if not success:
        raise HTTPException(status_code=400, detail="Session not found or not ended yet")
    return {"status": "ok"}


@router.post("/api/session/{session_id}/yolo")
async def toggle_session_yolo(
    request: Request, session_id: str, data: dict[str, bool]
) -> dict[str, object]:
    state_manager: StateManager = request.app.state.state_manager
    enabled = data.get("enabled", False)
    success = await state_manager.set_session_yolo_mode(session_id, enabled=enabled)
    if not success:
        raise HTTPException(status_code=404, detail="Session not found")
    return {"status": "ok", "session_id": session_id, "yolo_mode": enabled}

