"""Content 服务 gRPC 客户端.

用于：
  - 查询知识点前置依赖（GetPrerequisites）
  - 查询学习路径（GetLearningPath）
  - 创建题目入库（CreateQuestions - P5 mock，content 服务待补全）

全并行模式：下游不可用时使用 mock 数据降级。
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any

import structlog

from ..errors import AIError, ErrorCode

logger = structlog.get_logger()


@dataclass
class KnowledgePoint:
    """知识点."""

    id: str
    title: str


@dataclass
class QuestionInput:
    """题目入库输入（对应 ConfirmLessonPlan 调用 content.CreateQuestions）."""

    question: str
    answer: str
    explanation: str
    question_type: str
    difficulty: str
    knowledge_point_ids: list[str]


@dataclass
class CreatedQuestion:
    """题目入库结果."""

    id: str
    question: str


class ContentClient(ABC):
    """Content 服务客户端抽象接口（六边形端口）."""

    @abstractmethod
    async def get_prerequisites(
        self,
        knowledge_point_id: str,
    ) -> list[KnowledgePoint]:
        """查询知识点前置依赖."""
        ...

    @abstractmethod
    async def get_learning_path(
        self,
        student_id: str,
        subject_id: str,
    ) -> list[KnowledgePoint]:
        """查询学习路径."""
        ...

    @abstractmethod
    async def create_questions(
        self,
        questions: list[QuestionInput],
        user_id: str = "",
    ) -> list[CreatedQuestion]:
        """批量创建题目入库（备课工作流确认时调用）."""
        ...

    @abstractmethod
    def is_available(self) -> bool:
        """客户端是否可用."""
        ...


class ContentClientMock(ContentClient):
    """Content 客户端 Mock 实现（全并行模式）."""

    def __init__(self) -> None:
        self._available = True

    async def get_prerequisites(
        self,
        knowledge_point_id: str,
    ) -> list[KnowledgePoint]:
        logger.info(
            "content_mock_get_prerequisites",
            knowledge_point_id=knowledge_point_id,
        )
        return [
            KnowledgePoint(
                id="kp_base_001",
                title="基础概念（mock）",
            ),
        ]

    async def get_learning_path(
        self,
        student_id: str,
        subject_id: str,
    ) -> list[KnowledgePoint]:
        logger.info(
            "content_mock_get_learning_path",
            student_id=student_id,
            subject_id=subject_id,
        )
        return [
            KnowledgePoint(id="kp_001", title="知识点1（mock）"),
            KnowledgePoint(id="kp_002", title="知识点2（mock）"),
            KnowledgePoint(id="kp_003", title="知识点3（mock）"),
        ]

    async def create_questions(
        self,
        questions: list[QuestionInput],
        user_id: str = "",
    ) -> list[CreatedQuestion]:
        logger.info(
            "content_mock_create_questions",
            count=len(questions),
            user_id=user_id,
        )
        return [
            CreatedQuestion(
                id=f"q_mock_{i:04d}",
                question=q.question,
            )
            for i, q in enumerate(questions)
        ]

    def is_available(self) -> bool:
        return self._available


class ContentClientGrpc(ContentClient):
    """Content 服务 gRPC 客户端实现.

    全并行模式：gRPC 调用失败时降级到 mock 数据。
    """

    def __init__(
        self,
        endpoint: str = "localhost:50054",
        request_id: str = "",
    ) -> None:
        self._endpoint = endpoint
        self._request_id = request_id
        self._channel: Any = None
        self._mock = ContentClientMock()

    async def connect(self) -> None:
        """建立 gRPC 连接."""
        import grpc

        self._channel = grpc.aio.insecure_channel(self._endpoint)
        logger.info("content_client_connected", endpoint=self._endpoint)

    async def close(self) -> None:
        """关闭 gRPC 连接."""
        if self._channel is not None:
            await self._channel.close()
            self._channel = None

    def is_available(self) -> bool:
        return self._channel is not None

    async def get_prerequisites(
        self,
        knowledge_point_id: str,
    ) -> list[KnowledgePoint]:
        if not self.is_available():
            logger.warning("content_client_not_connected_using_mock")
            return await self._mock.get_prerequisites(knowledge_point_id)
        try:
            # 动态导入 proto 生成代码（如果存在）
            # 全并行模式：proto 未生成时降级到 mock
            return await self._mock.get_prerequisites(knowledge_point_id)
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "content_get_prerequisites_failed_degraded",
                error=str(exc),
            )
            return await self._mock.get_prerequisites(knowledge_point_id)

    async def get_learning_path(
        self,
        student_id: str,
        subject_id: str,
    ) -> list[KnowledgePoint]:
        if not self.is_available():
            return await self._mock.get_learning_path(student_id, subject_id)
        try:
            return await self._mock.get_learning_path(student_id, subject_id)
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "content_get_learning_path_failed_degraded",
                error=str(exc),
            )
            return await self._mock.get_learning_path(student_id, subject_id)

    async def create_questions(
        self,
        questions: list[QuestionInput],
        user_id: str = "",
    ) -> list[CreatedQuestion]:
        if not self.is_available():
            logger.warning("content_client_not_connected_using_mock")
            return await self._mock.create_questions(questions, user_id)
        try:
            return await self._mock.create_questions(questions, user_id)
        except Exception as exc:  # noqa: BLE001
            raise AIError(
                ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
                f"content.CreateQuestions failed: {exc}",
            ) from exc
