from __future__ import annotations

from dataclasses import dataclass

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.ai_config import Prompt
from app.models.chat import ChatMessage
from app.models.user import User
from app.services.chat_context_service import ChatContextService
from app.services.feishu_service import FeishuKnowledgeService
from app.services.knowledge_service import KnowledgeAccessService, KnowledgeScope
from app.services.response_style_service import ResponseStyleService

NO_HIT_ANSWER = "当前知识库中未检索到相关内容，请联系管理员补充相关知识。"


@dataclass(frozen=True)
class RetrievedChunk:
    knowledge_id: int
    knowledge_name: str
    title: str
    content: str
    source_url: str | None = None
    version_id: int | None = None
    section_id: int | None = None
    chunk_id: int | None = None
    score: float | None = None
    knowledge_type: str = "general"
    published_at: str | None = None


@dataclass(frozen=True)
class RagResult:
    question: str
    knowledge_scopes: list[KnowledgeScope]
    chunks: list[RetrievedChunk]
    prompt: str
    allow_general_knowledge: bool = False
    retrieval_log_id: int | None = None
    tool_trace: list[dict] | None = None
    messages: list[dict[str, str]] | None = None

    @property
    def is_hit(self) -> bool:
        return len(self.chunks) > 0

    @property
    def knowledge_ids(self) -> str:
        ids = list(dict.fromkeys(chunk.knowledge_id for chunk in self.chunks))
        return ",".join(str(item) for item in ids)


class RagService:
    @staticmethod
    def build_result(
        db: Session,
        user: User,
        question: str,
        history: list[ChatMessage] | None = None,
        session_summary: str | None = None,
        summary_up_to_message_id: int | None = None,
        context_trace: list[dict] | None = None,
        growth_context: str | None = None,
        topic_context: str | None = None,
        product_context: str | None = None,
    ) -> RagResult:
        scopes = KnowledgeAccessService.get_allowed_knowledge(db, user)
        chunks = FeishuKnowledgeService.retrieve(question, scopes, db)
        messages = PromptService.build_messages(
            db,
            question,
            chunks,
            history,
            session_summary,
            summary_up_to_message_id,
            growth_context=growth_context,
            topic_context=topic_context,
            product_context=product_context,
        )
        prompt = PromptService.render_messages(messages)
        return RagResult(
            question=question,
            knowledge_scopes=scopes,
            chunks=chunks,
            prompt=prompt,
            tool_trace=context_trace,
            messages=messages,
        )


