"""Agent 会话历史的查询、授权与删除应用服务。"""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from collections.abc import Callable
from typing import Any, Optional, Protocol
from weakref import WeakValueDictionary

from app.application.database import (
    AsyncDatabaseExecutor,
)
from app.schemas.agent import AgentChatSessionDetail, AgentChatSessionSummary
from app.schemas.exception import AgentChatPersistenceUnavailableError
from app.runtime.observability import record_metric


DEFAULT_AGENT_CHAT_WRITE_CAPACITY = 32
DEFAULT_AGENT_CHAT_SESSION_CAPACITY = 4


def has_custom_agent_chat_title(value: Optional[str]) -> bool:
    """判断会话标题是否已经脱离默认占位标题。"""
    return bool(value and value.strip() and value.strip() != "未命名会话")


class AgentChatPrincipal(Protocol):
    """会话访问控制所需的最小用户身份。"""

    id: Any
    name: Optional[str]
    is_superuser: bool


class AsyncAgentChatRepository(Protocol):
    """Agent 会话用例需要的最小异步持久化端口。"""

    async def async_list_by_page(
        self,
        page: int = 1,
        count: int = 30,
        user_id: Optional[str] = None,
        username: Optional[str] = None,
    ) -> list[Any]:
        """分页读取用户可见的会话。"""
        ...

    async def async_get(
        self,
        session_id: str,
        user_id: Optional[str] = None,
    ) -> Optional[Any]:
        """按服务端会话 ID 读取记录。"""
        ...

    async def async_delete(
        self,
        session_id: str,
        user_id: Optional[str] = None,
    ) -> bool:
        """删除指定服务端会话。"""
        ...

    async def async_stage_delete(
        self,
        session_id: str,
        user_id: Optional[str] = None,
    ) -> bool:
        """暂存删除指定服务端会话，不提交调用方事务。"""
        ...

    def get(self, session_id: str, user_id: Optional[str] = None) -> Optional[Any]:
        """同步读取服务端会话。"""
        ...


class SyncAgentChatRepository(Protocol):
    """仅包含 Agent 编排所需同步持久化方法的适配器端口。"""

    def append_display_messages(
        self,
        session_id: str,
        user_id: Optional[str] = None,
        messages: Optional[list[dict]] = None,
        username: Optional[str] = None,
        channel: Optional[Any] = None,
        source: Optional[str] = None,
        original_chat_id: Optional[str] = None,
        client_session_id: Optional[str] = None,
    ) -> Optional[Any]:
        """追加用户可见消息。"""
        ...

    def save_display_messages(
        self,
        session_id: str,
        user_id: Optional[str] = None,
        messages: Optional[list[dict]] = None,
        username: Optional[str] = None,
        channel: Optional[Any] = None,
        source: Optional[str] = None,
        original_chat_id: Optional[str] = None,
        client_session_id: Optional[str] = None,
    ) -> Optional[Any]:
        """保存用户可见消息快照。"""
        ...

    def save_agent_messages(
        self,
        session_id: str,
        user_id: Optional[str],
        messages: list[dict],
    ) -> None:
        """保存可恢复的原始 Agent 消息。"""
        ...

    def update_title_if_empty(
        self,
        session_id: str,
        user_id: Optional[str],
        title: Optional[str],
        username: Optional[str] = None,
        channel: Optional[Any] = None,
        source: Optional[str] = None,
        original_chat_id: Optional[str] = None,
        client_session_id: Optional[str] = None,
    ) -> None:
        """在会话尚无标题时写入标题。"""
        ...


SyncAgentChatRepositoryFactory = Callable[[object], SyncAgentChatRepository]
SyncAgentChatTransaction = Callable[[Callable[[object], object]], object]


@dataclass(frozen=True, slots=True)
class AgentChatRecord:
    """脱离 ORM 会话的 Agent 会话持久化投影。"""

    id: Optional[int]
    session_id: str
    client_session_id: Optional[str]
    title: Optional[str]
    channel: Optional[str]
    source: Optional[str]
    user_id: Optional[str]
    username: Optional[str]
    original_chat_id: Optional[str]
    message_count: int
    created_at: Any
    updated_at: Any
    messages: list[dict]
    agent_messages: list[dict]


