"""
Chat-related API routes.

This module handles all chat endpoints including:
- Creating, reading, updating, and deleting chats
- Streaming chat responses
- Stopping active streams
"""

import asyncio
import json
import traceback

from starlette.requests import Request
from starlette.responses import JSONResponse, StreamingResponse

from suzent.config import CONFIG
from suzent.database import get_database
from suzent.logger import get_logger
from suzent.streaming import stop_stream
from suzent.core.stream_registry import (
    get_background_queue,
    is_background_streaming,
    register_background_stream,
    unregister_background_stream,
)

logger = get_logger(__name__)
_chat_send_tasks: set[asyncio.Task[None]] = set()


def _display_file_metadata(files_list: list) -> list[dict]:
    """Return JSON-safe file metadata for immediate user-message display."""
    result: list[dict] = []
    for file in files_list or []:
        if isinstance(file, dict):
            result.append(file)
    return result


def _prewrite_user_display_message(
    chat_id: str,
    message: str,
    files_list: list,
) -> None:
    """Append the user's display row before stream_started can trigger reloads."""
    if not chat_id:
        return

    content = (message or "").strip()
    files = _display_file_metadata(files_list)
    if not content and not files:
        return

    entry: dict = {"role": "user", "content": content}
    if files:
        entry["files"] = files

    try:
        get_database().append_chat_message(chat_id, entry)
    except Exception as exc:
        logger.debug(f"Failed to prewrite user display message for {chat_id}: {exc}")


