"""Tests for the exception hierarchy and global exception handlers."""

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from app.exceptions import (
    AppError,
    AuthenticationError,
    AuthorizationError,
    DuplicateError,
    NotFoundError,
    ProviderError,
    RateLimitError,
    ValidationError,
)
from app.middleware import register_exception_handlers
from app.schemas.common import APIEnvelope


# ── Exception hierarchy tests ──


def test_app_error_defaults():
    err = AppError()
    assert err.status_code == 500
    assert err.message == "Internal server error"
    assert str(err) == "Internal server error"


def test_app_error_custom_message():
    err = AppError("something broke")
    assert err.message == "something broke"
    assert str(err) == "something broke"


@pytest.mark.parametrize(
    "cls,code,default_msg",
    [
        (ValidationError, 400, "Validation error"),
        (NotFoundError, 404, "Resource not found"),
        (DuplicateError, 409, "Resource already exists"),
        (AuthenticationError, 401, "Authentication required"),
        (AuthorizationError, 403, "Insufficient permissions"),
        (ProviderError, 502, "Market data provider unavailable"),
        (RateLimitError, 429, "Rate limited"),
    ],
)
def test_subclass_defaults(cls, code, default_msg):
    err = cls()
    assert err.status_code == code
    assert err.message == default_msg


def test_subclass_custom_message():
    err = NotFoundError("Ticker not found: AAPL")
    assert err.status_code == 404
    assert err.message == "Ticker not found: AAPL"


def test_all_subclasses_are_app_errors():
    for cls in (
        ValidationError,
        NotFoundError,
        DuplicateError,
        AuthenticationError,
        AuthorizationError,
        ProviderError,
        RateLimitError,
    ):
        assert issubclass(cls, AppError)


# ── APIEnvelope schema tests ──


def test_envelope_success():
    env = APIEnvelope(status="success", data={"id": 1})
    assert env.status == "success"
    assert env.data == {"id": 1}
    assert env.error is None


def test_envelope_error():
    env = APIEnvelope(status="error", error="bad request")
    assert env.status == "error"
    assert env.data is None
    assert env.error == "bad request"


# ── Middleware integration tests ──


def _make_app() -> FastAPI:
    """Create a minimal FastAPI app with exception handlers and test routes."""
    app = FastAPI()
    register_exception_handlers(app)

    @app.get("/raise-not-found")
    async def _raise_not_found():
        raise NotFoundError("Ticker not found: XYZ")

    @app.get("/raise-validation")
    async def _raise_validation():
        raise ValidationError("high < low")

    @app.get("/raise-duplicate")
    async def _raise_duplicate():
        raise DuplicateError("Ticker already exists: AAPL")

    @app.get("/raise-auth")
    async def _raise_auth():
        raise AuthenticationError()

    @app.get("/raise-authz")
    async def _raise_authz():
        raise AuthorizationError()

    @app.get("/raise-provider")
    async def _raise_provider():
        raise ProviderError()

    @app.get("/raise-rate-limit")
    async def _raise_rate_limit():
        raise RateLimitError("Rate limited. Ingested 42 records. Resume available.")

    @app.get("/raise-unhandled")
    async def _raise_unhandled():
        raise RuntimeError("unexpected")

    return app


@pytest.fixture
def client():
    return TestClient(_make_app())


def test_middleware_not_found(client):
    resp = client.get("/raise-not-found")
    assert resp.status_code == 404
    body = resp.json()
    assert body["status"] == "error"
    assert body["data"] is None
    assert body["error"] == "Ticker not found: XYZ"


def test_middleware_validation(client):
    resp = client.get("/raise-validation")
    assert resp.status_code == 400
    body = resp.json()
    assert body["status"] == "error"
    assert body["error"] == "high < low"


def test_middleware_duplicate(client):
    resp = client.get("/raise-duplicate")
    assert resp.status_code == 409
    body = resp.json()
    assert body["status"] == "error"
    assert "already exists" in body["error"]


def test_middleware_authentication(client):
    resp = client.get("/raise-auth")
    assert resp.status_code == 401
    body = resp.json()
    assert body["status"] == "error"


def test_middleware_authorization(client):
    resp = client.get("/raise-authz")
    assert resp.status_code == 403
    body = resp.json()
    assert body["status"] == "error"


def test_middleware_provider_error(client):
    resp = client.get("/raise-provider")
    assert resp.status_code == 502
    body = resp.json()
    assert body["status"] == "error"


def test_middleware_rate_limit(client):
    resp = client.get("/raise-rate-limit")
    assert resp.status_code == 429
    body = resp.json()
    assert body["status"] == "error"
    assert "42 records" in body["error"]


def test_middleware_unhandled_exception():
    app = _make_app()
    with TestClient(app, raise_server_exceptions=False) as c:
        resp = c.get("/raise-unhandled")
    assert resp.status_code == 500
    body = resp.json()
    assert body["status"] == "error"
    assert body["data"] is None
    assert body["error"] == "Internal server error"
