"""Helpers for PostgreSQL-backed components."""
from __future__ import annotations

import logging
import os
import re
import threading
from typing import Any


_logger = logging.getLogger(__name__)
_SAFE_IDENTIFIER_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_.]*$")


def safe_identifier(name: str) -> str:
    """Validate that *name* is a safe SQL identifier (table/column/schema name).

    Raises ``ValueError`` if the name contains characters outside
    ``[a-zA-Z0-9_.]`` or doesn't start with a letter/underscore.
    This prevents SQL injection through f-string table name interpolation.
    """
    if not name or not _SAFE_IDENTIFIER_RE.match(name):
        raise ValueError(
            f"Unsafe SQL identifier: {name!r}. "
            "Only letters, digits, underscores and dots are allowed."
        )
    return name


def resolve_database_url(
    explicit: str | None = None,
    *,
    env_vars: tuple[str, ...] = (
        "LOGOS_ADK_DATABASE_URL",
        "DATABASE_URL",
    ),
) -> str | None:
    """Resolve a database URL from explicit config or environment."""
    if explicit:
        return explicit
    for env_var in env_vars:
        value = os.getenv(env_var)
        if value:
            return value
    return None


def import_psycopg() -> Any:
    """Import psycopg lazily with a consistent installation hint."""
    try:
        import psycopg
    except ImportError as exc:
        raise ImportError(
            "PostgreSQL support requires the 'psycopg' package. "
            "Install it with: pip install logos-adk[postgresql]"
        ) from exc
    return psycopg


# ---------------------------------------------------------------------------
# Connection pool registry — one pool per DSN, shared across all backends
# ---------------------------------------------------------------------------

_pool_registry: dict[str, Any] = {}
_pool_lock = threading.Lock()


def get_connection_pool(
    dsn: str,
    *,
    min_size: int = 1,
    max_size: int = 10,
) -> Any:
    """Return a shared ``psycopg_pool.ConnectionPool`` for *dsn*.

    Pools are created lazily and cached by DSN.  If ``psycopg_pool`` is not
    installed, falls back to direct connections (returns ``None``).
    """
    with _pool_lock:
        pool = _pool_registry.get(dsn)
        if pool is not None:
            return pool

    try:
        from psycopg_pool import ConnectionPool
    except ImportError:
        _logger.debug("psycopg_pool not installed — using direct connections")
        return None

    pool = ConnectionPool(
        dsn,
        min_size=min_size,
        max_size=max_size,
        open=True,
    )
    with _pool_lock:
        # Double-check: another thread may have created the pool.
        existing = _pool_registry.get(dsn)
        if existing is not None:
            pool.close()
            return existing
        _pool_registry[dsn] = pool
    return pool


def pooled_connect(dsn: str) -> Any:
    """Get a connection from the shared pool, or fall back to ``psycopg.connect``."""
    pool = get_connection_pool(dsn)
    if pool is not None:
        return pool.connection()
    return import_psycopg().connect(dsn)


def close_all_pools() -> None:
    """Close every pool in the registry.  Call on application shutdown."""
    with _pool_lock:
        pools = list(_pool_registry.values())
        _pool_registry.clear()
    for pool in pools:
        try:
            pool.close()
        except Exception:
            pass