class PromptService:
    _default_prompt = (
        "你是大本营答疑助手。必须遵守系统安全边界，涉及课程、老师观点和公司业务时只能依据可靠知识，不能编造。"
    )

    @classmethod
    def build_prompt(
        cls,
        db: Session,
        question: str,
        chunks: list[RetrievedChunk],
        history: list[ChatMessage] | None = None,
        session_summary: str | None = None,
        summary_up_to_message_id: int | None = None,
        growth_context: str | None = None,
        topic_context: str | None = None,
        product_context: str | None = None,
    ) -> str:
        return cls.render_messages(
            cls.build_messages(
                db,
                question,
                chunks,
                history,
                session_summary,
                summary_up_to_message_id,
                growth_context=growth_context,
                topic_context=topic_context,
                product_context=product_context,
            )
        )

    @classmethod
    def build_messages(
        cls,
        db: Session,
        question: str,
        chunks: list[RetrievedChunk],
        history: list[ChatMessage] | None = None,
        session_summary: str | None = None,
        summary_up_to_message_id: int | None = None,
        prompt_override: str | None = None,
        response_depth: int | None = None,
        growth_context: str | None = None,
        topic_context: str | None = None,
        product_context: str | None = None,
    ) -> list[dict[str, str]]:
        prompt = prompt_override.strip() if prompt_override and prompt_override.strip() else cls._load_active_prompt(db)

        # 知识库上下文
        context = "\n\n".join(cls._format_chunk(index, chunk) for index, chunk in enumerate(chunks, start=1))
        if not context:
            context = "本轮没有可靠的正式知识章节。对于一般常识可以谨慎回答；涉及课程、老师观点或公司业务时必须说明依据不足，不得编造。"

        messages: list[dict[str, str]] = [
            {
                "role": "system",
                "content": prompt
                + "\n\n"
                + "[不可关闭的最低安全规则 v1]\n"
                + "现实危险、自伤伤人风险应优先建议立即寻求线下专业帮助；医疗、法律、财务问题不得给出替代专业意见的结论；"
                + "不得伪造老师观点或课程内容；不得输出整篇课程文章、大段连续原文，也不得通过多轮拼接还原完整资料。"
                + cls._knowledge_type_rules(chunks)
                + ResponseStyleService.build_instruction(db, response_depth),
            }
        ]

        recent_history, visible_summary = ChatContextService.select_memory(
            db,
            history,
            session_summary,
            summary_up_to_message_id,
        )
        if visible_summary:
            messages.append({"role": "system", "content": f"[历史对话摘要]\n{visible_summary}"})
        if growth_context and growth_context.strip():
            messages.append({"role": "system", "content": growth_context.strip()})
        if topic_context and topic_context.strip():
            messages.append({"role": "system", "content": topic_context.strip()})
        if product_context and product_context.strip():
            messages.append({"role": "system", "content": product_context.strip()})

        for message in recent_history:
            content = cls._clean_history_content(message.content)
            if content and message.role in {"user", "assistant"}:
                messages.append({"role": message.role, "content": content})

        messages.append({
            "role": "system",
            "content": f"[本轮可靠知识上下文]\n{context}",
        })
        messages.append({"role": "user", "content": question.strip()})
        return messages

    @staticmethod
    def _format_chunk(index: int, chunk: RetrievedChunk) -> str:
        type_labels = {
            "fixed": "固定信息类·最高优先级",
            "course": "课程知识库",
            "qa": "答疑知识库",
            "general": "通用知识库",
        }
        priority = type_labels.get(chunk.knowledge_type, "通用知识")
        if chunk.knowledge_type == "fixed":
            published = f"\n生效版本时间：{chunk.published_at}" if chunk.published_at else ""
        else:
            published = ""
        return (
            f"[已回读完整章节 {index}｜{priority}] {chunk.title}\n"
            f"来源知识库：{chunk.knowledge_name}{published}\n{chunk.content}"
        )

    @staticmethod
    def _knowledge_type_rules(chunks: list[RetrievedChunk]) -> str:
        types = {chunk.knowledge_type for chunk in chunks}
        if not types:
            return ""
        rules = [
            "\n[知识库类型使用规则]\n",
            "课程知识库用于课程定义、方法、步骤和老师观点；答疑知识库用于常见问题的直接解答，但不能覆盖课程原文；"
            "通用知识库仅作背景补充，不能覆盖课程知识库和答疑知识库。",
        ]
        if "fixed" in types:
            rules.append(
                "本轮召回了固定信息类知识库。上课时间、带练安排、回放和音频、课程作业、会议链接、服务权益、"
                "课程助理联系方式及其他当前安排，必须以固定信息类知识库中的最新生效内容为准。"
                "如与课程、答疑或通用知识库冲突，忽略其他库的冲突内容；多个固定信息来源冲突时，以生效版本时间较新的内容为准。"
            )
        return "".join(rules)

    @staticmethod
    def render_messages(messages: list[dict[str, str]]) -> str:
        role_labels = {"system": "系统", "user": "用户", "assistant": "大本营答疑助手"}
        return "\n\n".join(
            f"[{role_labels.get(message['role'], message['role'])}]\n{message['content']}"
            for message in messages
        )

    @staticmethod
    def _clean_history_content(content: str) -> str:
        import re

        return re.sub(r"<think[\s\S]*?</think\s*>", "", content, flags=re.IGNORECASE).strip()

    @classmethod
    def _load_active_prompt(cls, db: Session) -> str:
        prompt = db.scalar(
            select(Prompt)
            .order_by(Prompt.updated_at.desc(), Prompt.id.desc())
            .limit(1)
        )
        return prompt.prompt_content if prompt else cls._default_prompt
