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.services.ai_request_log_service import AiRequestLogService
from app.services.entitlement_service import EntitlementService
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.rag_service import RagService
from app.services.topic_session_service import TopicSessionService


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:
        user = ChatService.prepare_daily_quota(db, user)
        session = ChatService._get_user_session(db, user, session_id)
        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.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_growth_profile", "status": "success", "count": 1})

            # 构建 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,
            )
            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,
                model_id=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,
            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,
        )
        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:
        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 "新聊天"
