"""Startup must survive Postgres arriving late.

On prod, 2026-10-07 15:20 the service came up before docker did (the agent had
just rebooted the host itself) and a single ConnectError from
create_container() aborted the whole lifespan — the table-DDL retry loop sits
*below* that call and never got a turn.
"""

import pytest

import navi.main as main_mod


def stub_create_container(monkeypatch, errors):
    """`errors` — exceptions to raise, in order; then a sentinel container."""
    container = object()
    calls = {"n": 0}

    async def fake():
        calls["n"] += 1
        if calls["n"] <= len(errors):
            raise errors[calls["n"] - 1]
        return container

    monkeypatch.setattr(main_mod, "create_container", fake)
    return container, calls


class TestTransientClassification:
    def test_connection_failures_are_transient(self):
        assert main_mod._is_transient_db_error(ConnectionRefusedError()) is True
        assert main_mod._is_transient_db_error(OSError("no route to host")) is True
        # what asyncpg raises when the pool gives up mid-handshake; the code
        # spells it asyncio.TimeoutError, which is the same class on 3.11+
        assert main_mod._is_transient_db_error(TimeoutError()) is True

    def test_misconfiguration_is_not(self):
        assert main_mod._is_transient_db_error(RuntimeError("DATABASE_URL is not set")) is False
        assert main_mod._is_transient_db_error(ValueError("bad port")) is False


class TestContainerRetry:
    async def test_a_refused_connection_is_retried(self, monkeypatch):
        container, calls = stub_create_container(monkeypatch, [ConnectionRefusedError()])

        result = await main_mod._create_container_with_retry(attempts=5, delay=0)

        assert result is container
        assert calls["n"] == 2

    async def test_a_late_postgres_is_waited_out(self, monkeypatch):
        _, calls = stub_create_container(
            monkeypatch, [ConnectionRefusedError(), OSError("timeout")] * 3
        )

        await main_mod._create_container_with_retry(attempts=8, delay=0)

        assert calls["n"] == 7

    async def test_exhausting_the_attempts_still_raises(self, monkeypatch):
        _, calls = stub_create_container(monkeypatch, [ConnectionRefusedError()] * 10)

        with pytest.raises(ConnectionRefusedError):
            await main_mod._create_container_with_retry(attempts=3, delay=0)

        assert calls["n"] == 3

    async def test_misconfiguration_fails_fast(self, monkeypatch):
        """Retrying a missing DATABASE_URL just delays an error that will never
        clear itself."""
        _, calls = stub_create_container(
            monkeypatch, [RuntimeError("DATABASE_URL is not set")] * 10
        )

        with pytest.raises(RuntimeError):
            await main_mod._create_container_with_retry(attempts=5, delay=0)

        assert calls["n"] == 1
