"""Data-ana 服务 gRPC 客户端.

用于：
  - 查询班级学情（GetClassPerformance）
  - 查询学生薄弱点（GetStudentWeakness）
  - 查询学习趋势（GetLearningTrend）

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

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

import structlog

logger = structlog.get_logger()


@dataclass
class StudentScore:
    """学生成绩."""

    student_id: str
    score: float
    grade: str


@dataclass
class ClassPerformance:
    """班级学情."""

    class_id: str
    average_score: float
    pass_rate: float
    scores: list[StudentScore] = field(default_factory=list)


@dataclass
class WeakPoint:
    """薄弱知识点."""

    knowledge_point_id: str
    title: str
    mastery: float


@dataclass
class StudentWeakness:
    """学生薄弱点."""

    student_id: str
    weak_points: list[WeakPoint] = field(default_factory=list)


@dataclass
class TrendPoint:
    """趋势数据点."""

    date: int
    score: float


@dataclass
class LearningTrend:
    """学习趋势."""

    student_id: str
    points: list[TrendPoint] = field(default_factory=list)


class DataAnaClient(ABC):
    """Data-ana 服务客户端抽象接口（六边形端口）."""

    @abstractmethod
    async def get_class_performance(
        self,
        class_id: str,
        subject_id: str,
        start_date: int = 0,
        end_date: int = 0,
    ) -> ClassPerformance:
        """查询班级学情."""
        ...

    @abstractmethod
    async def get_student_weakness(
        self,
        student_id: str,
        subject_id: str,
    ) -> StudentWeakness:
        """查询学生薄弱点."""
        ...

    @abstractmethod
    async def get_learning_trend(
        self,
        student_id: str,
        start_date: int = 0,
        end_date: int = 0,
    ) -> LearningTrend:
        """查询学习趋势."""
        ...

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


class DataAnaClientMock(DataAnaClient):
    """Data-ana 客户端 Mock 实现（全并行模式）."""

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

    async def get_class_performance(
        self,
        class_id: str,
        subject_id: str,
        start_date: int = 0,
        end_date: int = 0,
    ) -> ClassPerformance:
        logger.info(
            "data_ana_mock_class_performance",
            class_id=class_id,
            subject_id=subject_id,
        )
        return ClassPerformance(
            class_id=class_id,
            average_score=78.5,
            pass_rate=0.85,
            scores=[
                StudentScore(student_id="s_001", score=85.0, grade="A"),
                StudentScore(student_id="s_002", score=72.0, grade="B"),
                StudentScore(student_id="s_003", score=65.0, grade="C"),
            ],
        )

    async def get_student_weakness(
        self,
        student_id: str,
        subject_id: str,
    ) -> StudentWeakness:
        logger.info(
            "data_ana_mock_student_weakness",
            student_id=student_id,
            subject_id=subject_id,
        )
        return StudentWeakness(
            student_id=student_id,
            weak_points=[
                WeakPoint(
                    knowledge_point_id="kp_001",
                    title="函数概念（mock）",
                    mastery=0.45,
                ),
                WeakPoint(
                    knowledge_point_id="kp_005",
                    title="三角函数（mock）",
                    mastery=0.52,
                ),
            ],
        )

    async def get_learning_trend(
        self,
        student_id: str,
        start_date: int = 0,
        end_date: int = 0,
    ) -> LearningTrend:
        logger.info(
            "data_ana_mock_learning_trend",
            student_id=student_id,
        )
        return LearningTrend(
            student_id=student_id,
            points=[
                TrendPoint(date=20260101, score=65.0),
                TrendPoint(date=20260201, score=70.0),
                TrendPoint(date=20260301, score=75.0),
            ],
        )

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


class DataAnaClientGrpc(DataAnaClient):
    """Data-ana 服务 gRPC 客户端实现.

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

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

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

        self._channel = grpc.aio.insecure_channel(self._endpoint)
        logger.info("data_ana_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_class_performance(
        self,
        class_id: str,
        subject_id: str,
        start_date: int = 0,
        end_date: int = 0,
    ) -> ClassPerformance:
        if not self.is_available():
            return await self._mock.get_class_performance(
                class_id, subject_id, start_date, end_date,
            )
        try:
            # 全并行模式：proto 未生成时降级到 mock
            return await self._mock.get_class_performance(
                class_id, subject_id, start_date, end_date,
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "data_ana_class_performance_failed_degraded",
                error=str(exc),
            )
            return await self._mock.get_class_performance(
                class_id, subject_id, start_date, end_date,
            )

    async def get_student_weakness(
        self,
        student_id: str,
        subject_id: str,
    ) -> StudentWeakness:
        if not self.is_available():
            return await self._mock.get_student_weakness(student_id, subject_id)
        try:
            return await self._mock.get_student_weakness(student_id, subject_id)
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "data_ana_student_weakness_failed_degraded",
                error=str(exc),
            )
            return await self._mock.get_student_weakness(student_id, subject_id)

    async def get_learning_trend(
        self,
        student_id: str,
        start_date: int = 0,
        end_date: int = 0,
    ) -> LearningTrend:
        if not self.is_available():
            return await self._mock.get_learning_trend(
                student_id, start_date, end_date,
            )
        try:
            return await self._mock.get_learning_trend(
                student_id, start_date, end_date,
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "data_ana_learning_trend_failed_degraded",
                error=str(exc),
            )
            return await self._mock.get_learning_trend(
                student_id, start_date, end_date,
            )
