"""A fake, in-memory stand-in for `asyncpg.Pool` — `asyncpg` needs a running Postgres; this
needs nothing, so the test suite never touches a database. Provided; do not edit.
`f1api/app.py` talks to it only through `acquire()` / `in_use` / `healthy` / `close()` — the
same shape a real `asyncpg.Pool` has — so `main.py` swapping in the real thing is a one-line
change.

Unlike the real pool (whose `close()` waits patiently for every checked-out connection to come
back), this fake raises the instant `close()` is called while one is still out — a shutdown
handler that forgets to drain fails a test immediately, instead of the test quietly racing real
I/O timing to notice.
"""

from __future__ import annotations

import asyncio
from types import TracebackType


class PoolClosedError(RuntimeError):
    """Raised by `acquire()` on a closed pool, and by `close()` on one still in use."""


class FakeConnection:
    """Stands in for an `asyncpg.Connection`. `execute` sleeps for `delay` seconds before
    returning — that sleep is *inside* the checked-out window (between `acquire()` and the
    `async with` block exiting), the same way a real query holds its connection for the round
    trip, not for however long it took the pool to hand the connection out."""

    def __init__(self, delay: float) -> None:
        self._delay = delay
        self.calls: list[tuple[str, tuple[object, ...]]] = []

    async def execute(self, query: str, *args: object) -> str:
        await asyncio.sleep(self._delay)
        self.calls.append((query, args))
        return "INSERT 1"


class _Acquire:
    def __init__(self, pool: FakePool) -> None:
        self._pool = pool

    async def __aenter__(self) -> FakeConnection:
        if self._pool.closed:
            raise PoolClosedError("acquire() called on a closed pool")
        await self._pool._sem.acquire()  # instant while a permit is free, exactly like asyncpg
        self._pool.in_use += 1
        self._pool.peak_in_use = max(self._pool.peak_in_use, self._pool.in_use)
        return FakeConnection(self._pool.acquire_delay)

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        self._pool.in_use -= 1
        self._pool._sem.release()


class FakePool:
    """`min_size`/`max_size` mirror `asyncpg.Pool`'s constructor. `acquire_delay` is how long
    a connection stays checked out once handed out — a query's round trip, not the handshake to
    obtain one. `peak_in_use` is the highest number of connections ever checked out at once —
    what a "pool size respected" test reads back. `simulate_loss()` is the test double for "the
    database is unreachable", the same thing a real readiness probe would learn from a failed
    `SELECT 1`."""

    def __init__(self, min_size: int = 2, max_size: int = 4, acquire_delay: float = 0.01) -> None:
        self.min_size = min_size
        self.max_size = max_size
        self.acquire_delay = acquire_delay
        self.in_use = 0
        self.peak_in_use = 0
        self.closed = False
        self._lost = False
        self._sem = asyncio.Semaphore(max_size)

    def acquire(self) -> _Acquire:
        return _Acquire(self)

    async def close(self) -> None:
        if self.in_use > 0:
            raise PoolClosedError(
                f"close() called with {self.in_use} connection(s) still checked out"
            )
        self.closed = True

    def simulate_loss(self) -> None:
        self._lost = True

    @property
    def healthy(self) -> bool:
        return not self.closed and not self._lost