async def chat(request: Request) -> StreamingResponse:
    """
    Handle chat requests, stream agent responses, and manage the SSE stream.

    Accepts POST with JSON body or multipart form-data containing:
    - message: The user's message
    - reset: Optional boolean to reset agent memory
    - config: Optional agent configuration
    - chat_id: Optional chat identifier
    - stream: Optional boolean (default True, JSON only)
    - files: Optional image files (multipart only)
    """
    try:
        content_type = request.headers.get("content-type", "")

        if "multipart/form-data" in content_type:
            form = await request.form()
            message = form.get("message", "").strip()
            # reset = form.get("reset", "false").lower() == "true"
            config_str = form.get("config", "{}")
            chat_id = form.get("chat_id")
            stream = True  # Multipart always streams

            try:
                config = json.loads(config_str)
            except json.JSONDecodeError:
                config = {}

            resume_approvals_str = form.get("resume_approvals", "[]")
            try:
                resume_approvals = json.loads(resume_approvals_str)
            except json.JSONDecodeError:
                resume_approvals = []

            files_list = form.getlist("files")
            file_mentions_str = form.get("file_mentions", "[]")
            try:
                file_mentions = json.loads(file_mentions_str)
            except json.JSONDecodeError:
                file_mentions = []
            is_heartbeat = False
        else:
            data = await request.json()
            message = data.get("message", "").strip()
            # reset = data.get("reset", False)
            config = data.get("config", {})
            chat_id = data.get("chat_id")
            stream = data.get("stream", True)
            files_list = data.get("files", [])
            file_mentions = data.get("file_mentions", [])
            resume_approvals = data.get("resume_approvals", [])
            is_heartbeat = data.get("is_heartbeat", False)

        if not message and not files_list and not resume_approvals and not is_heartbeat:
            return StreamingResponse(
                iter(
                    ['data: {"type": "error", "data": "Empty message received."}\n\n']
                ),
                media_type="text/event-stream",
                status_code=400,
            )

        logger.info(
            f"Chat request received - chat_id: {chat_id}, "
            f"message_len: {len(message)}, files: {len(files_list)}"
        )

        from suzent.core.chat_processor import ChatProcessor
        from suzent.agent_manager import build_agent_config

        processor = ChatProcessor()
        config_override = build_agent_config(config, require_social_tool=False)

        # When the frontend initiates a heartbeat SSE stream, claim the pending slot and
        # update heartbeat_last_run_at so the backend deferred fallback doesn't double-run.
        if is_heartbeat and chat_id:
            try:
                from suzent.core.heartbeat import get_active_heartbeat
                from suzent.database import ChatModel
                from datetime import datetime, timezone
                from sqlmodel import Session
                from sqlalchemy.orm.attributes import flag_modified

                runner = get_active_heartbeat()
                if runner:
                    runner._pending_heartbeats.pop(chat_id, None)

                    # Apply heartbeat tool-approval policy from global config.
                    from suzent.routes.heartbeat_routes import _load_heartbeat_config

                    allowed = _load_heartbeat_config().get("allowed_tools") or []
                    if allowed:
                        hb_policy = {t: "always_allow" for t in allowed}
                        existing = config_override.get("tool_approval_policy") or {}
                        config_override["tool_approval_policy"] = {
                            **existing,
                            **hb_policy,
                        }
                    config_override["permission_mode"] = "auto"
                    config_override["interaction_profile"] = "headless"

                    # Update heartbeat_last_run_at so the deferred fallback doesn't double-run.
                    db = get_database()
                    with Session(db.engine) as _s:
                        _chat = _s.get(ChatModel, chat_id)
                        if _chat and _chat.config:
                            _chat.config["heartbeat_last_run_at"] = datetime.now(
                                timezone.utc
                            ).isoformat()
                            _chat.config.pop("heartbeat_last_result", None)
                            flag_modified(_chat, "config")
                            _s.commit()

                # Build prompt from file — frontend no longer needs to know the template.
                from suzent.prompts import (
                    HEARTBEAT_BASE_INSTRUCTIONS,
                    HEARTBEAT_PROMPT_TEMPLATE,
                )

                hb_path = get_database().get_project_dir(chat_id) / "heartbeat.md"
                custom = (
                    hb_path.read_text(encoding="utf-8").strip()
                    if hb_path.exists()
                    else ""
                )
                combined = (
                    (HEARTBEAT_BASE_INSTRUCTIONS + "\n\n" + custom)
                    if custom
                    else HEARTBEAT_BASE_INSTRUCTIONS
                )
                message = HEARTBEAT_PROMPT_TEMPLATE.format(instructions=combined)
            except Exception as _hb_err:
                logger.warning(f"Heartbeat pre-processing failed: {_hb_err}")

        generator = processor.process_turn(
            chat_id=chat_id,
            user_id=CONFIG.user_id,
            message_content=message,
            files=files_list,
            file_mentions=file_mentions,
            config_override=config_override,
            resume_approvals=resume_approvals,
            is_heartbeat=is_heartbeat,
        )

        if stream:
            return StreamingResponse(
                generator,
                media_type="text/event-stream",
                headers={
                    "Content-Type": "text/event-stream",
                    "Cache-Control": "no-cache",
                    "Connection": "keep-alive",
                    "X-Accel-Buffering": "no",
                },
            )

        # Non-streaming: consume generator and return JSON
        full_response = ""
        async for chunk in generator:
            try:
                if chunk.startswith("data: "):
                    json_str = chunk[6:].strip()
                    if json_str == "[DONE]":
                        continue
                    event_data = json.loads(json_str)

                    msg_type = event_data.get("type")
                    if msg_type == "TEXT_MESSAGE_CONTENT":
                        full_response += event_data.get("delta", "")
                    elif msg_type == "RUN_ERROR":
                        error_msg = event_data.get("message", "Unknown error")
                        return JSONResponse({"error": error_msg}, status_code=500)
            except Exception:
                pass

        return JSONResponse({"response": full_response})

    except Exception as e:
        logger.error(f"Error handling chat request: {e}")
        return JSONResponse(status_code=500, content={"error": str(e)})


