from __future__ import annotations

from datetime import UTC, datetime
from time import perf_counter
from zoneinfo import ZoneInfo

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.core.auth_context import ChatAccessScope
from app.services.ai_request_log_service import AiRequestLogService
from app.services.entitlement_service import EntitlementService, entitlement_prompt_context
from app.services.external_errors import ExternalServiceError
from app.services.growth_profile_service import GrowthProfileService
from app.services.chat_context_service import ChatContextService
from app.services.model_service import ModelClientService
from app.services.model_routing_service import ModelRoutingService
from app.services.rag_service import RagService
from app.services.topic_session_service import TopicSessionService


class ChatService:
    @staticmethod
    def create_session(
        db: Session,
        user: User,
        scope: ChatAccessScope | None = None,
    ) -> ChatSession:
        scope = scope or ChatAccessScope.direct()
        now = _now()
        session = ChatSession(
            user_id=user.id,
            source_type=scope.source_type,
            source_client_id=scope.source_client_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,
        scope: ChatAccessScope | None = None,
    ) -> list[ChatSession]:
        scope = scope or ChatAccessScope.direct()
        return list(
            db.scalars(
                select(ChatSession)
                .where(
                    ChatSession.user_id == user.id,
                    ChatSession.is_deleted == 0,
                    *chat_scope_filters(scope),
                )
                .order_by(ChatSession.updated_at.desc())
            )
        )

    @staticmethod
    def get_history(
        db: Session,
        user: User,
        session_id: int,
        scope: ChatAccessScope | None = None,
    ) -> list[ChatMessage]:
        session = ChatService._get_user_session(db, user, session_id, scope)
        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,
        scope: ChatAccessScope | None = None,
    ) -> ChatSession:
        session = ChatService._get_user_session(db, user, session_id, scope)
        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,
        scope: ChatAccessScope | None = None,
    ) -> None:
        session = ChatService._get_user_session(db, user, session_id, scope)
        session.is_deleted = 1
        db.add(session)
        db.commit()

    @staticmethod
    def create_answer(
        db: Session,
        user: User,
        session_id: int,
        question: str,
        scope: ChatAccessScope | None = None,
    ) -> str:
        user = ChatService.prepare_daily_quota(db, user)
        session = ChatService._get_user_session(db, user, session_id, scope)
        ChatService._ensure_quota(user)
        entitlement = EntitlementService.active_entitlement(
            db,
            user,
            monthly_topic_used=TopicSessionService.monthly_used_count(db, user.id),
        )
        ChatService._ensure_topic_quota(db, user, session, entitlement)

        now = _now()
        normalized_question = question.strip()
        topic = TopicSessionService.get_or_create_active(
            db,
            user=user,
            session=session,
            question=normalized_question,
            deduct_quota=entitlement.deduct_quota,
        )
        user_message = ChatMessage(
            session_id=session.id,
            topic_session_id=topic.id,
            user_id=user.id,
            role="user",
            content=normalized_question,
            message_status="FINISHED",
            created_at=now,
        )
        db.add(user_message)
        TopicSessionService.attach_user_message(user_message, topic)
        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.topic_session_id == topic.id,
                        ChatMessage.user_id == user.id,
                        ChatMessage.id < user_message.id,
                    )
                    .order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
                )
            )

            summary_result = ChatContextService.update_summary(db, session, history)
            context_trace = [summary_result.trace] if summary_result.trace else None
            growth_context = GrowthProfileService.prompt_context(db, user) if entitlement.enable_growth_profile else None
            if growth_context:
                context_trace = list(context_trace or [])
                context_trace.append({"tool": "load_recent_practice_review", "status": "success", "count": 1})
            topic_context, topic_summary_used = TopicSessionService.prompt_context(db, topic)
            product_context = entitlement_prompt_context(entitlement)
            context_trace = list(context_trace or [])
            context_trace.extend(
                [
                    {
                        "tool": "load_topic_context",
                        "status": "success",
                        "response": {"topicSessionId": topic.id, "summaryUsed": topic_summary_used},
                    },
                    {
                        "tool": "load_product_entitlement",
                        "status": "success",
                        "response": {
                            "planId": entitlement.plan_id,
                            "allowHelpCard": entitlement.allow_help_card,
                            "allowShareDraft": entitlement.allow_share_draft,
                        },
                    },
                ]
            )

            # 构建 prompt（传入历史 + 摘要）
            rag_result = RagService.build_result(
                db, user, normalized_question,
                history=history,
                session_summary=session.summary,
                summary_up_to_message_id=session.summary_up_to_message_id,
                context_trace=context_trace,
                growth_context=growth_context,
                topic_context=topic_context,
                product_context=product_context,
            )
            completion = ModelClientService.complete(db, rag_result)
        except ExternalServiceError as exc:
            cost_ms = int((perf_counter() - started_at) * 1000)
            failed_route = ModelRoutingService.resolve_chat(
                db,
                [chunk.knowledge_type for chunk in rag_result.chunks] if rag_result is not None else [],
                rag_result.question if rag_result is not None else normalized_question,
            )
            AiRequestLogService.write_failed(
                db,
                session_id=session.id,
                message_id=user_message.id,
                user_id=user.id,
                model_name=getattr(
                    exc,
                    "model_name",
                    failed_route.model.model_name if failed_route.model is not None else 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,
                model_id=getattr(
                    exc,
                    "model_id",
                    failed_route.model.id if failed_route.model is not None else None,
                ),
                route_reason=getattr(exc, "route_reason", failed_route.reason),
                question_type=getattr(exc, "question_type", failed_route.question_type),
            )
            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,
            topic_session_id=topic.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)
        TopicSessionService.attach_assistant_message(
            assistant_message,
            topic,
            token_input=completion.input_token,
            token_output=completion.output_token,
        )
        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,
            model_id=completion.model_id,
            route_reason=completion.route_reason,
            question_type=completion.question_type,
        )
        db.commit()
        return completion.answer

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

    @staticmethod
    def _get_user_session(
        db: Session,
        user: User,
        session_id: int,
        scope: ChatAccessScope | None = None,
    ) -> ChatSession:
        scope = scope or ChatAccessScope.direct()
        session = db.scalar(
            select(ChatSession).where(
                ChatSession.id == session_id,
                ChatSession.user_id == user.id,
                ChatSession.is_deleted == 0,
                *chat_scope_filters(scope),
            )
        )
        if session is None:
            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:
        ChatContextService.update_summary(db, session, history)

    @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="今天的陪伴对话已经比较多了，建议先消化当前回答和功课；如需继续使用，可以联系运营老师确认权益。",
            )

    @staticmethod
    def _ensure_topic_quota(db: Session, user: User, session: ChatSession, entitlement) -> None:
        if not entitlement.deduct_quota or entitlement.monthly_topic_limit is None:
            return
        if TopicSessionService.active_for_session(db, user=user, session=session) is not None:
            return
        if entitlement.monthly_topic_used >= entitlement.monthly_topic_limit:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="本月深度主题使用较多，建议先完成已有功课；如需继续高频使用，可以联系运营老师确认权益。",
            )

    @staticmethod
    def prepare_daily_quota(db: Session, user: User) -> User:
        locked_user = db.scalar(select(User).where(User.id == user.id).with_for_update())
        if locked_user is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户不存在")

        today = datetime.now(ZoneInfo("Asia/Shanghai")).date()
        if locked_user.daily_chat_reset_date != today:
            locked_user.daily_chat_used = 0
            locked_user.daily_chat_reset_date = today
            db.add(locked_user)
            db.flush()
        return locked_user


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 "新聊天"


def chat_scope_filters(scope: ChatAccessScope) -> tuple:
    if scope.source_type == "direct":
        return (
            ChatSession.source_type == "direct",
            ChatSession.source_client_id.is_(None),
        )
    return (
        ChatSession.source_type == "sso",
        ChatSession.source_client_id == scope.source_client_id,
    )
