"""gRPC 客户端基类 + 拦截器.

提供：
  - LoggingInterceptor: 客户端请求日志
  - TracingInterceptor: 注入 traceparent 到 metadata
  - BaseGrpcClient: 通道管理 + 优雅关闭
"""

from abc import ABC, abstractmethod
from typing import Any

import grpc
import structlog

logger = structlog.get_logger()


class LoggingInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor,
    grpc.aio.UnaryStreamClientInterceptor,
):
    """客户端日志拦截器."""

    async def intercept_unary_unary(
        self,
        continuation: Any,
        client_call_details: grpc.aio.ClientCallDetails,
        request: Any,
    ) -> Any:
        method = client_call_details.method
        logger.debug("grpc_client_call", method=method)
        try:
            response = await continuation(client_call_details, request)
            logger.debug("grpc_client_success", method=method)
            return response
        except grpc.aio.AioRpcError as exc:
            logger.warning(
                "grpc_client_error",
                method=method,
                code=exc.code().name,
                details=exc.details(),
            )
            raise

    async def intercept_unary_stream(
        self,
        continuation: Any,
        client_call_details: grpc.aio.ClientCallDetails,
        request: Any,
    ) -> Any:
        method = client_call_details.method
        logger.debug("grpc_client_stream_call", method=method)
        try:
            async for response in await continuation(
                client_call_details, request,
            ):
                yield response
        except grpc.aio.AioRpcError as exc:
            logger.warning(
                "grpc_client_stream_error",
                method=method,
                code=exc.code().name,
                details=exc.details(),
            )
            raise


class TracingInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor,
    grpc.aio.UnaryStreamClientInterceptor,
):
    """客户端链路追踪拦截器（注入 traceparent）."""

    def __init__(self, request_id: str = "") -> None:
        self._request_id = request_id

    def _inject_metadata(
        self,
        metadata: list[tuple[str, str]] | None,
    ) -> list[tuple[str, str]]:
        """注入 traceparent + x-request-id."""
        if metadata is None:
            metadata = []
        metadata = list(metadata)
        if self._request_id:
            metadata.append(("x-request-id", self._request_id))
            metadata.append(
                ("traceparent", f"00-{self._request_id}-0000000000000000-01"),
            )
        return metadata

    async def intercept_unary_unary(
        self,
        continuation: Any,
        client_call_details: grpc.aio.ClientCallDetails,
        request: Any,
    ) -> Any:
        client_call_details.metadata = self._inject_metadata(
            client_call_details.metadata,
        )
        return await continuation(client_call_details, request)

    async def intercept_unary_stream(
        self,
        continuation: Any,
        client_call_details: grpc.aio.ClientCallDetails,
        request: Any,
    ) -> Any:
        client_call_details.metadata = self._inject_metadata(
            client_call_details.metadata,
        )
        return await continuation(client_call_details, request)


class BaseGrpcClient(ABC):
    """gRPC 客户端基类."""

    def __init__(self, endpoint: str, request_id: str = "") -> None:
        self._endpoint = endpoint
        self._channel: grpc.aio.Channel | None = None
        self._interceptors: list[Any] = [
            LoggingInterceptor(),
            TracingInterceptor(request_id),
        ]

    async def connect(self) -> None:
        """建立 gRPC 连接."""
        if self._channel is not None:
            return
        self._channel = grpc.aio.insecure_channel(
            self._endpoint,
            interceptors=self._interceptors,
        )
        logger.info("grpc_client_connected", endpoint=self._endpoint)

    async def close(self) -> None:
        """关闭 gRPC 连接."""
        if self._channel is not None:
            await self._channel.close()
            self._channel = None
            logger.info("grpc_client_closed", endpoint=self._endpoint)

    @property
    def channel(self) -> grpc.aio.Channel:
        """获取 gRPC channel（未连接时自动建立）."""
        if self._channel is None:
            raise RuntimeError("gRPC channel not connected, call connect() first")
        return self._channel

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