async def chat_send(request: Request) -> JSONResponse:
    """
    POST /chat/send — Start a user chat turn as a background stream.

    Returns 202 immediately. The response streams through /chat/live
    and is observable via /events/stream, enabling the frontend to
    watch other chats while this one processes.

    Also accepts ``resume_approvals`` to resume a paused tool-approval stream
    without a direct SSE connection (reconnectable after page refresh).
    """
    try:
        data = await request.json()
    except Exception:
        return JSONResponse({"error": "Invalid JSON"}, status_code=400)

    chat_id = data.get("chat_id")
    message = data.get("message", "").strip()
    config = data.get("config", {})
    files_list = data.get("files", [])
    file_mentions = data.get("file_mentions", [])
    resume_approvals = data.get("resume_approvals", [])

    if not chat_id:
        return JSONResponse({"error": "chat_id is required"}, status_code=400)
    if not message and not files_list and not resume_approvals:
        return JSONResponse({"error": "Empty message"}, status_code=400)
    from suzent.core.chat_processor import ChatProcessor
    from suzent.agent_manager import build_agent_config

    processor = ChatProcessor()
    config_override = build_agent_config(config, require_social_tool=False)
    if is_background_streaming(chat_id):
        return JSONResponse({"error": "Chat is already streaming"}, status_code=409)

    if not resume_approvals and not message.strip().startswith("/"):
        _prewrite_user_display_message(chat_id, message, files_list)

    stream_queue = register_background_stream(chat_id)

    async def _run() -> None:
        try:
            generator = processor.process_turn(
                chat_id=chat_id,
                user_id=CONFIG.user_id,
                message_content=message,
                files=files_list,
                file_mentions=file_mentions,
                config_override=config_override,
                resume_approvals=resume_approvals or [],
            )
            async for chunk in generator:
                await stream_queue.put(chunk)
        except Exception as exc:
            logger.error(f"[chat_send] Background turn error for {chat_id}: {exc}")
            error_payload = (
                f'data: {{"type":"RUN_ERROR","message":{json.dumps(str(exc))}}}\n\n'
            )
            try:
                await stream_queue.put(error_payload)
            except Exception:
                pass
        finally:
            await stream_queue.put(None)

    task = asyncio.create_task(_run())
    _chat_send_tasks.add(task)
    task.add_done_callback(_chat_send_tasks.discard)
    return JSONResponse({"chat_id": chat_id}, status_code=202)


async def steer_chat_send(request: Request) -> JSONResponse:
    """
    POST /chat/steer-send — Interrupt the current agent run and redirect via background queue.

    Returns 202 immediately. The steer response streams through /chat/live so the
    frontend can reconnect after a page refresh, matching the behaviour of /chat/send.
    """
    try:
        data = await request.json()
    except Exception:
        return JSONResponse({"error": "Invalid JSON"}, status_code=400)

    chat_id = data.get("chat_id")
    message = data.get("message", "").strip()
    config = data.get("config", {})

    if not chat_id:
        return JSONResponse({"error": "chat_id is required"}, status_code=400)
    if not message:
        return JSONResponse({"error": "message is required"}, status_code=400)

    from suzent.core.chat_processor import ChatProcessor
    from suzent.agent_manager import build_agent_config

    processor = ChatProcessor()
    config_override = build_agent_config(config, require_social_tool=False)
    stop_stream(chat_id, reason="Steered by user")
    _prewrite_user_display_message(chat_id, message, [])

    stream_queue = register_background_stream(chat_id)

    async def _run() -> None:
        try:
            generator = processor.process_steer(
                chat_id=chat_id,
                user_id=CONFIG.user_id,
                steer_message=message,
                config_override=config_override,
            )
            async for chunk in generator:
                await stream_queue.put(chunk)
        except Exception as exc:
            logger.error(f"[steer_send] Background steer error for {chat_id}: {exc}")
            error_payload = (
                f'data: {{"type":"RUN_ERROR","message":{json.dumps(str(exc))}}}\n\n'
            )
            try:
                await stream_queue.put(error_payload)
            except Exception:
                pass
        finally:
            await stream_queue.put(None)

    task = asyncio.create_task(_run())
    _chat_send_tasks.add(task)
    task.add_done_callback(_chat_send_tasks.discard)
    return JSONResponse({"chat_id": chat_id}, status_code=202)


