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


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()
        try:
            rag_result = RagService.build_result(db, user, normalized_question)
            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=normalized_question,
                knowledge_ids=None,
                retrieve_count=0,
                cost_ms=cost_ms,
                error_message=str(exc),
            )
            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,
        )
        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 _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 "新聊天"
