"""Shared Stathera SQL query builder for trade-ID continuity audits.

Issue #259 (5A): Three callers independently maintain lagInFrame() queries
with subtle differences. This module provides composable query fragments
to ensure consistency while respecting each caller's structural needs.

Callers:
    - kintsugi.py: per-pair detail rows
    - detect_gaps.py: per-pair filtered detail rows
    - health_heartbeat.py: cross-pair aggregates + top-5 breakdowns
"""

from __future__ import annotations

# Database and table constants (SSoT)
DATABASE = "opendeviationbar_cache"
TABLE = "open_deviation_bars"
FULL_TABLE = f"{DATABASE}.{TABLE}"

# Core Stathera window function columns.
# These are the SELECT expressions for the inner subquery.
STATHERA_CORE_COLUMNS = """\
    first_agg_trade_id,
    last_agg_trade_id,
    close_time_ms,
    lagInFrame(close_time_ms, 1) OVER w AS prev_close_time_ms,
    lagInFrame(last_agg_trade_id, 1) OVER w AS prev_last_tid,
    first_agg_trade_id - lagInFrame(last_agg_trade_id, 1) OVER w - 1 AS tid_delta,
    row_number() OVER w AS rn"""

# Standard WHERE clause for valid trade IDs.
VALID_TID_FILTER = "first_agg_trade_id > 0 AND last_agg_trade_id > 0"

# First-bar exclusion for aggregate gap/overlap counts.
#
# The first bar in each (symbol, threshold, ouroboros_mode) partition has no
# predecessor: lagInFrame() returns 0 (UInt64 default), making
#   tid_delta = first_agg_trade_id - 0 - 1 = first_agg_trade_id - 1
# which is a huge positive number falsely counted as a gap.
#
# This is NOT a real gap — data simply starts at effective_start (e.g.,
# 2018-01-16 for BTCUSDT). The registry's `effective_start` field encodes
# the authoritative "data begins here" date; rn > 1 is the SQL expression
# of the same invariant: only count a gap if a predecessor actually exists.
#
# Usage: include row_number() OVER w AS rn in the inner SELECT,
# then append AND {FIRST_BAR_EXCLUSION} to the outer WHERE clause.
FIRST_BAR_EXCLUSION = "rn > 1"

# Standard ORDER BY for window function (critical: first_agg_trade_id first
# for correct overlap detection, close_time_ms as tiebreaker).
WINDOW_ORDER = "ORDER BY first_agg_trade_id, close_time_ms"


def per_pair_window() -> str:
    """Window clause for per-pair queries (kintsugi, detect_gaps).

    No PARTITION BY — caller filters to a single (symbol, threshold) pair.
    """
    return f"WINDOW w AS ({WINDOW_ORDER})"


def cross_pair_window() -> str:
    """Window clause for cross-pair queries (heartbeat).

    PARTITION BY ensures each (symbol, threshold, ouroboros_mode) pair
    is analyzed independently within a single query across all pairs.
    """
    return (
        "WINDOW w AS (PARTITION BY symbol, threshold_decimal_bps, "
        f"ouroboros_mode {WINDOW_ORDER})"
    )


def per_pair_filter() -> str:
    """WHERE clause for per-pair queries with parameterized symbol/threshold."""
    return (
        f"WHERE symbol = {{symbol:String}} "
        f"AND threshold_decimal_bps = {{threshold:UInt32}} "
        f"AND ouroboros_mode = {{ouroboros:String}} "
        f"AND {VALID_TID_FILTER}"
    )


def enabled_symbols_sql() -> str:
    """SQL AND clause restricting queries to enabled symbols from the registry.

    Returns an empty string when all symbols are enabled (no filter needed),
    or ``AND symbol IN ('BTCUSDT', ...)`` when some are disabled.
    """
    from opendeviationbar.symbol_registry import get_registered_symbols

    enabled = get_registered_symbols(enabled_only=True)
    all_syms = get_registered_symbols(enabled_only=False)
    if len(enabled) == len(all_syms):
        return ""  # no filtering needed — all enabled
    quoted = ", ".join(f"'{s}'" for s in enabled)
    return f"AND symbol IN ({quoted})"


def cross_pair_filter() -> str:
    """WHERE clause for cross-pair queries, respecting the symbol registry."""
    sym_filter = enabled_symbols_sql()
    return f"WHERE {VALID_TID_FILTER} {sym_filter}"