async def retry_chat(request: Request) -> StreamingResponse:
    """
    Restore the last retry checkpoint for a chat and re-run the original user message.

    POST /chat/retry  body: {"chat_id": "..."}
    Returns text/event-stream SSE (same format as /chat).
    """
    try:
        data = await request.json()
    except json.JSONDecodeError:
        return StreamingResponse(
            iter(['data: {"type": "error", "data": "Invalid JSON."}\n\n']),
            media_type="text/event-stream",
            status_code=400,
        )

    chat_id = data.get("chat_id")
    if not chat_id:
        return StreamingResponse(
            iter(['data: {"type": "error", "data": "chat_id is required"}\n\n']),
            media_type="text/event-stream",
            status_code=400,
        )

    from suzent.core.retry import apply_retry_checkpoint
    from suzent.core.chat_processor import ChatProcessor
    from suzent.agent_manager import build_agent_config

    checkpoint_data = apply_retry_checkpoint(chat_id)
    if checkpoint_data is None:
        return StreamingResponse(
            iter(['data: {"type": "error", "data": "No retry checkpoint found."}\n\n']),
            media_type="text/event-stream",
            status_code=404,
        )

    user_message = checkpoint_data["user_message"]
    user_files = checkpoint_data["user_files"] or []
    config_snapshot = checkpoint_data.get("config_snapshot") or {}

    processor = ChatProcessor()
    config_override = build_agent_config(config_snapshot, require_social_tool=False)

    generator = processor.process_turn(
        chat_id=chat_id,
        user_id=CONFIG.user_id,
        message_content=user_message,
        files=user_files if user_files else None,
        config_override=config_override,
    )

    return StreamingResponse(
        generator,
        media_type="text/event-stream",
        headers={
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        },
    )


async def stop_chat(request: Request) -> JSONResponse:
    """Stop an active streaming session for the given chat."""
    try:
        data = await request.json()
    except json.JSONDecodeError:
        return JSONResponse({"error": "Invalid JSON."}, status_code=400)

    chat_id = data.get("chat_id")
    if not chat_id:
        return JSONResponse({"error": "chat_id is required"}, status_code=400)

    reason = data.get("reason") or "Stream stopped by user"
    success = stop_stream(chat_id, reason)

    if not success:
        return JSONResponse({"status": "no_active_stream"}, status_code=404)

    return JSONResponse({"status": "stopping", "reason": reason})


async def deactivate_tool(request: Request) -> JSONResponse:
    """Remove a specific agent-activated tool from the deferred toolset for a chat."""
    try:
        data = await request.json()
    except json.JSONDecodeError:
        return JSONResponse({"error": "Invalid JSON."}, status_code=400)

    chat_id = data.get("chat_id")
    tool_name = data.get("tool_name")
    if not chat_id or not tool_name:
        return JSONResponse(
            {"error": "chat_id and tool_name are required"}, status_code=400
        )

    from suzent.agent_manager import remove_unlocked_tool

    remove_unlocked_tool(chat_id, tool_name)
    return JSONResponse({"status": "ok", "tool_name": tool_name})


async def live_stream(request: Request) -> StreamingResponse:
    """Subscribe to a live background stream for a chat (cron, heartbeat, social).

    Accepts POST with JSON body: {"chat_id": "...", "wait_ms": 25000}
    Returns text/event-stream SSE (same format as /chat) if active, 204 if not.
    If `wait_ms` is provided (>0), waits up to that long for a stream to appear.
    """
    from starlette.responses import Response

    try:
        data = await request.json()
    except Exception:
        return Response(status_code=400)

    chat_id = data.get("chat_id", "")
    wait_ms_raw = data.get("wait_ms", 0)
    try:
        wait_ms = max(0, int(wait_ms_raw or 0))
    except Exception:
        wait_ms = 0

    if not chat_id:
        return Response(status_code=400)

    q = get_background_queue(chat_id)
    if q is None and wait_ms > 0:
        loop = asyncio.get_running_loop()
        deadline = loop.time() + (wait_ms / 1000.0)
        while loop.time() < deadline:
            await asyncio.sleep(0.15)
            q = get_background_queue(chat_id)
            if q is not None:
                break

    if q is None:
        return Response(status_code=204)

    async def generate():
        while True:
            try:
                chunk = await asyncio.wait_for(q.get(), timeout=60.0)
            except asyncio.TimeoutError:
                # Heartbeat to keep connection alive
                yield ": keep-alive\n\n"
                continue
            if chunk is None:
                unregister_background_stream(chat_id, q)
                return
            yield chunk

    return StreamingResponse(
        generate(),
        media_type="text/event-stream",
        headers={
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        },
    )


