"""Gunicorn runtime config.

`preload_app` imports Flask before worker fork. Any SQLAlchemy engines created
during import must drop inherited connection pools in each child worker.
"""

import os
import sys

from sqlalchemy.engine import Engine


bind = "0.0.0.0:80"
workers = int(os.getenv("WEB_CONCURRENCY", "4"))
timeout = int(os.getenv("GUNICORN_TIMEOUT", "300"))
accesslog = "-"
errorlog = "-"
preload_app = True


def _dispose_engine(engine, label, server):
    try:
        try:
            engine.dispose(close=False)
        except TypeError:
            engine.dispose()
        server.log.info("Disposed inherited SQLAlchemy engine after fork: %s", label)
        return True
    except Exception:
        server.log.exception("Failed disposing inherited SQLAlchemy engine: %s", label)
        return False


def post_fork(server, worker):
    """Reset DB pools inherited from the preloaded master process.

    SQLAlchemy engines are safe to keep as objects after fork, but their pools
    must be replaced so workers do not share PostgreSQL TCP sockets.
    """
    disposed_ids = set()
    disposed_count = 0
    prefixes = ("app", "database.", "routes.", "services.")

    def dispose_once(engine, label):
        nonlocal disposed_count
        engine_id = id(engine)
        if engine_id in disposed_ids:
            return
        disposed_ids.add(engine_id)
        if _dispose_engine(engine, label, server):
            disposed_count += 1

    for module_name, module in list(sys.modules.items()):
        if module is None or not module_name.startswith(prefixes):
            continue

        for attr_name, value in vars(module).items():
            candidates = []
            if isinstance(value, Engine):
                candidates.append((value, f"{module_name}.{attr_name}"))
            else:
                try:
                    engine = getattr(value, "engine", None)
                except Exception:
                    # Flask/Werkzeug LocalProxy objects may require a request
                    # context when inspected. They cannot own global DB pools.
                    continue
                if isinstance(engine, Engine):
                    candidates.append((engine, f"{module_name}.{attr_name}.engine"))

            for engine, label in candidates:
                dispose_once(engine, label)

    manager_module = sys.modules.get("database.manager")
    manager_class = getattr(manager_module, "DatabaseManager", None)
    manager_cache = getattr(manager_class, "_instance_cache", {}) if manager_class else {}
    for cache_key, cached in list(manager_cache.items()):
        if not isinstance(cached, dict):
            continue
        engine = cached.get("engine")
        if isinstance(engine, Engine):
            dispose_once(engine, f"database.manager.DatabaseManager._instance_cache[{cache_key!r}]")

    server.log.info(
        "Worker %s reset %s SQLAlchemy engine pool(s) after preload fork",
        worker.pid,
        disposed_count,
    )
