"""Tests for grpc_helper module: AIAutoRpcError, _should_retry_rpc_error, _parse_grpc_url."""

import grpc
import pytest

from aiauto.grpc_helper import AIAutoRpcError, _parse_grpc_url, _should_retry_rpc_error


class FakeRpcError(grpc.RpcError):
    """Fake grpc.RpcError with code() and details() for testing."""

    def __init__(self, status_code, details_msg=""):
        self._code = status_code
        self._details = details_msg

    def code(self):
        return self._code

    def details(self):
        return self._details


class TestAIAutoRpcError:
    """Test AIAutoRpcError class."""

    def test_init_sets_code_and_message(self):
        err = AIAutoRpcError("not_found", "resource missing")
        assert err.code == "not_found"
        assert err.message == "resource missing"
        assert "[not_found]" in str(err)
        assert "resource missing" in str(err)

    def test_is_retryable_unavailable(self):
        err = AIAutoRpcError("unavailable", "service down")
        assert err.is_retryable() is True

    def test_is_retryable_not_found(self):
        err = AIAutoRpcError("not_found", "gone")
        assert err.is_retryable() is False

    def test_is_retryable_internal(self):
        err = AIAutoRpcError("internal", "crash")
        assert err.is_retryable() is False

    def test_from_rpc_error(self):
        rpc_err = FakeRpcError(grpc.StatusCode.NOT_FOUND, "study not found")
        result = AIAutoRpcError.from_rpc_error(rpc_err)
        assert result.code == "not_found"
        assert result.message == "study not found"

    def test_from_rpc_error_unavailable(self):
        rpc_err = FakeRpcError(grpc.StatusCode.UNAVAILABLE, "service unavailable")
        result = AIAutoRpcError.from_rpc_error(rpc_err)
        assert result.code == "unavailable"
        assert result.is_retryable() is True

    def test_inherits_exception(self):
        err = AIAutoRpcError("internal", "boom")
        assert isinstance(err, Exception)

    def test_from_rpc_error_no_code_method(self):
        """grpc.RpcError without code() method falls back to 'unknown'."""

        class BareRpcError(grpc.RpcError):
            pass

        rpc_err = BareRpcError()
        result = AIAutoRpcError.from_rpc_error(rpc_err)
        assert result.code == "unknown"

    def test_from_rpc_error_no_details_method(self):
        """grpc.RpcError without details() falls back to str(error)."""

        class CodeOnlyRpcError(grpc.RpcError):
            def code(self):
                return grpc.StatusCode.INTERNAL

        rpc_err = CodeOnlyRpcError()
        result = AIAutoRpcError.from_rpc_error(rpc_err)
        assert result.code == "internal"
        # Falls back to str(rpc_err) since no details()
        assert isinstance(result.message, str)


class TestShouldRetryRpcError:
    """Test _should_retry_rpc_error function."""

    def test_aiauto_rpc_error_unavailable(self):
        err = AIAutoRpcError("unavailable", "service down")
        assert _should_retry_rpc_error(err) is True

    def test_aiauto_rpc_error_not_found(self):
        err = AIAutoRpcError("not_found", "gone")
        assert _should_retry_rpc_error(err) is False

    def test_grpc_rpc_error_unavailable(self):
        rpc_err = FakeRpcError(grpc.StatusCode.UNAVAILABLE)
        assert _should_retry_rpc_error(rpc_err) is True

    def test_grpc_rpc_error_not_found(self):
        rpc_err = FakeRpcError(grpc.StatusCode.NOT_FOUND)
        assert _should_retry_rpc_error(rpc_err) is False

    def test_runtime_error_not_retryable(self):
        assert _should_retry_rpc_error(RuntimeError("boom")) is False

    def test_value_error_not_retryable(self):
        assert _should_retry_rpc_error(ValueError("bad")) is False

    def test_grpc_rpc_error_without_code_not_retryable(self):
        """grpc.RpcError without code() attribute is not retryable."""

        class BareRpcError(grpc.RpcError):
            pass

        assert _should_retry_rpc_error(BareRpcError()) is False

    def test_grpc_rpc_error_internal_not_retryable(self):
        """grpc.RpcError with INTERNAL code is not retryable (only UNAVAILABLE is)."""
        rpc_err = FakeRpcError(grpc.StatusCode.INTERNAL)
        assert _should_retry_rpc_error(rpc_err) is False

    def test_grpc_rpc_error_deadline_exceeded_not_retryable(self):
        """DEADLINE_EXCEEDED is not in _RETRYABLE_GRPC_CODES."""
        rpc_err = FakeRpcError(grpc.StatusCode.DEADLINE_EXCEEDED)
        assert _should_retry_rpc_error(rpc_err) is False


class TestParseGrpcUrl:
    """Test _parse_grpc_url function."""

    def test_https_path_based(self):
        host, port, user_id = _parse_grpc_url("https://aiauto.pangyo.ainode.ai/grpc/user123")
        assert host == "aiauto.pangyo.ainode.ai"
        assert port == 443
        assert user_id == "user123"

    def test_http_path_based(self):
        host, port, user_id = _parse_grpc_url("http://localhost/grpc/test-user")
        assert host == "localhost"
        assert port == 80
        assert user_id == "test-user"

    def test_http_with_explicit_port(self):
        host, port, user_id = _parse_grpc_url("http://localhost:8080/grpc/usr")
        assert host == "localhost"
        assert port == 8080
        assert user_id == "usr"

    def test_legacy_host_port(self):
        host, port, user_id = _parse_grpc_url("grpc-storage.aiauto.svc:50051")
        assert host == "grpc-storage.aiauto.svc"
        assert port == 50051
        assert user_id == ""

    def test_invalid_url_raises(self):
        with pytest.raises(ValueError):
            _parse_grpc_url("just-a-hostname")

    def test_invalid_path_raises(self):
        with pytest.raises(ValueError, match="Invalid gRPC URL path"):
            _parse_grpc_url("https://aiauto.pangyo.ainode.ai/api/v1")

    def test_invalid_port_raises(self):
        with pytest.raises(ValueError, match="Invalid port"):
            _parse_grpc_url("host:notaport")

    def test_http_missing_grpc_prefix_raises(self):
        """HTTP URL without /grpc/ path prefix raises ValueError."""
        with pytest.raises(ValueError, match="Invalid gRPC URL path"):
            _parse_grpc_url("http://localhost:8080/api/user123")

    def test_http_grpc_but_no_user_id_raises(self):
        """HTTP URL with /grpc but no user_id (only 1 path part) raises ValueError."""
        with pytest.raises(ValueError, match="Invalid gRPC URL path"):
            _parse_grpc_url("http://localhost:8080/grpc")

    def test_https_with_ip_address(self):
        """HTTPS URL with IP address and path-based routing."""
        host, port, user_id = _parse_grpc_url("https://192.168.1.100:8443/grpc/admin")
        assert host == "192.168.1.100"
        assert port == 8443
        assert user_id == "admin"
