# tests/integration/conftest.py
"""Make the integration suite independent of collection order.

`app.core.config` builds its `settings` singleton once, at first import.
`tests/unit/conftest.py` seeds dummy DB_* credentials into os.environ via
`setdefault` so the unit suite can run with no database -- that file is
deliberately left alone.

The trouble is `pytest tests/unit tests/integration` (or plain `pytest
tests/`) collects both directories in one process, and collection imports
every test module -- and everything it pulls in -- before any test runs. If a
unit module is imported first, `app.core.config.settings` freezes on the dummy
test/test/localhost credentials, and every module that already did `from
app.core.config import settings` (e.g. `app.services.infra.database`) is holding a
reference to that same frozen object. Reassigning `app.core.config.settings`
to a new object at that point would not help: those other modules would still
be looking at the old one.

So this fixture reloads the real values from .env into os.environ, builds a
fresh Settings() from them, and copies its fields onto the *existing*
singleton object in place -- every module already holding a reference to it
sees the update without needing to be reloaded.
"""
import os

import pytest
from dotenv import load_dotenv

from app.core import config as config_module
from app.core.config import Settings


@pytest.fixture(scope="session", autouse=True)
def _use_real_settings_for_integration_tests():
    load_dotenv(override=True)
    fresh = Settings()
    for field_name in Settings.model_fields:
        setattr(config_module.settings, field_name, getattr(fresh, field_name))


from app.services.infra.database import get_db_connection

_TEMP_SCHEMA_PREFIX = "test_catalog_tmp_"


@pytest.fixture(scope="session", autouse=True)
def _sweep_stale_temp_schemas():
    # A killed run cannot drop its own schema. Without this they accumulate
    # in a shared database until someone notices. The prefix match is safe
    # against ever touching a real tenant: those are named "org_<uuid>", never
    # "test_catalog_tmp_*".
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                "SELECT nspname FROM pg_namespace WHERE nspname LIKE %s",
                (f"{_TEMP_SCHEMA_PREFIX}%",))
            for (name,) in cur.fetchall():
                if name != f"{_TEMP_SCHEMA_PREFIX}{os.getpid()}":
                    cur.execute(f'DROP SCHEMA IF EXISTS "{name}" CASCADE')
        conn.commit()
    finally:
        conn.close()
    yield


@pytest.fixture
def temp_tenant():
    """A throwaway schema so destructive tests never touch a real tenant.

    The scoped-delete behaviour this exercises is the one bug in this feature
    that would silently destroy a tenant's crawled catalog, so it is tested
    against real Postgres rather than by inspecting source text.
    """
    # Keyed by pid, not a fixed name: two concurrent suite runs (two CI
    # runners, two branches building at once) would otherwise create and
    # drop the same schema out from under each other.
    schema = f"{_TEMP_SCHEMA_PREFIX}{os.getpid()}"
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE')
            cur.execute(f'CREATE SCHEMA "{schema}"')
        conn.commit()
    finally:
        conn.close()

    yield schema

    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            # IF EXISTS: a test that failed before the schema was created
            # must not raise here and mask the real failure.
            cur.execute(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE')
        conn.commit()
    finally:
        conn.close()
