"""ClickHouse database statistics for heartbeat monitoring."""

from __future__ import annotations

import logging

from heartbeat.checks.freshness import collect_symbol_freshness

logger = logging.getLogger("health-heartbeat")


def collect_clickhouse_stats(
    client: object,  # clickhouse_connect Client
    results: dict[str, bool | str],
) -> None:
    """Collect ClickHouse database statistics into results dict."""
    from opendeviationbar.clickhouse.stathera_query import (
        FULL_TABLE as _FT,
    )
    from opendeviationbar.clickhouse.stathera_query import (
        enabled_symbols_sql,
    )

    _sym_filter = enabled_symbols_sql()

    # Total bars, pair count, and active symbol count
    row = client.query(  # type: ignore[union-attr]
        f"SELECT count(), uniqExact(symbol, threshold_decimal_bps), uniqExact(symbol) "
        f"FROM {_FT} WHERE 1=1 {_sym_filter}"
    )
    total_bars, n_pairs, n_symbols = row.result_rows[0]
    results["ch_total_bars"] = f"{total_bars:,}"
    results["ch_pairs"] = str(n_pairs)
    results["ch_symbols"] = str(n_symbols)

    # Database size on disk
    row = client.query(  # type: ignore[union-attr]
        "SELECT sum(bytes_on_disk), count() "
        "FROM system.parts "
        "WHERE database = 'opendeviationbar_cache' AND active"
    )
    bytes_on_disk, active_parts = row.result_rows[0]
    if bytes_on_disk < 1024 * 1024 * 1024:
        results["ch_db_size"] = f"{bytes_on_disk / 1024 / 1024:.1f} MiB"
    else:
        results["ch_db_size"] = f"{bytes_on_disk / 1024 / 1024 / 1024:.2f} GiB"
    results["ch_parts"] = str(active_parts)

    # 24h growth (bars inserted recently via computed_at)
    row = client.query(  # type: ignore[union-attr]
        f"SELECT count() FROM {_FT} "
        f"WHERE computed_at > now() - INTERVAL 24 HOUR {_sym_filter}"
    )
    bars_24h = row.result_rows[0][0]
    results["ch_bars_24h"] = f"+{bars_24h:,}"

    # Per-threshold staleness: top 5 stalest + freshest pair
    row = client.query(  # type: ignore[union-attr]
        f"SELECT symbol, threshold_decimal_bps, "
        f"dateDiff('minute', max(toDateTime(intDiv(close_time_ms, 1000), 'UTC')), now('UTC')) AS stale_min "
        f"FROM {_FT} "
        f"WHERE close_time_ms > 0 {_sym_filter} "
        f"GROUP BY symbol, threshold_decimal_bps "
        f"ORDER BY stale_min DESC"
    )
    if row.result_rows:
        # Stalest 5 pairs
        stalest_lines = []
        for sym, thr, stale_min in row.result_rows[:5]:
            stale_h = stale_min / 60
            age = f"{stale_h:.1f}h" if stale_h < 24 else f"{stale_h / 24:.1f}d"
            stalest_lines.append(f"{sym}@{thr} ({age} ago)")
        results["ch_stalest_pairs"] = "\n".join(stalest_lines)

        # Freshest pair (last in DESC order)
        sym, thr, fresh_min = row.result_rows[-1]
        results["ch_freshest"] = f"{sym}@{thr} ({fresh_min / 60:.1f}h ago)"

    # Per-symbol freshness: latest bar time for each symbol (BTCUSDT first)
    collect_symbol_freshness(client, results)


def collect_clickhouse_health_metrics(
    client: object,  # clickhouse_connect Client
    results: dict[str, bool | str],
) -> None:
    """Collect ClickHouse health metrics: pending mutations and active parts count.

    Pending mutations indicate in-flight DELETEs or ALTERs that may affect
    query accuracy (FINAL reads are needed). Active parts count tracks
    merge pressure — too many parts can degrade insert and query performance.
    """
    # Pending mutations (incomplete DELETEs/ALTERs)
    row = client.query(  # type: ignore[union-attr]
        "SELECT count() FROM system.mutations "
        "WHERE database = 'opendeviationbar_cache' AND is_done = 0"
    )
    pending_mutations = row.result_rows[0][0] if row.result_rows else 0
    results["pending_mutations"] = str(pending_mutations)
    results["mutations_healthy"] = pending_mutations == 0

    # Active parts count
    row = client.query(  # type: ignore[union-attr]
        "SELECT count() FROM system.parts "
        "WHERE active = 1 AND database = 'opendeviationbar_cache' "
        "AND table = 'open_deviation_bars'"
    )
    active_parts = row.result_rows[0][0] if row.result_rows else 0
    results["active_parts"] = str(active_parts)
