"""Shared crossover TID utility (XOVER-01, XOVER-02).

Provides get_crossover_tid(symbol, date) -- the first aggregated trade ID
at or after UTC midnight for a given date. Extracted from sidecar/midnight.py
so any module (seeder, kintsugi, heartbeat) can look up crossover TIDs
without importing sidecar internals.

2-tier lookup:
  Tier 1: CH seed (yesterday's last TID) + REST verify
  Tier 2: API-max post-midnight query (1-hour window = Binance API max)

Self-validation: 3 properties verified via REST, logs WARNING on violation,
never raises.
"""

from __future__ import annotations

import datetime
import logging

logger = logging.getLogger(__name__)


def get_crossover_tid(symbol: str, date: datetime.date | None = None) -> int | None:
    """Determine the first aggregated trade ID at or after UTC midnight for a date.

    Parameters
    ----------
    symbol : str
        Binance symbol (e.g., "BTCUSDT").
    date : datetime.date | None
        The date whose midnight crossover to look up. Defaults to today (UTC).

    Returns
    -------
    int | None
        The agg_trade_id of the first trade on or after midnight, or None on failure.

    First-principles design -- zero arbitrary parameters:

    1. **CH-seeded fromId** (1 REST call, 20 weight):
       CH day-before MAX(last_agg_trade_id) -> fromId=max+1 -> verify timestamp
       >= midnight. Fast path when CH has the prior day's data.

    2. **API-max post-midnight query** (1 REST call, 20 weight):
       ``startTime=midnight, endTime=midnight+1h`` -> take result[0].
       The 1-hour window is the Binance aggTrades API maximum allowed range.
       Parameter-free by construction.
    """
    if date is None:
        date = datetime.datetime.now(datetime.UTC).date()

    midnight_ms = int(
        datetime.datetime(date.year, date.month, date.day, tzinfo=datetime.UTC).timestamp()
        * 1000
    )

    # --- Tier 1: CH hint + REST verification (1 REST call) ---
    seed_tid = _ch_date_last_tid(symbol, date)
    if seed_tid is not None:
        crossover = _verify_crossover_from_seed(symbol, seed_tid, midnight_ms)
        if crossover is not None:
            _self_validate_crossover(symbol, crossover, midnight_ms, source="CH-seed")
            return crossover

    # --- Tier 2: API-max post-midnight (1 REST call, parameter-free) ---
    crossover = _api_max_post_midnight(symbol, midnight_ms)
    if crossover is not None:
        _self_validate_crossover(symbol, crossover, midnight_ms, source="API-max")
        return crossover

    logger.warning("crossover for %s on %s: both tiers failed", symbol, date)
    return None


def _self_validate_crossover(
    symbol: str, crossover_tid: int, midnight_ms: int, *, source: str
) -> None:
    """Self-validate the crossover TID by proving 3 properties from Binance REST.

    Fetches fromId=crossover_tid-1, limit=2 (1 REST call) to get both
    the predecessor trade and the crossover trade. Then verifies:

    1. predecessor.timestamp < midnight  (belongs to yesterday)
    2. crossover.timestamp >= midnight   (belongs to today)
    3. crossover.agg_trade_id == predecessor.agg_trade_id + 1  (Stathera continuity)

    Logs INFO on pass, WARNING on any violation. Never raises -- validation
    is advisory (the crossover is used regardless, but violations are loud).
    """
    from opendeviationbar._core import fetch_aggtrades_by_id

    try:
        pair_size = 2  # predecessor + crossover
        trades = fetch_aggtrades_by_id(symbol, crossover_tid - 1, pair_size)
        if not trades or len(trades) < pair_size:
            logger.info(
                "crossover for %s: tid=%d (%s) -- "
                "self-validation skipped (< 2 trades returned, likely first trade ever)",
                symbol,
                crossover_tid,
                source,
            )
            return

        pred = trades[0]
        cross = trades[1]

        pred_tid = pred.get("agg_trade_id", pred.get("a"))
        pred_ts = pred.get("timestamp", pred.get("T", 0))
        cross_tid = cross.get("agg_trade_id", cross.get("a"))
        cross_ts = cross.get("timestamp", cross.get("T", 0))

        # Property 1: predecessor is yesterday
        prop1 = pred_ts < midnight_ms
        # Property 2: crossover is today
        prop2 = cross_ts >= midnight_ms
        # Property 3: consecutive trade IDs (Stathera at day boundary)
        prop3 = cross_tid == pred_tid + 1

        if prop1 and prop2 and prop3:
            logger.info(
                "crossover for %s: tid=%d (%s) -- "
                "VALIDATED: pred_tid=%d ts=%d < midnight, cross_tid=%d ts=%d >= midnight, consecutive",
                symbol,
                crossover_tid,
                source,
                pred_tid,
                pred_ts,
                cross_tid,
                cross_ts,
            )
        else:
            violations = []
            if not prop1:
                violations.append(f"pred.ts={pred_ts} >= midnight (NOT yesterday)")
            if not prop2:
                violations.append(f"cross.ts={cross_ts} < midnight (NOT today)")
            if not prop3:
                violations.append(
                    f"cross_tid={cross_tid} != pred_tid+1={pred_tid + 1} (GAP)"
                )
            logger.warning(
                "crossover for %s: tid=%d (%s) -- "
                "VALIDATION FAILED: %s",
                symbol,
                crossover_tid,
                source,
                "; ".join(violations),
            )
    except Exception:
        logger.debug(
            "crossover self-validation failed for %s (tid=%d)",
            symbol,
            crossover_tid,
            exc_info=True,
        )


