"""AiService gRPC Servicer（8 RPC 实现）.

所有 RPC 返回 protobuf message，降级采用方案 B（degraded 字段在 message 内）。
业务逻辑由注入的 service 层处理，servicer 仅做 proto ↔ domain 模型转换。
"""

from collections.abc import AsyncGenerator
from typing import Any

import grpc
import structlog

from ..errors import AIError, AILLMUnavailableError, ErrorCode
from ..proto_gen import ai_pb2, ai_pb2_grpc
from .interceptors import get_user_context

logger = structlog.get_logger()


class AiServicer(ai_pb2_grpc.AiServiceServicer):
    """AiService gRPC Servicer.

    依赖注入：
      - chat_service: ChatService（Chat / StreamChat）
      - question_service: QuestionService（GenerateQuestion / StreamGenerateQuestion）
      - expression_service: ExpressionService（OptimizeExpression）
      - workflow_service: LessonPlanWorkflowService（备课工作流）
    """

    def __init__(
        self,
        chat_service: Any = None,
        question_service: Any = None,
        expression_service: Any = None,
        workflow_service: Any = None,
    ) -> None:
        self._chat_service = chat_service
        self._question_service = question_service
        self._expression_service = expression_service
        self._workflow_service = workflow_service

    async def Chat(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.ChatRequest,
        context: grpc.ServicerContext,
    ) -> ai_pb2.ChatResponse:
        """非流式聊天."""
        ctx = get_user_context(context)
        messages = [
            {"role": m.role, "content": m.content}
            for m in request.messages
        ]
        try:
            if self._chat_service is None:
                return _degraded_chat_response(request.model, "chat_service not initialized")
            result = await self._chat_service.chat(
                messages=messages,
                model=request.model or "gpt-4o-mini",
                temperature=request.temperature,
                user_id=request.user_id if request.HasField("user_id") else ctx.user_id,
                session_id=request.session_id if request.HasField("session_id") else None,
                data_scope=request.data_scope if request.HasField("data_scope") else ctx.data_scope,
            )
            return ai_pb2.ChatResponse(
                content=result.content,
                model=result.model,
                usage=ai_pb2.Usage(
                    prompt_tokens=result.usage.prompt_tokens,
                    completion_tokens=result.usage.completion_tokens,
                    total_tokens=result.usage.total_tokens,
                    latency_ms=result.usage.latency_ms,
                ),
                degraded=result.degraded,
                degraded_reason=result.degraded_reason,
            )
        except AILLMUnavailableError as exc:
            return _degraded_chat_response(request.model, str(exc))
        except AIError:
            raise
        except Exception as exc:  # noqa: BLE001
            logger.error("chat_rpc_error", error=str(exc))
            raise AIError(ErrorCode.AI_INTERNAL_ERROR, str(exc)) from exc

    async def StreamChat(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.ChatRequest,
        context: grpc.ServicerContext,
    ) -> AsyncGenerator[ai_pb2.ChatChunk, None]:
        """流式聊天（SSE over gRPC）."""
        ctx = get_user_context(context)
        messages = [
            {"role": m.role, "content": m.content}
            for m in request.messages
        ]
        try:
            if self._chat_service is None:
                yield ai_pb2.ChatChunk(content="[degraded] chat_service not initialized", done=True)
                return
            async for chunk in self._chat_service.stream_chat(
                messages=messages,
                model=request.model or "gpt-4o-mini",
                temperature=request.temperature,
                user_id=request.user_id if request.HasField("user_id") else ctx.user_id,
                session_id=request.session_id if request.HasField("session_id") else None,
                data_scope=request.data_scope if request.HasField("data_scope") else ctx.data_scope,
            ):
                yield ai_pb2.ChatChunk(content=chunk.content, done=chunk.done)
        except AILLMUnavailableError as exc:
            yield ai_pb2.ChatChunk(content=f"[degraded] {exc}", done=True)
        except Exception as exc:  # noqa: BLE001
            logger.error("stream_chat_rpc_error", error=str(exc))
            yield ai_pb2.ChatChunk(content=f"[error] {exc}", done=True)

    async def GenerateQuestion(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.GenerateQuestionRequest,
        context: grpc.ServicerContext,
    ) -> ai_pb2.GeneratedQuestion:
        """生成题目."""
        try:
            if self._question_service is None:
                return _degraded_question_response("question_service not initialized")
            result = await self._question_service.generate(request)
            return ai_pb2.GeneratedQuestion(
                question=result.question,
                answer=result.answer,
                explanation=result.explanation,
                question_type=result.question_type,
                difficulty=result.difficulty,
                knowledge_point_ids=result.knowledge_point_ids,
                evaluation_score=result.evaluation_score,
                degraded=result.degraded,
                degraded_reason=result.degraded_reason,
            )
        except AILLMUnavailableError as exc:
            return _degraded_question_response(str(exc))
        except AIError:
            raise
        except Exception as exc:  # noqa: BLE001
            logger.error("generate_question_rpc_error", error=str(exc))
            raise AIError(ErrorCode.AI_INTERNAL_ERROR, str(exc)) from exc

    async def StreamGenerateQuestion(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.GenerateQuestionRequest,
        context: grpc.ServicerContext,
    ) -> AsyncGenerator[ai_pb2.GeneratedQuestionChunk, None]:
        """题目逐字流式生成."""
        try:
            if self._question_service is None:
                yield ai_pb2.GeneratedQuestionChunk(
                    content="[degraded] question_service not initialized",
                    done=True,
                )
                return
            async for chunk in self._question_service.stream_generate(request):
                if chunk.complete_question is not None:
                    yield ai_pb2.GeneratedQuestionChunk(
                        content=chunk.content,
                        done=chunk.done,
                        complete_question=chunk.complete_question,
                    )
                else:
                    yield ai_pb2.GeneratedQuestionChunk(
                        content=chunk.content,
                        done=chunk.done,
                    )
        except AILLMUnavailableError as exc:
            yield ai_pb2.GeneratedQuestionChunk(
                content=f"[degraded] {exc}",
                done=True,
            )
        except Exception as exc:  # noqa: BLE001
            logger.error("stream_generate_question_rpc_error", error=str(exc))
            yield ai_pb2.GeneratedQuestionChunk(
                content=f"[error] {exc}",
                done=True,
            )

    async def OptimizeExpression(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.OptimizeExpressionRequest,
        context: grpc.ServicerContext,
    ) -> ai_pb2.OptimizedExpression:
        """优化表达."""
        try:
            if self._expression_service is None:
                return ai_pb2.OptimizedExpression(
                    optimized="[degraded] expression_service not initialized",
                    degraded=True,
                    degraded_reason="expression_service not initialized",
                )
            result = await self._expression_service.optimize(
                text=request.text,
                context=request.context,
            )
            return ai_pb2.OptimizedExpression(
                optimized=result.optimized,
                suggestions=result.suggestions,
                degraded=result.degraded,
                degraded_reason=result.degraded_reason,
            )
        except AILLMUnavailableError as exc:
            return ai_pb2.OptimizedExpression(
                optimized=f"[degraded] {exc}",
                degraded=True,
                degraded_reason=str(exc),
            )
        except AIError:
            raise
        except Exception as exc:  # noqa: BLE001
            logger.error("optimize_expression_rpc_error", error=str(exc))
            raise AIError(ErrorCode.AI_INTERNAL_ERROR, str(exc)) from exc

    async def GenerateLessonPlan(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.GenerateLessonPlanRequest,
        context: grpc.ServicerContext,
    ) -> ai_pb2.LessonPlanResponse:
        """备课工作流启动."""
        ctx = get_user_context(context)
        try:
            if self._workflow_service is None:
                return ai_pb2.LessonPlanResponse(
                    workflow_id="",
                    status="failed",
                    estimated_completion_seconds=0,
                    degraded=True,
                    degraded_reason="workflow_service not initialized",
                )
            result = await self._workflow_service.start(
                class_id=request.class_id,
                subject_id=request.subject_id,
                topic=request.topic,
                target_difficulty=request.target_difficulty,
                question_count=request.question_count,
                user_id=request.user_id or ctx.user_id,
                data_scope=request.data_scope or ctx.data_scope,
            )
            return ai_pb2.LessonPlanResponse(
                workflow_id=result.workflow_id,
                status=result.status,
                estimated_completion_seconds=result.estimated_completion_seconds,
                degraded=result.degraded,
                degraded_reason=result.degraded_reason,
            )
        except AIError:
            raise
        except Exception as exc:  # noqa: BLE001
            logger.error("generate_lesson_plan_rpc_error", error=str(exc))
            raise AIError(ErrorCode.AI_INTERNAL_ERROR, str(exc)) from exc

    async def GetLessonPlanStatus(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.GetLessonPlanStatusRequest,
        context: grpc.ServicerContext,
    ) -> ai_pb2.LessonPlanStatus:
        """查询备课工作流状态."""
        try:
            if self._workflow_service is None:
                return ai_pb2.LessonPlanStatus(
                    workflow_id=request.workflow_id,
                    status="failed",
                    error="workflow_service not initialized",
                    degraded=True,
                    degraded_reason="workflow_service not initialized",
                )
            result = await self._workflow_service.get_status(request.workflow_id)
            questions = [
                ai_pb2.GeneratedQuestion(
                    question=q.question,
                    answer=q.answer,
                    explanation=q.explanation,
                    question_type=q.question_type,
                    difficulty=q.difficulty,
                    knowledge_point_ids=q.knowledge_point_ids,
                    evaluation_score=q.evaluation_score,
                    degraded=q.degraded,
                    degraded_reason=q.degraded_reason,
                )
                for q in result.questions
            ]
            return ai_pb2.LessonPlanStatus(
                workflow_id=result.workflow_id,
                status=result.status,
                questions=questions,
                error=result.error,
                degraded=result.degraded,
                degraded_reason=result.degraded_reason,
            )
        except AIError:
            raise
        except Exception as exc:  # noqa: BLE001
            logger.error("get_lesson_plan_status_rpc_error", error=str(exc))
            raise AIError(ErrorCode.AI_INTERNAL_ERROR, str(exc)) from exc

    async def ConfirmLessonPlan(  # noqa: N802 - gRPC RPC 方法名必须匹配 proto 定义
        self,
        request: ai_pb2.ConfirmLessonPlanRequest,
        context: grpc.ServicerContext,
    ) -> ai_pb2.ConfirmResult:
        """教师确认备课结果入库."""
        try:
            if self._workflow_service is None:
                return ai_pb2.ConfirmResult(
                    success=False,
                    error="workflow_service not initialized",
                )
            modifications = dict(request.modifications)
            result = await self._workflow_service.confirm(
                workflow_id=request.workflow_id,
                modifications=modifications,
            )
            return ai_pb2.ConfirmResult(
                success=result.success,
                persisted_question_ids=result.persisted_question_ids,
                error=result.error,
            )
        except AIError:
            raise
        except Exception as exc:  # noqa: BLE001
            logger.error("confirm_lesson_plan_rpc_error", error=str(exc))
            raise AIError(ErrorCode.AI_INTERNAL_ERROR, str(exc)) from exc


def _degraded_chat_response(model: str, reason: str) -> ai_pb2.ChatResponse:
    """构建降级聊天响应."""
    return ai_pb2.ChatResponse(
        content=f"[degraded] {reason}",
        model=model,
        usage=ai_pb2.Usage(),
        degraded=True,
        degraded_reason=reason,
    )


def _degraded_question_response(reason: str) -> ai_pb2.GeneratedQuestion:
    """构建降级题目响应."""
    return ai_pb2.GeneratedQuestion(
        question=f"[degraded] {reason}",
        answer="",
        explanation="",
        question_type="",
        difficulty="",
        degraded=True,
        degraded_reason=reason,
    )
