"""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, no day-boundary filter
    - detect_gaps.py: per-pair filtered detail rows, day-boundary exclusion
    - 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"""

# Day-boundary detection expression (uses open_time_ms for current bar,
# prev close_time_ms for previous bar — matches heartbeat's day_diff logic).
DAY_DIFF_EXPR = (
    "toDate(open_time_ms / 1000) "
    "- toDate(lagInFrame(close_time_ms, 1) OVER w / 1000) AS day_diff"
)

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

# 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 cross_pair_filter() -> str:
    """WHERE clause for cross-pair queries (no symbol/threshold filter)."""
    return f"WHERE {VALID_TID_FILTER}"


def day_boundary_exclusion() -> str:
    """SQL condition to exclude day-boundary gaps (structural from Ouroboros).

    Day-boundary gaps have tid_delta > 0 and span exactly one UTC day.
    Overlaps (tid_delta < 0) are NEVER excluded.
    """
    return (
        "AND NOT (tid_delta > 0 "
        "AND toDate(close_time_ms / 1000, 'UTC') "
        "!= toDate(prev_close_time_ms / 1000, 'UTC'))"
    )
