from __future__ import annotations

import asyncio
from collections.abc import AsyncIterator
from dataclasses import replace
from types import SimpleNamespace

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

from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.user import User
from app.models.admin import Admin
from app.models.ai_config import ModelConfig
from app.schemas.admin import AgentDebugRequest
from app.services.admin_service import OperationLogService
from app.services.chat_context_service import ChatContextService
from app.services.entitlement_service import EntitlementService, entitlement_dict, entitlement_prompt_context
from app.services.growth_profile_service import GrowthProfileService
from app.services.knowledge_agent_service import KnowledgeAgentService
from app.services.model_stream_service import ModelStreamService
from app.services.model_routing_service import ModelRoutingService
from app.services.reasoning_policy_service import ReasoningPolicyService
from app.services.rag_service import RagResult
from app.services.topic_session_service import TopicSessionService


class AgentDebugService:
    @staticmethod
    async def build_result(db: Session, payload: AgentDebugRequest) -> RagResult:
        preview_knowledge_ids = payload.knowledgeIds or None
        version_overrides = payload.knowledgeVersions or None
        debug_context = AgentDebugService._debug_user_context(db, payload.userId, payload.topicSessionId)
        persisted_history = debug_context["history"]
        max_history_id = max((int(item.id) for item in persisted_history), default=0)
        preview_history = [
            SimpleNamespace(id=max_history_id + index + 1, role=item.role, content=item.content)
            for index, item in enumerate(payload.history)
        ]
        rag_result = await KnowledgeAgentService.build_result(
            db,
            question=payload.question,
            history=[*persisted_history, *preview_history],
            session_summary=debug_context["session_summary"],
            summary_up_to_message_id=debug_context["summary_up_to_message_id"],
            version_overrides=version_overrides,
            preview_knowledge_ids=preview_knowledge_ids,
            user_id=payload.userId,
            context_trace=debug_context["trace"],
            prompt_override=payload.promptContent,
            response_depth=payload.responseDepth,
            growth_context=debug_context["growth_context"],
            topic_context=debug_context["topic_context"],
            product_context=debug_context["product_context"],
        )
        return RagResult(
            question=rag_result.question,
            knowledge_scopes=rag_result.knowledge_scopes,
            chunks=rag_result.chunks,
            prompt=rag_result.prompt,
            allow_general_knowledge=rag_result.allow_general_knowledge,
            retrieval_log_id=rag_result.retrieval_log_id,
            tool_trace=rag_result.tool_trace,
            messages=rag_result.messages,
        )

    @staticmethod
    def _debug_user_context(db: Session, user_id: int | None, topic_session_id: int | None) -> dict:
        if user_id is None:
            if topic_session_id is not None:
                raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="选择主题前请先选择模拟学员")
            return {
                "growth_context": None,
                "topic_context": None,
                "product_context": None,
                "session_summary": None,
                "summary_up_to_message_id": None,
                "history": [],
                "trace": [],
            }
        user = db.get(User, user_id)
        if user is None or user.is_deleted:
            if topic_session_id is not None:
                raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="模拟学员不存在或已删除")
            return {
                "growth_context": None,
                "topic_context": None,
                "product_context": None,
                "session_summary": None,
                "summary_up_to_message_id": None,
                "history": [],
                "trace": [
                    {
                        "tool": "load_debug_user_context",
                        "order": 1,
                        "request": {"userId": user_id},
                        "status": "failed",
                        "durationMs": 0,
                        "response": None,
                        "error": "模拟学员不存在或已删除",
                    }
                ],
            }
        entitlement = EntitlementService.active_entitlement(
            db,
            user,
            monthly_topic_used=TopicSessionService.monthly_used_count(db, user.id),
        )
        growth_context = GrowthProfileService.prompt_context(db, user) if entitlement.enable_growth_profile else None
        product_context = entitlement_prompt_context(entitlement)
        topic = None
        topic_context = None
        topic_summary_used = False
        session_summary = None
        summary_up_to_message_id = None
        history: list[ChatMessage] = []
        if topic_session_id is not None:
            topic = db.scalar(
                select(TopicSession).where(
                    TopicSession.id == topic_session_id,
                    TopicSession.user_id == user.id,
                )
            )
            if topic is None:
                raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="所选主题不属于当前模拟学员")
            history_limit = ChatContextService.message_limit(db)
            if history_limit > 0:
                latest_history = list(
                    db.scalars(
                        select(ChatMessage)
                        .where(
                            ChatMessage.topic_session_id == topic.id,
                            ChatMessage.user_id == user.id,
                            ChatMessage.role.in_(("user", "assistant")),
                        )
                        .order_by(ChatMessage.created_at.desc(), ChatMessage.id.desc())
                        .limit(history_limit)
                    )
                )
                history = list(reversed(latest_history))
            if topic.status == "active":
                chat_session = db.get(ChatSession, topic.chat_session_id)
                if chat_session is not None and chat_session.user_id == user.id:
                    session_summary = chat_session.summary
                    summary_up_to_message_id = chat_session.summary_up_to_message_id
            topic_context, topic_summary_used = TopicSessionService.prompt_context(db, topic)
        return {
            "growth_context": growth_context,
            "topic_context": topic_context,
            "product_context": product_context,
            "session_summary": session_summary,
            "summary_up_to_message_id": summary_up_to_message_id,
            "history": history,
            "trace": [
                {
                    "tool": "load_debug_user_context",
                    "order": 1,
                    "request": {"userId": user.id, "topicSessionId": topic_session_id},
                    "status": "success",
                    "durationMs": 0,
                    "response": {
                        "userId": user.id,
                        "userName": user.name,
                        "entitlement": entitlement_dict(entitlement),
                        "recentPracticeReviewUsed": bool(growth_context),
                        "productContextUsed": True,
                        "topic": {
                            "id": topic.id,
                            "title": topic.title,
                            "status": topic.status,
                            "messageCount": topic.message_count,
                            "loadedHistoryCount": len(history),
                            "summaryUsed": topic_summary_used,
                            "rollingSummaryUsed": bool(session_summary),
                        } if topic is not None else None,
                    },
                }
            ],
        }

    @staticmethod
    def overrides(payload: AgentDebugRequest) -> dict:
        return {
            "temperature": payload.temperature,
            "top_p": payload.topP,
            "top_k": payload.topK,
            "presence_penalty": payload.presencePenalty,
            "frequency_penalty": payload.frequencyPenalty,
            "max_token": payload.maxToken,
            "stream_enabled": payload.streamEnabled,
        }

    @classmethod
    async def stream(
        cls,
        payload: AgentDebugRequest,
        db: Session,
        current_admin: Admin,
    ) -> AsyncIterator[dict]:
        requested_model = db.get(ModelConfig, payload.modelId)
        if requested_model is None:
            yield {"type": "error", "message": "模型不存在"}
            return
        try:
            reasoning_visible = payload.reasoningVisible == 1
            yield {
                "type": "status",
                "stage": "retrieving",
                "message": "思考中",
                "reasoningVisible": reasoning_visible,
            }
            rag_result = await cls.build_result(db, payload)
            default_model = ModelRoutingService.default_model(db)
            chat_route = ModelRoutingService.resolve_chat(
                db,
                [chunk.knowledge_type for chunk in rag_result.chunks],
                rag_result.question,
            )
            automatic_route = default_model is not None and requested_model.id == default_model.id
            if automatic_route:
                model = chat_route.model or requested_model
                route_reason = chat_route.reason
            else:
                model = requested_model
                route_reason = "后台调试：管理员手动指定非默认模型，不执行自动模型分流"
            route_trace = {
                "tool": "model_route",
                "order": len(rag_result.tool_trace or []) + 1,
                "request": {
                    "requestedModelId": requested_model.id,
                    "automaticRoute": automatic_route,
                    "knowledgeTypes": sorted({chunk.knowledge_type for chunk in rag_result.chunks}),
                },
                "status": "success",
                "durationMs": 0,
                "response": {
                    "modelId": model.id,
                    "modelName": model.display_name or model.model_name,
                    "questionType": chat_route.question_type,
                    "routeReason": route_reason,
                },
            }
            rag_result = replace(
                rag_result,
                tool_trace=[*(rag_result.tool_trace or []), route_trace],
            )
            model_response = ModelStreamService.debug_stream_async(
                model,
                rag_result,
                cls.overrides(payload),
                fallback_model=default_model if automatic_route else None,
                route_reason=route_reason,
                question_type=chat_route.question_type,
            )
            async for segment in ReasoningPolicyService.iter_segments(model_response.chunks):
                if segment.kind == "content":
                    yield {"type": "content", "content": segment.content}
                elif reasoning_visible:
                    yield {"type": "reasoning", "content": segment.content}
            OperationLogService.write(
                db,
                admin_id=current_admin.id,
                module="agent",
                action="debug_stream",
                target_id=model_response.model_id,
            )
            db.commit()
            yield {
                "type": "complete",
                "message": "Agent 调试完成",
                "modelName": model_response.model_name,
                "retrieveCount": len(rag_result.chunks),
                "knowledgeIds": rag_result.knowledge_ids,
                "retrievalTrace": rag_result.tool_trace or [],
                "retrievalLogId": rag_result.retrieval_log_id,
                "routeReason": model_response.route_reason,
                "questionType": model_response.question_type,
            }
        except asyncio.CancelledError:
            db.rollback()
            raise
        except Exception as exc:
            db.rollback()
            yield {"type": "error", "message": str(exc) or "Agent 调试失败"}
