# RESUME-04: Optimized Aion DELETE strategy with skip-if-clean pre-check
"""Aion DELETE strategy that skips no-op partitions.

Pre-checks ``max(last_agg_trade_id)`` per threshold partition BEFORE issuing
synchronous ``DELETE IN PARTITION`` mutations. Partitions where max_tid <= resume_tid
are no-ops and skipped entirely, avoiding ~8.5s synchronous partition scans each.
"""

from __future__ import annotations

import logging
import time
from collections.abc import Callable
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from opendeviationbar.clickhouse.cache import OpenDeviationBarCache

logger = logging.getLogger("opendeviationbar.sidecar")


def _aion_delete_after_resume(
    ch_cache: OpenDeviationBarCache,
    aion_symbols: list[str],
    resume_tids: dict[str, int | None],
    get_thresholds_fn: Callable[[str], list[int]],
    per_threshold_tids: dict[str, dict[int, int]] | None = None,
) -> dict[str, int | float]:
    """DELETE bars after resume_tid, skipping partitions that are already clean.

    Uses **per-threshold** resume TIDs for the skip-if-clean check and DELETE
    scope. Each threshold has its own committed TID — using a global max()
    across thresholds would skip trailing thresholds that still need cleanup,
    producing overlaps on restart.

    Parameters
    ----------
    ch_cache
        ClickHouse cache instance with ``.client.query()`` and ``.client.command()``.
    aion_symbols
        List of Aion-mode symbols to process.
    resume_tids
        Mapping of symbol -> global resume trade ID (fallback when per-threshold unavailable).
    get_thresholds_fn
        Callable(symbol) -> list[int] returning threshold_decimal_bps values.
    per_threshold_tids
        Mapping of symbol -> {threshold_dbps: last_committed_tid}. When available,
        each threshold uses its own TID for skip-if-clean and DELETE scope.

    Returns
    -------
    dict
        ``{"deleted": int, "skipped": int, "total": int, "elapsed": float}``
    """
    t_del_start = time.monotonic()
    deleted = 0
    skipped = 0
    total = 0
    if per_threshold_tids is None:
        per_threshold_tids = {}

    for sym in aion_symbols:
        global_resume = resume_tids.get(sym)
        if global_resume is None:
            continue

        sym_thr_tids = per_threshold_tids.get(sym, {})

        # Batch pre-check: one query per symbol, all thresholds at once
        result = ch_cache.client.query(
            "SELECT threshold_decimal_bps, max(last_agg_trade_id) AS max_tid"
            " FROM opendeviationbar_cache.open_deviation_bars FINAL"
            f" WHERE symbol = '{sym}' AND first_agg_trade_id > 0"
            " GROUP BY threshold_decimal_bps"
        )
        max_tids: dict[int, int] = {}
        for row in result.result_rows:
            max_tids[int(row[0])] = int(row[1])

        for thr in get_thresholds_fn(sym):
            total += 1
            max_tid = max_tids.get(thr)
            # Use per-threshold resume TID (not global max)
            resume_tid = sym_thr_tids.get(thr, global_resume)

            if max_tid is None or max_tid <= resume_tid:
                skipped += 1
                continue

            # Partition has bars beyond resume point — DELETE required.
            # Use last_agg_trade_id >= resume_tid (not first_agg_trade_id >)
            # to catch bars that SPAN the resume boundary.
            ch_cache.client.command(
                "ALTER TABLE opendeviationbar_cache.open_deviation_bars "
                f"DELETE IN PARTITION ('{sym}', {thr}) "
                f"WHERE last_agg_trade_id >= {resume_tid} "
                "SETTINGS mutations_sync = 1"
            )
            deleted += 1

    t_del_elapsed = time.monotonic() - t_del_start
    logger.info(
        "aion: DELETE after resume_tid — %d/%d partitions mutated (%d skipped) in %.1fs",
        deleted,
        total,
        skipped,
        t_del_elapsed,
    )

    return {
        "deleted": deleted,
        "skipped": skipped,
        "total": total,
        "elapsed": t_del_elapsed,
    }
