"""DB-or-CSV dispatch helper.

During the CSV → Postgres migration, most data loaders need to:
  1. Use Postgres when it is up and the table has been populated.
  2. Fall back to the existing CSV loader otherwise — so the app keeps
     working on a fresh checkout that has not yet run `docker compose up`
     and `python db/init_db.py`.

Wrap each call-site like:

    from db.csv_fallback import db_or_csv

    df = db_or_csv(
        db_query_fn=lambda: pd.read_sql("SELECT ...", get_engine()),
        csv_fallback_fn=lambda: pd.read_csv("data/sales_clean.csv"),
    )

The DB path is taken only when (a) is_db_available() returns True AND
(b) the DB callable returns a non-empty result without raising. Any
exception inside db_query_fn is swallowed and the CSV path runs.
"""
from __future__ import annotations

import logging
from typing import Any, Callable, Optional

from db.connection import is_db_available

logger = logging.getLogger(__name__)


def _is_empty(result: Any) -> bool:
    """True if result is None, or has __len__ == 0, or is a pandas object
    with .empty == True. Conservative — anything we can't introspect is
    considered non-empty."""
    if result is None:
        return True
    empty_attr = getattr(result, "empty", None)
    if isinstance(empty_attr, bool):
        return empty_attr
    try:
        return len(result) == 0
    except TypeError:
        return False


def db_or_csv(
    db_query_fn: Callable[[], Any],
    csv_fallback_fn: Callable[[], Any],
    *,
    label: Optional[str] = None,
) -> Any:
    """Try the DB path first, fall back to CSV on any failure or empty result.

    `label` is an optional tag included in log messages — useful when the
    same helper is called from many sites and you want to know which one
    fell back.
    """
    tag = f" [{label}]" if label else ""

    if not is_db_available():
        logger.info("Falling back to CSV%s (DB not available)", tag)
        return csv_fallback_fn()

    try:
        result = db_query_fn()
    except Exception as e:
        logger.warning("Falling back to CSV%s (DB query raised: %s)", tag, e)
        return csv_fallback_fn()

    if _is_empty(result):
        logger.info("Falling back to CSV%s (DB returned empty)", tag)
        return csv_fallback_fn()

    logger.info("Using PostgreSQL%s", tag)
    return result
