from __future__ import annotations

import logging
import re
from dataclasses import dataclass
from time import perf_counter

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.ai_config import SystemConfig
from app.models.chat import ChatMessage, ChatSession

logger = logging.getLogger(__name__)

DEFAULT_CONTEXT_MESSAGE_COUNT = 10
MAX_CONTEXT_MESSAGE_COUNT = 100


@dataclass(frozen=True)
class SummaryUpdateResult:
    trace: dict | None = None


@dataclass(frozen=True)
class _SummaryWork:
    messages: list[ChatMessage]
    messages_text: str
    request: dict


class ChatContextService:
    """Owns the runtime policy for a session's conversational memory."""

    @staticmethod
    def message_limit(db: Session) -> int:
        config = db.scalar(
            select(SystemConfig).where(SystemConfig.config_key == "chat_context_message_count")
        )
        if config is None or not config.config_value.strip():
            return DEFAULT_CONTEXT_MESSAGE_COUNT
        try:
            value = int(float(config.config_value.strip()))
        except ValueError:
            logger.warning(
                "Invalid chat_context_message_count=%r; falling back to %s",
                config.config_value,
                DEFAULT_CONTEXT_MESSAGE_COUNT,
            )
            return DEFAULT_CONTEXT_MESSAGE_COUNT
        return max(0, min(MAX_CONTEXT_MESSAGE_COUNT, value))

    @classmethod
    def select_memory(
        cls,
        db: Session,
        history: list[ChatMessage] | None,
        summary: str | None,
        summary_up_to_message_id: int | None = None,
    ) -> tuple[list[ChatMessage], str | None]:
        limit = cls.message_limit(db)
        if limit == 0:
            return [], None
        recent = list((history or [])[-limit:])
        if summary and summary_up_to_message_id is not None:
            recent = [message for message in recent if message.id > summary_up_to_message_id]
        return recent, summary

    @classmethod
    def update_summary(
        cls,
        db: Session,
        session: ChatSession,
        history: list[ChatMessage],
    ) -> SummaryUpdateResult:
        """Roll older messages into one bounded summary as soon as they leave the raw window."""
        work = cls._prepare_summary(db, session, history)
        if work is None:
            return SummaryUpdateResult()
        started = perf_counter()
        try:
            # Local import avoids a rag_service -> context_service -> model_service cycle.
            from app.services.model_service import ModelClientService

            new_summary = ModelClientService.summarize_or_raise(
                db,
                work.messages_text,
                previous_summary=session.summary,
            )
            return cls._apply_summary(session, work, new_summary, started)
        except Exception as exc:
            return cls._summary_failure(session, work, exc, started)

    @classmethod
    async def update_summary_async(
        cls,
        db: Session,
        session: ChatSession,
        history: list[ChatMessage],
    ) -> SummaryUpdateResult:
        work = cls._prepare_summary(db, session, history)
        if work is None:
            return SummaryUpdateResult()
        started = perf_counter()
        try:
            from app.services.model_service import ModelClientService

            new_summary = await ModelClientService.summarize_or_raise_async(
                db,
                work.messages_text,
                previous_summary=session.summary,
            )
            return cls._apply_summary(session, work, new_summary, started)
        except Exception as exc:
            return cls._summary_failure(session, work, exc, started)

    @classmethod
    def _prepare_summary(
        cls,
        db: Session,
        session: ChatSession,
        history: list[ChatMessage],
    ) -> _SummaryWork | None:
        limit = cls.message_limit(db)
        if limit == 0 or len(history) <= limit:
            return None
        cutoff = len(history) - limit
        start_idx = cls._covered_history_index(session, history)
        if cutoff <= start_idx:
            return None
        messages = history[start_idx:cutoff]
        messages_text = cls.messages_text(messages)
        if not messages_text:
            return None
        return _SummaryWork(
            messages=messages,
            messages_text=messages_text,
            request={
                "previousSummary": session.summary or "",
                "messages": messages_text,
                "messageCount": len(messages),
                "contextMessageLimit": limit,
            },
        )

    @classmethod
    def _apply_summary(
        cls,
        session: ChatSession,
        work: _SummaryWork,
        summary: str,
        started: float,
    ) -> SummaryUpdateResult:
        summary = summary.strip()
        if not summary:
            raise ValueError("模型返回了空的历史摘要")
        session.summary = summary
        session.summary_up_to_message_id = work.messages[-1].id
        duration_ms = int((perf_counter() - started) * 1000)
        return SummaryUpdateResult(
            trace=cls._trace(
                work.request,
                {"summary": summary, "count": len(work.messages)},
                duration_ms=duration_ms,
            )
        )

    @classmethod
    def _summary_failure(
        cls,
        session: ChatSession,
        work: _SummaryWork,
        exc: Exception,
        started: float,
    ) -> SummaryUpdateResult:
        duration_ms = int((perf_counter() - started) * 1000)
        logger.exception(
            "Chat history summary failed for session_id=%s message_count=%s",
            session.id,
            len(work.messages),
            exc_info=exc,
        )
        return SummaryUpdateResult(
            trace=cls._trace(
                work.request,
                {"count": 0, "fallback": "recent_messages_only"},
                duration_ms=duration_ms,
                status="fallback",
                error=str(exc)[:500],
            )
        )

    @staticmethod
    def _covered_history_index(session: ChatSession, history: list[ChatMessage]) -> int:
        if session.summary_up_to_message_id is None:
            return 0
        for index, message in enumerate(history):
            if message.id > session.summary_up_to_message_id:
                return index
        return len(history)

    @staticmethod
    def messages_text(messages: list[ChatMessage]) -> str:
        role_labels = {"user": "用户", "assistant": "大本营答疑助手"}
        parts: list[str] = []
        for message in messages:
            content = re.sub(
                r"<think[\s\S]*?</think\s*>",
                "",
                message.content,
                flags=re.IGNORECASE,
            ).strip()
            if content:
                parts.append(f"{role_labels.get(message.role, message.role)}：{content}")
        return "\n".join(parts)

    @staticmethod
    def _trace(
        request: dict,
        response: dict,
        *,
        duration_ms: int,
        status: str = "success",
        error: str | None = None,
    ) -> dict:
        return {
            "tool": "chat_memory_summary",
            "order": 0,
            "request": request,
            "response": response,
            **response,
            "status": status,
            "startedAtMs": 0,
            "endedAtMs": duration_ms,
            "durationMs": duration_ms,
            "responseCount": response.get("count"),
            "error": error,
            "retries": 0,
        }