class AsyncUnitOfWork(Protocol):
    """Agent 会话异步写用例所需的最小事务端口。"""

    async def commit(self) -> None:
        """提交当前请求事务。"""
        ...

    async def rollback(self) -> None:
        """回滚当前请求事务。"""
        ...


class AgentChatService:
    """统一执行 Agent 会话查询、访问控制和删除。"""

    def __init__(
        self,
        repository: AsyncAgentChatRepository,
        unit_of_work: Optional[AsyncUnitOfWork] = None,
    ) -> None:
        """保存会话持久化端口和可选请求级事务。"""
        self._repository = repository
        self._unit_of_work = unit_of_work

    async def list(
        self,
        principal: AgentChatPrincipal,
        *,
        page: int = 1,
        count: int = 30,
    ) -> list[AgentChatSessionSummary]:
        """分页返回当前用户可见的会话摘要。"""
        user_id = None if principal.is_superuser else str(principal.id)
        username = None if principal.is_superuser else principal.name
        records = await self._repository.async_list_by_page(
            page=page,
            count=count,
            user_id=user_id,
            username=username,
        )
        return [self.to_summary(self._project(record)) for record in records]

    async def get_accessible(
        self,
        session_id: str,
        principal: AgentChatPrincipal,
    ) -> Optional[AgentChatRecord]:
        """读取会话并在应用边界执行访问控制。"""
        projected = await self.get(session_id)
        if projected is None:
            return None
        if not self.can_access(projected, principal):
            return None
        return projected

    async def get(
        self,
        session_id: str,
        user_id: Optional[str] = None,
    ) -> Optional[AgentChatRecord]:
        """读取不附带授权判断的会话投影。"""
        record = await self._repository.async_get(
            session_id=session_id,
            user_id=user_id,
        )
        if record is None:
            return None
        return self._project(record)

    async def delete(
        self,
        session_id: str,
        principal: AgentChatPrincipal,
    ) -> bool:
        """仅在当前用户可访问时删除会话。"""
        record = await self.get_accessible(session_id, principal)
        if record is None:
            return False
        if self._unit_of_work is None:
            return await self._repository.async_delete(session_id=session_id)
        try:
            deleted = await self._repository.async_stage_delete(
                session_id=session_id
            )
            if deleted:
                await self._unit_of_work.commit()
            return deleted
        except Exception:
            await self._unit_of_work.rollback()
            raise

    def get_sync(self, session_id: str) -> Optional[AgentChatRecord]:
        """同步读取会话投影，供同步 Agent 编排路径使用。"""
        record = self._repository.get(session_id=session_id)
        return self._project(record) if record is not None else None

    @staticmethod
    def can_access(
        record: AgentChatRecord,
        principal: AgentChatPrincipal,
    ) -> bool:
        """判断用户是否拥有会话访问权。"""
        if principal.is_superuser:
            return True
        user_id = str(principal.id)
        username = str(principal.name or "")
        return record.user_id == user_id or (
            bool(username) and record.username == username
        )

    @staticmethod
    def to_summary(record: AgentChatRecord) -> AgentChatSessionSummary:
        """把持久化投影转换为会话摘要 DTO。"""
        return AgentChatSessionSummary(
            id=record.id,
            session_id=record.session_id,
            client_session_id=record.client_session_id,
            title=record.title,
            channel=record.channel,
            source=record.source,
            user_id=record.user_id,
            username=record.username,
            original_chat_id=record.original_chat_id,
            message_count=record.message_count,
            created_at=record.created_at,
            updated_at=record.updated_at,
        )

    @classmethod
    def to_detail(cls, record: AgentChatRecord) -> AgentChatSessionDetail:
        """把持久化投影转换为会话详情 DTO。"""
        return AgentChatSessionDetail(
            **cls.to_summary(record).model_dump(),
            messages=record.messages,
        )

    @staticmethod
    def _project(record: Any) -> AgentChatRecord:
        """立即复制 ORM 字段，避免对象越过请求级会话边界。"""
        return AgentChatRecord(
            id=record.id,
            session_id=record.session_id,
            client_session_id=record.client_session_id,
            title=record.title,
            channel=record.channel,
            source=record.source,
            user_id=record.user_id,
            username=record.username,
            original_chat_id=record.original_chat_id,
            message_count=record.message_count or 0,
            created_at=record.created_at,
            updated_at=record.updated_at,
            messages=list(record.display_messages or []),
            agent_messages=list(record.agent_messages or []),
        )


