from __future__ import annotations

from rungate._exceptions import (
    AgentDisabledError,
    APIConnectionError,
    APIError,
    APITimeoutError,
    ApprovalRequiredError,
    AuthenticationError,
    BudgetAlertExceededError,
    BudgetExceededError,
    GovernanceError,
    HourlyCostExceededError,
    InternalServerError,
    ModelNotAllowedError,
    ProviderError,
    RateLimitedError,
    RunawayPreventionError,
    RunForceStoppedError,
    RungateError,
    ToolBlockedError,
)


class TestAPIError:
    def test_has_fields(self):
        err = APIError(message="test error", status_code=400, body={"error": {"type": "test"}})
        assert err.status_code == 400
        assert err.body == {"error": {"type": "test"}}
        assert err.message == "test error"
        assert str(err) == "test error"

    def test_authentication_error_is_subclass(self):
        err = AuthenticationError(message="invalid key", status_code=401)
        assert isinstance(err, APIError)
        assert isinstance(err, RungateError)

    def test_internal_server_error_is_subclass(self):
        err = InternalServerError(message="server error", status_code=500)
        assert isinstance(err, APIError)
        assert err.status_code == 500


class TestProviderError:
    def test_has_provider_errors_list(self):
        err = ProviderError(
            message="All providers failed",
            provider_errors=[{"provider": "openai", "status": 500, "message": "Internal error"}],
        )
        assert err.status_code == 502
        assert len(err.provider_errors) == 1
        assert err.provider_errors[0]["provider"] == "openai"

    def test_is_subclass_of_api_error(self):
        err = ProviderError(message="failed")
        assert isinstance(err, APIError)
        assert isinstance(err, RungateError)

    def test_default_empty_provider_errors(self):
        err = ProviderError(message="failed")
        assert err.provider_errors == []


class TestGovernanceError:
    def test_has_governance_attributes(self):
        err = GovernanceError(
            message="blocked",
            status_code=402,
            governance_code="budget_exceeded",
            run_id="run-123",
            details={"cost_usd": 10.0},
        )
        assert err.governance_code == "budget_exceeded"
        assert err.run_id == "run-123"
        assert err.details == {"cost_usd": 10.0}
        assert err.status_code == 402

    def test_is_subclass_of_api_error(self):
        err = GovernanceError(message="blocked", status_code=403, governance_code="test")
        assert isinstance(err, APIError)
        assert isinstance(err, RungateError)

    def test_default_empty_details(self):
        err = GovernanceError(message="blocked", status_code=403, governance_code="test")
        assert err.details == {}
        assert err.run_id is None

    def test_caught_by_except_api_error(self):
        """GovernanceError and all subclasses can be caught with except APIError."""
        errors = [
            BudgetExceededError(message="over", status_code=402, governance_code="budget_exceeded"),
            ToolBlockedError(message="blocked", status_code=403, governance_code="tool_blocked"),
            RateLimitedError(message="limited", status_code=429, governance_code="rate_limited"),
        ]
        for err in errors:
            assert isinstance(err, APIError)
            assert isinstance(err, GovernanceError)


class TestGovernanceSubclasses:
    def test_budget_exceeded(self):
        err = BudgetExceededError(message="over", status_code=402, governance_code="budget_exceeded")
        assert isinstance(err, GovernanceError)
        assert err.governance_code == "budget_exceeded"

    def test_budget_alert_exceeded(self):
        err = BudgetAlertExceededError(message="alert", status_code=402, governance_code="budget_alert_exceeded")
        assert isinstance(err, GovernanceError)

    def test_hourly_cost_exceeded(self):
        err = HourlyCostExceededError(message="hourly", status_code=402, governance_code="hourly_cost_exceeded")
        assert isinstance(err, GovernanceError)

    def test_rate_limited(self):
        err = RateLimitedError(message="limited", status_code=429, governance_code="rate_limited")
        assert isinstance(err, GovernanceError)

    def test_model_not_allowed(self):
        err = ModelNotAllowedError(message="denied", status_code=403, governance_code="model_not_allowed")
        assert isinstance(err, GovernanceError)

    def test_tool_blocked(self):
        err = ToolBlockedError(message="blocked", status_code=403, governance_code="tool_blocked")
        assert isinstance(err, GovernanceError)

    def test_agent_disabled(self):
        err = AgentDisabledError(message="disabled", status_code=403, governance_code="agent_disabled")
        assert isinstance(err, GovernanceError)

    def test_run_force_stopped(self):
        err = RunForceStoppedError(message="stopped", status_code=403, governance_code="run_force_stopped")
        assert isinstance(err, GovernanceError)

    def test_runaway_prevention(self):
        err = RunawayPreventionError(message="runaway", status_code=403, governance_code="runaway_prevention")
        assert isinstance(err, GovernanceError)


class TestApprovalRequiredError:
    def test_has_approval_requests(self):
        err = ApprovalRequiredError(
            message="needs approval",
            run_id="run-123",
            approval_requests=[{"id": "apr-1", "tool": "execute_code"}],
        )
        assert err.status_code == 202
        assert err.governance_code == "tool_approval_required"
        assert err.run_id == "run-123"
        assert len(err.approval_requests) == 1
        assert err.approval_requests[0]["tool"] == "execute_code"

    def test_is_governance_error(self):
        err = ApprovalRequiredError(message="needs approval")
        assert isinstance(err, GovernanceError)
        assert isinstance(err, APIError)
        assert isinstance(err, RungateError)

    def test_default_empty_approval_requests(self):
        err = ApprovalRequiredError(message="needs approval")
        assert err.approval_requests == []

    def test_caught_by_except_governance_error(self):
        err = ApprovalRequiredError(message="needs approval")
        assert isinstance(err, GovernanceError)


class TestConnectionErrors:
    def test_connection_error(self):
        err = APIConnectionError("cannot connect")
        assert isinstance(err, RungateError)
        assert err.message == "cannot connect"

    def test_timeout_error(self):
        err = APITimeoutError("timed out")
        assert isinstance(err, RungateError)
        assert err.message == "timed out"

    def test_connection_error_default_message(self):
        err = APIConnectionError()
        assert "Failed to connect" in err.message

    def test_timeout_error_default_message(self):
        err = APITimeoutError()
        assert "timed out" in err.message

    def test_not_subclass_of_api_error(self):
        """Connection errors are NOT APIError — they're network-level, not HTTP-level."""
        assert not isinstance(APIConnectionError(), APIError)
        assert not isinstance(APITimeoutError(), APIError)
