from __future__ import annotations

from datetime import UTC, datetime
from time import perf_counter

from fastapi import HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.chat import ChatMessage, ChatSession
from app.models.user import User
from app.services.ai_request_log_service import AiRequestLogService
from app.services.external_errors import ExternalServiceError
from app.services.model_service import ModelClientService
from app.services.rag_service import RagService, SUMMARY_TRIGGER_ROUNDS


class ChatService:
    @staticmethod
    def create_session(db: Session, user: User) -> ChatSession:
        now = _now()
        session = ChatSession(
            user_id=user.id,
            title="新聊天",
            message_count=0,
            last_message_at=now,
            is_deleted=0,
        )
        db.add(session)
        db.commit()
        db.refresh(session)
        return session

    @staticmethod
    def list_sessions(db: Session, user: User) -> list[ChatSession]:
        return list(
            db.scalars(
                select(ChatSession)
                .where(ChatSession.user_id == user.id, ChatSession.is_deleted == 0)
                .order_by(ChatSession.updated_at.desc())
            )
        )

    @staticmethod
    def get_history(db: Session, user: User, session_id: int) -> list[ChatMessage]:
        session = ChatService._get_user_session(db, user, session_id)
        return list(
            db.scalars(
                select(ChatMessage)
                .where(ChatMessage.session_id == session.id, ChatMessage.user_id == user.id)
                .order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
            )
        )

    @staticmethod
    def update_title(db: Session, user: User, session_id: int, title: str) -> ChatSession:
        session = ChatService._get_user_session(db, user, session_id)
        session.title = title.strip()
        db.add(session)
        db.commit()
        db.refresh(session)
        return session

    @staticmethod
    def delete_session(db: Session, user: User, session_id: int) -> None:
        session = ChatService._get_user_session(db, user, session_id)
        session.is_deleted = 1
        db.add(session)
        db.commit()

    @staticmethod
    def create_answer(db: Session, user: User, session_id: int, question: str) -> str:
        session = ChatService._get_user_session(db, user, session_id)
        ChatService._ensure_quota(user)

        now = _now()
        normalized_question = question.strip()
        user_message = ChatMessage(
            session_id=session.id,
            user_id=user.id,
            role="user",
            content=normalized_question,
            message_status="FINISHED",
            created_at=now,
        )
        db.add(user_message)
        db.flush()

        started_at = perf_counter()
        rag_result = None
        try:
            # 获取历史消息（不含刚插入的 user_message，它还没 flush id）
            history = list(
                db.scalars(
                    select(ChatMessage)
                    .where(
                        ChatMessage.session_id == session.id,
                        ChatMessage.user_id == user.id,
                        ChatMessage.id < user_message.id,
                    )
                    .order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
                )
            )

            # 判断是否需要生成/更新摘要
            rounds = len(history) // 2  # 1 轮 = user + assistant
            if rounds >= SUMMARY_TRIGGER_ROUNDS:
                ChatService._maybe_update_summary(db, session, history)

            # 构建 prompt（传入历史 + 摘要）
            rag_result = RagService.build_result(
                db, user, normalized_question,
                history=history,
                session_summary=session.summary,
            )
            completion = ModelClientService.complete(db, rag_result)
        except ExternalServiceError as exc:
            cost_ms = int((perf_counter() - started_at) * 1000)
            AiRequestLogService.write_failed(
                db,
                session_id=session.id,
                message_id=user_message.id,
                user_id=user.id,
                model_name=None,
                prompt=rag_result.prompt if rag_result is not None else normalized_question,
                knowledge_ids=rag_result.knowledge_ids if rag_result is not None else None,
                retrieve_count=len(rag_result.chunks) if rag_result is not None else 0,
                cost_ms=cost_ms,
                error_message=str(exc),
                retrieved_chunks=rag_result.chunks if rag_result is not None else None,
            )
            db.commit()
            raise HTTPException(
                status_code=status.HTTP_502_BAD_GATEWAY,
                detail=str(exc),
            ) from exc
        cost_ms = int((perf_counter() - started_at) * 1000)

        assistant_message = ChatMessage(
            session_id=session.id,
            user_id=user.id,
            role="assistant",
            content=completion.answer,
            message_status="FINISHED",
            token_input=completion.input_token,
            token_output=completion.output_token,
            response_time_ms=cost_ms,
            model_id=completion.model_id,
            created_at=now,
        )
        db.add(assistant_message)
        db.flush()

        session.message_count += 2
        session.last_message_at = now
        if session.title == "新聊天":
            session.title = _title_from_question(normalized_question)

        user.daily_chat_used += 1
        db.add_all([session, user])
        AiRequestLogService.write_success(
            db,
            session_id=session.id,
            message_id=assistant_message.id,
            user_id=user.id,
            model_name=completion.model_name,
            prompt=rag_result.prompt,
            knowledge_ids=rag_result.knowledge_ids,
            retrieve_count=len(rag_result.chunks),
            input_token=completion.input_token,
            output_token=completion.output_token,
            cost_ms=cost_ms,
            retrieved_chunks=rag_result.chunks,
        )
        db.commit()
        return completion.answer

    @staticmethod
    def stop_generation(db: Session, user: User, session_id: int) -> None:
        ChatService._get_user_session(db, user, session_id)

    @staticmethod
    def _get_user_session(db: Session, user: User, session_id: int) -> ChatSession:
        session = db.get(ChatSession, session_id)
        if session is None or session.user_id != user.id or session.is_deleted:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="会话不存在")
        return session

    @staticmethod
    def _maybe_update_summary(db: Session, session: ChatSession, history: list[ChatMessage]) -> None:
        """当历史超过阈值时，生成/更新摘要。摘要失败不阻断主流程。"""
        if not history:
            return

        # 计算需要摘要的部分：已有摘要覆盖到哪条消息之后的部分
        start_idx = 0
        if session.summary_up_to_message_id is not None:
            for i, msg in enumerate(history):
                if msg.id <= session.summary_up_to_message_id:
                    start_idx = i + 1
                else:
                    break

        # 需要摘要的消息 = 已有摘要之后、到最近 KEEP_RECENT*2 条之前的部分
        from app.services.rag_service import KEEP_RECENT
        end_idx = max(start_idx, len(history) - KEEP_RECENT * 2)

        if end_idx <= start_idx:
            return  # 没有新的需要摘要的内容

        to_summarize = history[start_idx:end_idx]
        if not to_summarize:
            return

        # 拼接成文本
        import re
        role_labels = {"user": "用户", "assistant": "大本营答疑助手"}
        parts = []
        for msg in to_summarize:
            content = re.sub(r"<think[\s\S]*?</think\s*>", "", msg.content, flags=re.IGNORECASE).strip()
            if content:
                parts.append(f"{role_labels.get(msg.role, msg.role)}：{content}")
        messages_text = "\n".join(parts)

        if not messages_text:
            return

        # 调用模型生成摘要
        new_summary = ModelClientService.summarize(db, messages_text)
        if new_summary:
            # 追加到已有摘要后面
            combined = session.summary or ""
            if combined:
                combined += "\n"
            combined += new_summary
            session.summary = combined
            session.summary_up_to_message_id = to_summarize[-1].id

    @staticmethod
    def _ensure_quota(user: User) -> None:
        if user.daily_chat_used >= user.daily_chat_limit:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="今日提问次数已用完")


def _now() -> datetime:
    return datetime.now(UTC).replace(tzinfo=None)


def _title_from_question(question: str) -> str:
    title = question.strip().replace("\n", " ")
    return title[:20] if title else "新聊天"
