from __future__ import annotations

import re
from datetime import UTC, datetime
from typing import TYPE_CHECKING

import httpx
import structlog

from adapter.matrix.store import (
    get_room_meta,
    next_platform_chat_id,
    set_load_pending,
    set_platform_chat_id,
)
from core.protocol import IncomingCommand, OutgoingEvent, OutgoingMessage

if TYPE_CHECKING:
    from core.store import StateStore
    from sdk.prototype_state import PrototypeStateStore

logger = structlog.get_logger(__name__)

SAVE_PROMPT = (
    "Summarize our conversation and save to /workspace/contexts/{name}.md. "
    "Reply only with: Saved: {name}"
)
LOAD_PROMPT = (
    "Load context from /workspace/contexts/{name}.md and use it as background "
    "for our conversation. Reply: Loaded: {name}"
)
_VALID_NAME = re.compile(r"^[A-Za-z0-9_-]+$")


def _sanitize_session_name(raw_name: str) -> str | None:
    name = raw_name.strip()
    if not name or not _VALID_NAME.fullmatch(name):
        return None
    return name


async def _resolve_room_id(event: IncomingCommand, chat_mgr) -> str:
    if chat_mgr is None:
        return event.chat_id
    ctx = await chat_mgr.get(event.chat_id, user_id=event.user_id)
    if ctx is not None and ctx.surface_ref:
        return ctx.surface_ref
    return event.chat_id


async def _resolve_context_scope(
    event: IncomingCommand,
    store: StateStore,
    chat_mgr,
) -> tuple[str, str | None]:
    room_id = await _resolve_room_id(event, chat_mgr)
    room_meta = await get_room_meta(store, room_id)
    platform_chat_id = room_meta.get("platform_chat_id") if room_meta else None
    return room_id, platform_chat_id


async def _require_platform_context(
    event: IncomingCommand,
    store: StateStore,
    chat_mgr,
) -> tuple[str, str]:
    room_id, platform_chat_id = await _resolve_context_scope(event, store, chat_mgr)
    if not platform_chat_id:
        raise RuntimeError(f"matrix room context is incomplete: {room_id}")
    return room_id, platform_chat_id


def make_handle_save(agent_api, store: StateStore, prototype_state: PrototypeStateStore):
    async def handle_save(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list[OutgoingEvent]:
        if event.args:
            name = _sanitize_session_name(event.args[0])
            if name is None:
                return [
                    OutgoingMessage(
                        chat_id=event.chat_id,
                        text="Имя сохранения может содержать только буквы, цифры, _ и -.",
                    )
                ]
        else:
            name = f"context-{datetime.now(UTC).strftime('%Y%m%d-%H%M%S')}"

        try:
            await platform.send_message(
                event.user_id,
                event.chat_id,
                SAVE_PROMPT.format(name=name),
            )
        except Exception as exc:
            logger.warning("save_agent_call_failed", error=str(exc))
            return [OutgoingMessage(chat_id=event.chat_id, text=f"Ошибка при сохранении: {exc}")]

        try:
            _, platform_chat_id = await _require_platform_context(event, store, chat_mgr)
        except RuntimeError as exc:
            logger.warning("save_context_incomplete", error=str(exc))
            return [OutgoingMessage(chat_id=event.chat_id, text="Контекст комнаты не готов. Попробуй позже.")]

        await prototype_state.add_saved_session(
            event.user_id,
            name,
            source_context_id=platform_chat_id,
        )
        return [
            OutgoingMessage(
                chat_id=event.chat_id,
                text=f"Запрос на сохранение отправлен агенту: {name}",
            )
        ]

    return handle_save


def make_handle_load(store: StateStore, prototype_state: PrototypeStateStore):
    async def handle_load(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list[OutgoingEvent]:
        sessions = await prototype_state.list_saved_sessions(event.user_id)
        if not sessions:
            return [
                OutgoingMessage(
                    chat_id=event.chat_id,
                    text="Нет сохранённых сессий. Используй !save [имя].",
                )
            ]

        room_id, _ = await _resolve_context_scope(event, store, chat_mgr)
        lines = ["Сохранённые сессии:"]
        for index, session in enumerate(sessions, start=1):
            created = session.get("created_at", "")[:10]
            lines.append(f"  {index}. {session['name']} ({created})")
        lines.append("")
        lines.append("Введи номер или 0 / !cancel для отмены.")

        await set_load_pending(store, event.user_id, room_id, {"saves": sessions})
        return [OutgoingMessage(chat_id=event.chat_id, text="\n".join(lines))]

    return handle_load


def make_handle_reset(store: StateStore, prototype_state: PrototypeStateStore):
    async def handle_reset(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list[OutgoingEvent]:
        try:
            room_id, old_chat_id = await _require_platform_context(event, store, chat_mgr)
        except RuntimeError as exc:
            logger.warning("clear_context_incomplete", error=str(exc))
            return [OutgoingMessage(chat_id=event.chat_id, text="Контекст комнаты не готов. Попробуй позже.")]

        new_chat_id = await next_platform_chat_id(store)
        await set_platform_chat_id(store, room_id, new_chat_id)

        disconnect = getattr(platform, "disconnect_chat", None)
        if callable(disconnect):
            await disconnect(old_chat_id)

        await prototype_state.clear_current_session(old_chat_id)
        await prototype_state.clear_current_session(new_chat_id)

        return [
            OutgoingMessage(
                chat_id=event.chat_id,
                text="Контекст сброшен. Агент не помнит предыдущий разговор.",
            )
        ]

    return handle_reset


async def _call_reset_endpoint(agent_base_url: str, chat_id: str) -> list[OutgoingEvent]:
    try:
        async with httpx.AsyncClient() as client:
            response = await client.post(f"{agent_base_url}/reset", timeout=5.0)
    except (httpx.ConnectError, httpx.TimeoutException) as exc:
        logger.warning("reset_endpoint_unreachable", error=str(exc))
        return [
            OutgoingMessage(
                chat_id=chat_id,
                text="Reset endpoint недоступен. Обратитесь к администратору.",
            )
        ]

    if response.status_code == 404:
        return [
            OutgoingMessage(
                chat_id=chat_id,
                text="Reset endpoint недоступен. Обратитесь к администратору.",
            )
        ]
    return [OutgoingMessage(chat_id=chat_id, text="Контекст сброшен.")]


def make_handle_context(store: StateStore, prototype_state: PrototypeStateStore):
    async def handle_context(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list[OutgoingEvent]:
        try:
            _, platform_chat_id = await _require_platform_context(event, store, chat_mgr)
        except RuntimeError as exc:
            logger.warning("context_scope_incomplete", error=str(exc))
            return [OutgoingMessage(chat_id=event.chat_id, text="Контекст комнаты не готов. Попробуй позже.")]

        current_session = await prototype_state.get_current_session(platform_chat_id)
        tokens_used = await prototype_state.get_last_tokens_used(platform_chat_id)
        sessions = await prototype_state.list_saved_sessions(event.user_id)

        lines = [
            "Контекст:",
            f"  Контекст чата: {platform_chat_id}",
            f"  Сессия: {current_session or 'не загружена'}",
            f"  Токены (последний ответ): {tokens_used}",
            f"  Сохранения ({len(sessions)}):",
        ]
        if sessions:
            for session in sessions:
                created = session.get("created_at", "")[:10]
                lines.append(f"    - {session['name']} ({created})")
        else:
            lines.append("    (нет)")

        return [OutgoingMessage(chat_id=event.chat_id, text="\n".join(lines))]

    return handle_context
