from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import Any

import structlog
from nio.api import RoomVisibility
from nio.responses import RoomCreateError

from adapter.matrix.agent_registry import AgentRegistry
from adapter.matrix.handlers.auth import default_agent_notice
from adapter.matrix.store import (
    get_user_meta,
    next_chat_id,
    next_platform_chat_id,
    set_room_meta,
)
from core.protocol import IncomingCommand, OutgoingMessage

logger = structlog.get_logger(__name__)


def _is_unregistered_chat_id(chat_id: str) -> bool:
    return chat_id.startswith("unregistered:")


async def _fallback_new_chat(
    event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
    if not await auth_mgr.is_authenticated(event.user_id):
        return [OutgoingMessage(chat_id=event.chat_id, text="Введите !start чтобы начать.")]

    name = " ".join(event.args).strip() if event.args else ""
    chats = await chat_mgr.list_active(event.user_id)
    chat_id = f"C{len(chats) + 1}"
    ctx = await chat_mgr.get_or_create(
        user_id=event.user_id,
        chat_id=chat_id,
        platform=event.platform,
        surface_ref=event.chat_id,
        name=name or None,
    )
    return [
        OutgoingMessage(
            chat_id=event.chat_id, text=f"Создан чат: {ctx.display_name} ({ctx.chat_id})"
        )
    ]


def make_handle_new_chat(
    client: Any | None,
    store: Any | None,
    registry: AgentRegistry | None = None,
) -> Callable[..., Awaitable[list]]:
    async def handle_new_chat(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list:
        if client is None or store is None:
            return await _fallback_new_chat(event, auth_mgr, platform, chat_mgr, settings_mgr)

        if not await auth_mgr.is_authenticated(event.user_id):
            return [
                OutgoingMessage(
                    chat_id=event.chat_id,
                    text="Сначала примите приглашение бота.",
                )
            ]

        user_meta = await get_user_meta(store, event.user_id)
        space_id = (user_meta or {}).get("space_id")
        if not space_id:
            return [
                OutgoingMessage(
                    chat_id=event.chat_id,
                    text="Ошибка: Space не найден. Примите приглашение бота заново.",
                )
            ]

        name = " ".join(event.args).strip() if event.args else ""
        chat_id = await next_chat_id(store, event.user_id)
        platform_chat_id = await next_platform_chat_id(store)
        room_name = name or f"Чат {chat_id}"

        response = await client.room_create(
            name=room_name,
            visibility=RoomVisibility.private,
            is_direct=False,
            invite=[event.user_id],
        )
        if isinstance(response, RoomCreateError):
            logger.error(
                "room_create failed",
                user_id=event.user_id,
                status_code=getattr(response, "status_code", None),
            )
            return [OutgoingMessage(chat_id=event.chat_id, text="Не удалось создать комнату.")]

        room_id = getattr(response, "room_id", None)
        if not room_id:
            return [OutgoingMessage(chat_id=event.chat_id, text="Не удалось создать комнату.")]

        homeserver = event.user_id.split(":")[-1]
        await client.room_put_state(
            room_id=space_id,
            event_type="m.space.child",
            content={"via": [homeserver]},
            state_key=room_id,
        )

        agent_id = None
        agent_assignment = "none"
        if registry is not None:
            assignment = registry.resolve_agent_for_user(event.user_id)
            agent_id = assignment.agent_id
            agent_assignment = assignment.source

        room_meta: dict = {
            "room_type": "chat",
            "chat_id": chat_id,
            "display_name": room_name,
            "matrix_user_id": event.user_id,
            "space_id": space_id,
            "platform_chat_id": platform_chat_id,
            "agent_id": agent_id,
            "agent_assignment": agent_assignment,
        }
        await set_room_meta(store, room_id, room_meta)
        ctx = await chat_mgr.get_or_create(
            user_id=event.user_id,
            chat_id=chat_id,
            platform=event.platform,
            surface_ref=room_id,
            name=room_name,
        )
        text = f"Создан чат: {ctx.display_name} ({ctx.chat_id})"
        if agent_assignment == "default":
            text = f"{text}\n\n{default_agent_notice()}"
        return [
            OutgoingMessage(
                chat_id=event.chat_id,
                text=text,
            )
        ]

    return handle_new_chat


async def handle_list_chats(
    event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
    chats = await chat_mgr.list_active(event.user_id)
    if not chats:
        return [OutgoingMessage(chat_id=event.chat_id, text="Нет активных чатов.")]
    lines = [f"• {c.display_name} ({c.chat_id})" for c in chats]
    return [OutgoingMessage(chat_id=event.chat_id, text="\n".join(lines))]


def make_handle_rename(
    client: Any | None,
    store: Any | None,
) -> Callable[..., Awaitable[list]]:
    async def handle_rename(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list:
        if not event.args:
            return [
                OutgoingMessage(chat_id=event.chat_id, text="Укажите название: !rename Название")
            ]
        if _is_unregistered_chat_id(event.chat_id):
            return [
                OutgoingMessage(
                    chat_id=event.chat_id,
                    text=(
                        "Этот чат не найден в локальном состоянии бота. "
                        "Открой зарегистрированную комнату или создай новый чат через !new."
                    ),
                )
            ]

        new_name = " ".join(event.args)
        ctx = await chat_mgr.rename(event.chat_id, new_name, user_id=event.user_id)
        if client is not None and ctx.surface_ref:
            await client.room_put_state(
                room_id=ctx.surface_ref,
                event_type="m.room.name",
                content={"name": new_name},
                state_key="",
            )

        return [OutgoingMessage(chat_id=event.chat_id, text=f"Переименован в: {ctx.display_name}")]

    return handle_rename


def make_handle_archive(
    client: Any | None,
    store: Any | None,
) -> Callable[..., Awaitable[list]]:
    async def handle_archive(
        event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
    ) -> list:
        if _is_unregistered_chat_id(event.chat_id):
            return [
                OutgoingMessage(
                    chat_id=event.chat_id,
                    text=(
                        "Этот чат не найден в локальном состоянии бота. "
                        "Создай новый чат через !new."
                    ),
                )
            ]
        ctx = await chat_mgr.get(event.chat_id, user_id=event.user_id)
        if ctx is None:
            return [OutgoingMessage(chat_id=event.chat_id, text="Этот чат не найден.")]
        await chat_mgr.archive(event.chat_id, user_id=event.user_id)
        if client is not None and ctx.surface_ref:
            await client.room_leave(ctx.surface_ref)
        return [OutgoingMessage(chat_id=event.chat_id, text="Чат архивирован.")]

    return handle_archive