async def get_chats(request: Request) -> JSONResponse:
    """Return list of chat summaries with pagination and optional search.

    Query params:
        limit, offset: pagination
        search: substring match on title or message body
        project_id: filter to chats in this project
        platform: legacy filter; prefer ``project_id`` going forward
    """
    try:
        db = get_database()

        limit = int(request.query_params.get("limit", 50))
        offset = int(request.query_params.get("offset", 0))
        search = request.query_params.get("search", "").strip() or None
        platform = request.query_params.get("platform", "").strip() or None
        project_id = request.query_params.get("project_id", "").strip() or None

        chats = db.list_chats(
            limit=limit,
            offset=offset,
            search=search,
            platform=platform,
            project_id=project_id,
        )
        total = db.get_chat_count(
            search=search, platform=platform, project_id=project_id
        )
        kind_counts = db.get_chat_kind_counts(
            search=search, platform=platform, project_id=project_id
        )

        # Convert Pydantic models to dicts, annotating live background streams
        chats_data = [
            {
                **c.model_dump(mode="json", by_alias=True),
                "isRunning": is_background_streaming(c.id),
            }
            for c in chats
        ]

        return JSONResponse(
            {
                "chats": chats_data,
                "total": total,
                "kindCounts": kind_counts,
                "limit": limit,
                "offset": offset,
                "search": search,
                "projectId": project_id,
            }
        )
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


async def get_chat(request: Request) -> JSONResponse:
    """Return a specific chat by ID (excluding binary agent_state)."""
    try:
        chat_id = request.path_params["chat_id"]
        db = get_database()

        chat = db.get_chat(chat_id)
        if not chat:
            return JSONResponse({"error": "Chat not found"}, status_code=404)

        response_chat = chat.model_dump(
            mode="json", by_alias=True, exclude={"agent_state"}
        )
        context_usage = dict(chat.context_usage or {})

        if chat.agent_state and context_usage.get("context_tokens") is None:
            try:
                from suzent.core.agent_serializer import deserialize_state
                from suzent.core.context_compressor import estimate_tokens
                from suzent.config import CONFIG as _CFG

                state = deserialize_state(chat.agent_state)
                if state and state.get("message_history"):
                    budget = estimate_tokens(
                        state["message_history"], _CFG.max_context_tokens
                    )
                    response_chat["contextTokens"] = budget.estimated_tokens
                    context_usage["context_tokens"] = budget.estimated_tokens
            except Exception:
                pass

        response_chat.pop("context_usage", None)
        if context_usage:
            response_chat["contextUsage"] = context_usage

        hb_path = get_database().get_project_dir(chat_id) / "heartbeat.md"
        if hb_path.exists():
            try:
                if "config" not in response_chat:
                    response_chat["config"] = {}
                response_chat["config"]["heartbeat_instructions"] = hb_path.read_text(
                    encoding="utf-8"
                )
            except Exception:
                pass

        return JSONResponse(response_chat)
    except Exception as e:
        logger.error(f"Error in get_chat: {e}")
        traceback.print_exc()
        return JSONResponse({"error": str(e)}, status_code=500)


