"""gRPC-related helper functions for AIAuto client.

This module contains gRPC-specific utilities for error handling and URL parsing.
These functions are used internally by core.py.
"""

from typing import Tuple
from urllib.parse import urlparse

import grpc

# gRPC URL path parsing constants
_MIN_GRPC_PATH_PARTS = 2  # /grpc/{user_id} requires at least 2 parts

# gRPC status codes that should be retried (temporary unavailability)
_RETRYABLE_GRPC_CODES = frozenset({grpc.StatusCode.UNAVAILABLE})


class AIAutoRpcError(Exception):
    """Exception for AIAuto gRPC RPC errors with code and message.

    Wraps grpc.RpcError into a simpler exception with string code/message,
    consistent with the error interface used by core.py retry logic.
    """

    def __init__(self, code: str, message: str):
        self.code = code
        self.message = message
        super().__init__(f"[{code}] {message}")

    def is_retryable(self) -> bool:
        """Check if this error should be retried."""
        return self.code == "unavailable"

    @staticmethod
    def from_rpc_error(rpc_error: grpc.RpcError) -> "AIAutoRpcError":
        """Create AIAutoRpcError from grpc.RpcError.

        Extracts gRPC status code name and details message.
        """
        code_name = rpc_error.code().name.lower() if hasattr(rpc_error, "code") else "unknown"
        details = rpc_error.details() if hasattr(rpc_error, "details") else str(rpc_error)
        return AIAutoRpcError(code_name, details)


def _should_retry_rpc_error(exception: Exception) -> bool:
    """Check if RPC error should be retried.

    Handles both AIAutoRpcError (from core.py call_rpc wrapper) and
    raw grpc.RpcError (from direct gRPC stub calls).

    Args:
        exception: The exception to check

    Returns:
        True if the exception is a retryable RPC error, False otherwise
    """
    if isinstance(exception, AIAutoRpcError):
        return exception.is_retryable()
    if isinstance(exception, grpc.RpcError) and hasattr(exception, "code"):
        return exception.code() in _RETRYABLE_GRPC_CODES
    return False


def _parse_grpc_url(url: str) -> Tuple[str, int, str]:
    """Parse gRPC storage URL to extract host, port, and user_id.

    Supports both path-based routing (new) and legacy host:port format.

    Path-based format:
        "https://aiauto.pangyo.ainode.ai/grpc/user123"
        -> host="aiauto.pangyo.ainode.ai", port=443, user_id="user123"

    Legacy format:
        "grpc-storage.aiauto-user.svc.cluster.local:50051"
        -> host="grpc-storage.aiauto-user.svc.cluster.local", port=50051, user_id=""

    Args:
        url: gRPC storage URL (path-based or legacy host:port)

    Returns:
        Tuple of (host, port, user_id)

    Raises:
        ValueError: If URL format is invalid
    """
    if url.startswith("http://") or url.startswith("https://"):
        parsed = urlparse(url)
        host = parsed.hostname or ""
        port = parsed.port or (443 if parsed.scheme == "https" else 80)

        path_parts = [p for p in parsed.path.split("/") if p]
        if len(path_parts) >= _MIN_GRPC_PATH_PARTS and path_parts[0] == "grpc":
            user_id = path_parts[1]
        else:
            raise ValueError(
                f"Invalid gRPC URL path: {parsed.path}. Expected format: /grpc/{{user_id}}"
            )

        return host, port, user_id

    if ":" in url:
        parts = url.rsplit(":", 1)
        host = parts[0]
        try:
            port = int(parts[1])
        except ValueError as e:
            raise ValueError(f"Invalid port in gRPC URL: {url}") from e
        return host, port, ""

    raise ValueError(
        f"Invalid gRPC URL format: {url}. Expected: https://host/grpc/user_id or host:port"
    )