def _ch_date_last_tid(symbol: str, date: datetime.date) -> int | None:
    """Query CH for MAX(last_agg_trade_id) on the day BEFORE the given date.

    Returns None if no data exists for that day (fresh symbol, post-OPTIMIZE, etc).
    """
    from datetime import timedelta

    prior_date = date - timedelta(days=1)

    try:
        from opendeviationbar.clickhouse.cache import OpenDeviationBarCache

        with OpenDeviationBarCache() as cache:
            result = cache.client.query(
                "SELECT max(last_agg_trade_id) "
                "FROM opendeviationbar_cache.open_deviation_bars FINAL "
                "WHERE symbol = %(symbol)s "
                "  AND toDate(close_time_us / 1000000) = %(target_date)s "
                "  AND last_agg_trade_id > 0",
                parameters={"symbol": symbol, "target_date": str(prior_date)},
            )
            rows = result.result_rows
            if rows and rows[0][0] and rows[0][0] > 0:
                return int(rows[0][0])
    except Exception:
        logger.debug("CH date query failed for %s on %s", symbol, prior_date, exc_info=True)
    return None


def _verify_crossover_from_seed(
    symbol: str, seed_tid: int, midnight_ms: int
) -> int | None:
    """Verify a CH-seeded TID by fetching fromId=seed+1 from Binance REST.

    If the seed is the prior day's exact last TID, the returned trade IS the
    crossover (timestamp >= midnight). If the seed is slightly early (e.g.,
    orphan bar missing), the first trade might still be before midnight --
    in that case we return None and let Tier 2 handle it.

    Exactly 1 REST call. Source of truth: Binance.
    """
    from opendeviationbar._core import fetch_aggtrades_by_id

    try:
        trades = fetch_aggtrades_by_id(symbol, seed_tid + 1, 10)
        if not trades:
            return None

        first = trades[0]
        ts = first.get("timestamp", first.get("T", 0))
        if ts >= midnight_ms:
            return first.get("agg_trade_id", first.get("a"))

        # Seed was slightly early -- trades are still before midnight. Tier 2 will fix.
        return None  # noqa: TRY300
    except Exception:
        logger.debug(
            "fromId verify failed for %s (seed=%d)", symbol, seed_tid, exc_info=True
        )
        return None


def _api_max_post_midnight(symbol: str, midnight_ms: int) -> int | None:
    """Query the first trade after midnight using the API's maximum time range.

    Binance aggTrades API constraint: startTime-to-endTime must be < 1 hour.
    We use exactly that limit: [midnight, midnight + 3_600_000). The first
    element of the result is the first trade of today.

    This is parameter-free: the 1-hour window is the API's own maximum,
    not a value we chose.
    """
    from opendeviationbar._core import fetch_aggtrades_rest

    # 3_600_000 ms = 1 hour = Binance aggTrades API max time range
    binance_api_max_range_ms = 3_600_000

    try:
        trades = fetch_aggtrades_rest(
            symbol, midnight_ms, midnight_ms + binance_api_max_range_ms
        )
        if trades:
            return trades[0].get("agg_trade_id", trades[0].get("a"))
    except Exception:
        logger.debug("API-max post-midnight query failed for %s", symbol, exc_info=True)

    return None
