"""Fixtures: a fresh app, database and lifespan for every test; an in-memory webhook receiver."""
import httpx
import pytest

from orders_api.config import Settings
from orders_api.main import create_app
from orders_api.outbox import sign
from orders_api.seed import DEMO_PASSWORD

WEBHOOK_URL = "https://hooks.partner.example/orders"


def pytest_configure(config):
    if not config.pluginmanager.has_plugin("anyio"):        # e.g. PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
        import anyio.pytest_plugin
        config.pluginmanager.register(anyio.pytest_plugin, "anyio")


@pytest.fixture
def anyio_backend():
    return "asyncio"


class WebhookReceiver:
    """Plays the partner's endpoint: verifies signatures, can be told to fail."""

    def __init__(self, secret: str) -> None:
        self.secret, self.received, self.failures_left, self.bad_signatures = secret, [], 0, 0

    def __call__(self, request: httpx.Request) -> httpx.Response:
        body = request.content.decode()
        expected = sign(self.secret, request.headers["webhook-id"], request.headers["webhook-timestamp"], body)
        if request.headers["webhook-signature"] != expected:
            self.bad_signatures += 1
            return httpx.Response(401)
        if self.failures_left:
            self.failures_left -= 1
            return httpx.Response(503)
        self.received.append((request.headers["webhook-id"], httpx.Response(200, content=body).json()))
        return httpx.Response(204)


@pytest.fixture
def settings(tmp_path) -> Settings:
    return Settings(environment="test", database_url=f"sqlite+aiosqlite:///{(tmp_path / 'test.db').as_posix()}",
                    bcrypt_rounds=4, log_level="WARNING", webhook_url=WEBHOOK_URL,
                    outbox_relay_enabled=False, login_attempts_per_minute=5, rate_limit_per_minute=1000)


@pytest.fixture
def receiver(settings) -> WebhookReceiver:
    return WebhookReceiver(settings.webhook_secret.get_secret_value())


@pytest.fixture
async def app_and_state(settings, receiver):
    app = create_app(settings, webhook_transport=httpx.MockTransport(receiver))
    async with app.router.lifespan_context(app) as state:       # the real startup and shutdown
        yield app, state


@pytest.fixture
def state(app_and_state) -> dict:
    return app_and_state[1]


@pytest.fixture
async def client(app_and_state):
    app, state = app_and_state

    async def with_state(scope, receive, send):                # what uvicorn does: lifespan state per request
        scope["state"] = dict(state)
        await app(scope, receive, send)

    transport = httpx.ASGITransport(app=with_state, client=("203.0.113.7", 50000))
    async with httpx.AsyncClient(transport=transport, base_url="http://api.test") as c:
        yield c


@pytest.fixture
def login(client):
    async def _login(email: str) -> dict[str, str]:
        r = await client.post("/v1/auth/token", data={"username": email, "password": DEMO_PASSWORD})
        assert r.status_code == 200, r.text
        return {"Authorization": f"Bearer {r.json()['access_token']}"}
    return _login


@pytest.fixture
async def asha(login):
    return await login("asha@example.com")


@pytest.fixture
async def ravi(login):
    return await login("ravi@example.com")


@pytest.fixture
async def admin(login):
    return await login("admin@example.com")


LATTE_AND_CROISSANT = {"items": [{"sku": "latte", "quantity": 2}, {"sku": "croissant", "quantity": 1}]}


@pytest.fixture
def create_order(client):
    counter = iter(range(1, 10_000))

    async def _create(headers: dict, body: dict | None = None, key: str | None = None) -> httpx.Response:
        return await client.post("/v1/orders", json=body or LATTE_AND_CROISSANT,
                                 headers=headers | {"Idempotency-Key": key or f"test-{next(counter)}"})
    return _create