async def create_chat(request: Request) -> JSONResponse:
    """Create a new chat. Optional ``project_id`` body field assigns to a project."""
    try:
        data = await request.json()
        title = data.get("title", "New Chat")
        config = data.get("config", {})
        instructions = (
            config.pop("heartbeat_instructions", None)
            if isinstance(config, dict)
            else None
        )
        messages = data.get("messages", [])
        project_id = data.get("project_id") or data.get("projectId")

        db = get_database()
        chat_id = db.create_chat(title, config, messages, project_id=project_id)

        if instructions is not None:
            hb_path = get_database().get_project_dir(chat_id) / "heartbeat.md"
            try:
                hb_path.parent.mkdir(parents=True, exist_ok=True)
                hb_path.write_text(instructions, encoding="utf-8")
            except Exception as e:
                logger.error(f"Failed to write initial heartbeat.md: {e}")

        chat = db.get_chat(chat_id)
        if not chat:
            return JSONResponse({"error": "Failed to create chat"}, status_code=500)

        response_chat = chat.model_dump(
            mode="json", by_alias=True, exclude={"agent_state"}
        )
        if instructions is not None:
            if "config" not in response_chat:
                response_chat["config"] = {}
            response_chat["config"]["heartbeat_instructions"] = instructions

        return JSONResponse(response_chat, status_code=201)
    except Exception as e:
        logger.error(f"Error in create_chat: {e}")
        traceback.print_exc()
        return JSONResponse({"error": str(e)}, status_code=500)


async def update_chat(request: Request) -> JSONResponse:
    """Update an existing chat."""
    try:
        chat_id = request.path_params["chat_id"]
        data = await request.json()

        db = get_database()

        title = data.get("title")
        config = data.get("config")
        messages = data.get("messages")

        instructions = None
        if isinstance(config, dict) and "heartbeat_instructions" in config:
            instructions = config.pop("heartbeat_instructions")

        success = db.update_chat(chat_id, title=title, config=config, messages=messages)
        if not success:
            return JSONResponse({"error": "Chat not found"}, status_code=404)

        if instructions is not None:
            hb_path = get_database().get_project_dir(chat_id) / "heartbeat.md"
            try:
                hb_path.parent.mkdir(parents=True, exist_ok=True)
                hb_path.write_text(instructions, encoding="utf-8")
            except Exception as e:
                logger.error(f"Failed to write heartbeat.md during update: {e}")

        chat = db.get_chat(chat_id)
        if not chat:
            return JSONResponse(
                {"error": "Failed to retrieve updated chat"}, status_code=500
            )

        response_chat = chat.model_dump(
            mode="json", by_alias=True, exclude={"agent_state"}
        )

        hb_path = get_database().get_project_dir(chat_id) / "heartbeat.md"
        if hb_path.exists():
            try:
                if "config" not in response_chat:
                    response_chat["config"] = {}
                response_chat["config"]["heartbeat_instructions"] = hb_path.read_text(
                    encoding="utf-8"
                )
            except Exception:
                pass

        return JSONResponse(response_chat)
    except Exception as e:
        logger.error(f"Error in update_chat: {e}")
        traceback.print_exc()
        return JSONResponse({"error": str(e)}, status_code=500)


async def delete_chat(request: Request) -> JSONResponse:
    """Delete a chat. Pass ?cascade=true to also delete subagent children."""
    try:
        chat_id = request.path_params["chat_id"]
        cascade = request.query_params.get("cascade", "false").lower() == "true"
        db = get_database()

        # Collect subagent child IDs before deletion (for tool cleanup)
        child_ids = db.get_subagent_chat_ids_for_parent_chat(chat_id) if cascade else []

        success = db.delete_chat(chat_id, cascade_subagents=cascade)
        if not success:
            return JSONResponse({"error": "Chat not found"}, status_code=404)

        from suzent.agent_manager import clear_unlocked_tools

        clear_unlocked_tools(chat_id)
        for child_id in child_ids:
            clear_unlocked_tools(child_id)
        return JSONResponse(
            {
                "message": "Chat deleted successfully",
                "deleted_subagents": len(child_ids),
            }
        )
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


async def mark_chat_read(request: Request) -> JSONResponse:
    """POST /chats/{chat_id}/mark-read — reset unread counter for a chat."""
    try:
        chat_id = request.path_params["chat_id"]
        db = get_database()
        db.mark_chat_read(chat_id)
        return JSONResponse({"ok": True})
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