class AgentChatPersistenceService:
    """把 Agent 编排所需的同步持久化操作委托给有界数据库 worker。"""

    def __init__(
        self,
        repository: SyncAgentChatRepositoryFactory,
        async_executor: AsyncDatabaseExecutor,
        sync_transaction: SyncAgentChatTransaction,
        capacity: int = DEFAULT_AGENT_CHAT_WRITE_CAPACITY,
        session_capacity: int = DEFAULT_AGENT_CHAT_SESSION_CAPACITY,
    ) -> None:
        """保存同步仓储工厂、事务端口和两级写入容量。"""
        if capacity < 1:
            raise ValueError("AgentChat 写入容量必须大于 0")
        if session_capacity < 1:
            raise ValueError("AgentChat 单会话写入容量必须大于 0")
        self._repository = repository
        self._async_executor = async_executor
        self._sync_transaction = sync_transaction
        self._capacity = capacity
        self._session_capacity = session_capacity
        self._pending_writes = 0
        self._pending_by_session: dict[str, int] = {}
        self._active_tasks: set[asyncio.Task[object]] = set()
        self._closing = False
        # append_display_messages 属于读取旧快照后整列写回的复合操作；按会话串行化，
        # 才能在 worker 并发下保持首次建行和既有会话追加的完整性。弱引用避免长期运行
        # 中为一次性会话永久保留锁对象，不限制不同会话之间的 worker 并行度。
        self._session_locks: WeakValueDictionary[str, asyncio.Lock] = WeakValueDictionary()

    def _session_lock(self, session_id: str) -> asyncio.Lock:
        """返回当前进程内指定会话的写锁。"""
        lock = self._session_locks.get(session_id)
        if lock is None:
            lock = asyncio.Lock()
            self._session_locks[session_id] = lock
        return lock

    def begin_shutdown(self) -> None:
        """停止接受新的 AgentChat 持久化任务。"""
        self._closing = True

    async def _run_write(
        self,
        session_id: str,
        operation: Callable[[SyncAgentChatRepository], object],
    ) -> None:
        """在线程 worker 内完成同步写入并丢弃仓储对象返回值。"""
        # 同时限制全局和单会话等待量，避免一个热点会话占满总 admission 后饿死其他会话。
        if self._closing:
            raise AgentChatPersistenceUnavailableError(
                "AgentChat 持久化服务当前不可接收任务"
            )
        session_pending = self._pending_by_session.get(session_id, 0)
        if (
            self._pending_writes >= self._capacity
            or session_pending >= self._session_capacity
        ):
            record_metric("agent.chat.persistence.rejected")
            raise AgentChatPersistenceUnavailableError(
                f"AgentChat 写入容量已用尽（全局上限 {self._capacity}，"
                f"单会话上限 {self._session_capacity}）"
            )
        self._pending_writes += 1
        self._pending_by_session[session_id] = session_pending + 1
        current = asyncio.current_task()
        if current is not None:
            self._active_tasks.add(current)
        record_metric("agent.chat.persistence.pending", 1)
        try:
            async with self._session_lock(session_id):
                def execute() -> None:
                    """在单一同步事务中执行写入，不让 ORM 对象越过 worker 边界。"""
                    self._sync_transaction(
                        lambda session: operation(self._repository(session))
                    )

                await self._async_executor.run(execute)
        finally:
            self._pending_writes -= 1
            remaining = self._pending_by_session.get(session_id, 1) - 1
            if remaining:
                self._pending_by_session[session_id] = remaining
            else:
                self._pending_by_session.pop(session_id, None)
            if current is not None:
                self._active_tasks.discard(current)
            record_metric("agent.chat.persistence.pending", -1)

    async def shutdown(self) -> None:
        """拒绝新写入并等待当前会话锁和 worker 操作取得终态。"""
        self.begin_shutdown()
        current = asyncio.current_task()
        tasks = tuple(task for task in self._active_tasks if task is not current)
        if tasks:
            # wait 不会在生命周期超时时取消实际写入；外层可及时返回并保留
            # 数据库 worker owner，已开始的事务继续由 worker 收口。
            await asyncio.wait(tasks)

    async def async_append_display_messages(
        self,
        *,
        session_id: str,
        user_id: Optional[str] = None,
        messages: Optional[list[dict]] = None,
        username: Optional[str] = None,
        channel: Optional[Any] = None,
        source: Optional[str] = None,
        original_chat_id: Optional[str] = None,
        client_session_id: Optional[str] = None,
    ) -> None:
        """异步追加展示消息，等待同步事务取得确定终态。"""
        await self._run_write(
            session_id,
            lambda repository: repository.append_display_messages(
                session_id=session_id,
                user_id=user_id,
                messages=messages,
                username=username,
                channel=channel,
                source=source,
                original_chat_id=original_chat_id,
                client_session_id=client_session_id,
            )
        )
        return None

    async def async_save_display_messages(
        self,
        *,
        session_id: str,
        user_id: Optional[str] = None,
        messages: Optional[list[dict]] = None,
        username: Optional[str] = None,
        channel: Optional[Any] = None,
        source: Optional[str] = None,
        original_chat_id: Optional[str] = None,
        client_session_id: Optional[str] = None,
    ) -> None:
        """异步保存展示消息快照，实际写入由有界 worker 承接。"""
        await self._run_write(
            session_id,
            lambda repository: repository.save_display_messages(
                session_id=session_id,
                user_id=user_id,
                messages=messages,
                username=username,
                channel=channel,
                source=source,
                original_chat_id=original_chat_id,
                client_session_id=client_session_id,
            )
        )
        return None

    async def async_save_agent_messages(
        self,
        *,
        session_id: str,
        user_id: str,
        messages: list[dict],
    ) -> None:
        """异步保存可恢复的原始消息。"""
        await self._run_write(
            session_id,
            lambda repository: repository.save_agent_messages(
                session_id=session_id,
                user_id=user_id,
                messages=messages,
            )
        )

    async def async_update_title_if_empty(
        self,
        *,
        session_id: str,
        user_id: Optional[str],
        title: Optional[str],
        username: Optional[str] = None,
        channel: Optional[Any] = None,
        source: Optional[str] = None,
        original_chat_id: Optional[str] = None,
        client_session_id: Optional[str] = None,
    ) -> None:
        """异步写入首次生成的会话标题。"""
        await self._run_write(
            session_id,
            lambda repository: repository.update_title_if_empty(
                session_id=session_id,
                user_id=user_id,
                title=title,
                username=username,
                channel=channel,
                source=source,
                original_chat_id=original_chat_id,
                client_session_id=client_session_id,
            )
        )


_configured_agent_chat_service: AgentChatService | None = None
_configured_agent_chat_persistence: AgentChatPersistenceService | None = None


def configure_agent_chat_service(service: AgentChatService) -> None:
    """由启动组合根登记同步 Agent 会话服务。"""
    global _configured_agent_chat_service
    _configured_agent_chat_service = service


def get_configured_agent_chat_service() -> AgentChatService:
    """返回启动阶段登记的 Agent 会话服务。"""
    if _configured_agent_chat_service is None:
        raise RuntimeError("Agent 会话服务尚未配置")
    return _configured_agent_chat_service


def configure_agent_chat_persistence(
    service: AgentChatPersistenceService,
) -> None:
    """由启动组合根登记 Agent 编排所需的同步持久化端口。"""
    global _configured_agent_chat_persistence
    _configured_agent_chat_persistence = service


def get_configured_agent_chat_persistence() -> AgentChatPersistenceService:
    """返回由启动组合根登记的 AgentChat worker 端口。"""
    if _configured_agent_chat_persistence is None:
        raise RuntimeError("Agent 会话持久化服务尚未配置")
    return _configured_agent_chat_persistence
