from __future__ import annotations

import asyncio
from typing import Self

import pytest
from httpx import ASGITransport, AsyncClient

from app.config import Settings
from app.db.pool import Database
from app.main import create_app


class FakeDatabase:
    def __init__(self, ready: bool) -> None:
        self.ready = ready
        self.connected = False
        self.closed = False

    async def connect(self) -> None:
        self.connected = True

    async def close(self) -> None:
        self.closed = True

    async def is_ready(self) -> bool:
        return self.ready

    async def __aenter__(self) -> Self:
        await self.connect()
        return self

    async def __aexit__(self, exc_type: object, exc: object, traceback: object) -> None:
        await self.close()


@pytest.mark.parametrize(("database_ready", "status_code"), [(True, 200), (False, 503)])
async def test_health_endpoints(database_ready: bool, status_code: int) -> None:
    database = FakeDatabase(database_ready)

    def factory(settings: Settings) -> Database:
        del settings
        return database

    application = create_app(
        Settings(contract_snapshot=None, compatibility_evidence=None),
        factory,
        worker_factories=(),
    )
    async with application.router.lifespan_context(application):
        async with AsyncClient(
            transport=ASGITransport(app=application, raise_app_exceptions=False),
            base_url="http://test",
        ) as client:
            live = await client.get("/health/live")
            ready = await client.get("/health/ready", headers={"X-Request-ID": "test-request"})

    assert live.status_code == 200
    assert live.json() == {"status": "ok"}
    assert ready.status_code == status_code
    assert ready.headers["X-Request-ID"] == "test-request"
    assert database.connected
    assert database.closed


async def test_lifespan_starts_and_stops_injected_workers() -> None:
    database = FakeDatabase(True)
    started = asyncio.Event()
    stopping = asyncio.Event()

    class Worker:
        async def run(self) -> None:
            started.set()
            await stopping.wait()

        def stop(self) -> None:
            stopping.set()

    application = create_app(
        Settings(contract_snapshot=None, compatibility_evidence=None),
        lambda _settings: database,
        worker_factories=(lambda _database: Worker(),),
    )
    async with application.router.lifespan_context(application):
        await started.wait()

    assert stopping.is_set()