_PERMISSION_MODES = {
    "default",
    "accept_edits",
    "plan",
    "auto",
    "strict_readonly",
}


async def get_permission_mode(request: Request) -> JSONResponse:
    chat_id = request.path_params["chat_id"]
    chat = get_database().get_chat(chat_id)
    if chat is None:
        return JSONResponse({"error": "Chat not found"}, status_code=404)
    mode = str((chat.config or {}).get("permission_mode") or "default")
    pre_plan_mode = str((chat.config or {}).get("pre_plan_permission_mode") or "")
    if mode not in _PERMISSION_MODES:
        mode = "default"
    if pre_plan_mode not in _PERMISSION_MODES or pre_plan_mode == "plan":
        pre_plan_mode = ""
    return JSONResponse(
        {
            "mode": mode,
            "prePlanMode": pre_plan_mode or None,
            "availableModes": [
                "default",
                "accept_edits",
                "plan",
                "auto",
                "strict_readonly",
            ],
            "autoModeAvailable": True,
            "unavailableReasons": {},
        }
    )


async def set_permission_mode(request: Request) -> JSONResponse:
    chat_id = request.path_params["chat_id"]
    data = await request.json()
    mode = str(data.get("mode") or "").strip().lower()
    restore_previous = bool(data.get("restorePrevious"))
    db = get_database()
    chat = db.get_chat(chat_id)
    if chat is None:
        return JSONResponse({"error": "Chat not found"}, status_code=404)

    current_mode = str((chat.config or {}).get("permission_mode") or "default")
    pre_plan_mode = str((chat.config or {}).get("pre_plan_permission_mode") or "")
    if restore_previous:
        if current_mode != "plan":
            return JSONResponse(
                {"error": "Previous mode can only be restored from Plan mode"},
                status_code=409,
            )
        mode = (
            pre_plan_mode
            if pre_plan_mode in _PERMISSION_MODES and pre_plan_mode != "plan"
            else "default"
        )

    if mode not in _PERMISSION_MODES:
        return JSONResponse(
            {
                "error": "Invalid permission mode",
                "validModes": sorted(_PERMISSION_MODES),
            },
            status_code=400,
        )
    updates: dict[str, str | None] = {"permission_mode": mode}
    if mode == "plan" and current_mode != "plan":
        updates["pre_plan_permission_mode"] = (
            current_mode if current_mode in _PERMISSION_MODES else "default"
        )
    elif current_mode == "plan" and mode != "plan":
        updates["pre_plan_permission_mode"] = None
    db.merge_chat_config(chat_id, updates)
    return await get_permission_mode(request)


async def steer_chat(request: Request) -> StreamingResponse:
    """
    Interrupt the current agent run and redirect with a new message.

    POST /chat/steer
    {
      "chat_id": "...",
      "message": "Use Python instead",
      "config": {}           // optional
    }
    Returns SSE stream (same format as /chat).
    """
    try:
        data = await request.json()
        chat_id = data.get("chat_id")
        message = data.get("message", "").strip()
        config = data.get("config", {})

        if not chat_id:
            return JSONResponse({"error": "chat_id is required"}, status_code=400)
        if not message:
            return JSONResponse({"error": "message is required"}, status_code=400)

        from suzent.core.chat_processor import ChatProcessor
        from suzent.agent_manager import build_agent_config

        processor = ChatProcessor()
        config_override = build_agent_config(config, require_social_tool=False)

        generator = processor.process_steer(
            chat_id=chat_id,
            user_id=CONFIG.user_id,
            steer_message=message,
            config_override=config_override,
        )

        return StreamingResponse(
            generator,
            media_type="text/event-stream",
            headers={
                "Content-Type": "text/event-stream",
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
                "X-Accel-Buffering": "no",
            },
        )

    except Exception as e:
        logger.error(f"Error handling steer request: {e}")
        return JSONResponse(status_code=500, content={"error": str(e)})


async def approve_tool(request: Request) -> JSONResponse:
    """Legacy endpoint. Approvals are now passed via /chat with resume_approvals."""
    return JSONResponse({"status": "resolved", "approved": True})
