#!/usr/bin/env python3
"""Single-job cache population with date bounds (Issue #158 Phase 9).

For historical backfill across multiple years, use kintsugi catch-up mode:
    mise run kintsugi:catchup

This script is for bounded single-job population only (max 366 days).

Usage:
    uv run python scripts/populate_full_cache.py \
        --symbol BTCUSDT --threshold 250 \
        --start-date 2025-01-01 --end-date 2025-12-31

    uv run python scripts/populate_full_cache.py --status
"""

from __future__ import annotations

import argparse
import logging
import sys
from datetime import UTC, datetime

logger = logging.getLogger(__name__)


def _get_symbols() -> dict[str, str]:
    """Get symbol -> start_date mapping from registry SSoT.

    Raises
    ------
    RuntimeError
        If the symbol registry cannot be loaded. Never falls back silently.
    """
    from opendeviationbar.symbol_registry import (
        get_effective_start_date,
        get_registered_symbols,
    )

    symbols = {}
    registered = get_registered_symbols(asset_class="crypto")
    if not registered:
        msg = (
            "Symbol registry returned 0 crypto symbols. "
            "Is symbols.toml present and maturin develop run?"
        )
        raise RuntimeError(msg)
    skipped = []
    for symbol in registered:
        start = get_effective_start_date(symbol)
        if start:
            symbols[symbol] = start
        else:
            skipped.append(symbol)
    if skipped:
        logger.warning(
            "Symbols without effective_start (skipped): %s", ", ".join(skipped)
        )
    return symbols


SYMBOLS = _get_symbols()

_MAX_DAYS = 366  # SSoT-OK: intentional safety guard


def populate_job(
    symbol: str,
    threshold: int,
    start_date: str,
    *,
    end_date: str,
    force_refresh: bool = False,
    include_microstructure: bool = True,
    ouroboros_mode: str | None = None,
    max_days: int = _MAX_DAYS,
    market: str | None = None,
) -> int:
    """Run a single population job. Returns bar count."""
    from opendeviationbar import populate_cache_resumable

    logger.info(
        "Starting: %s @ %d dbps from %s to %s (force_refresh=%s, microstructure=%s)",
        symbol,
        threshold,
        start_date,
        end_date,
        force_refresh,
        include_microstructure,
    )
    start_time = datetime.now(tz=UTC)

    try:
        bars = populate_cache_resumable(
            symbol,
            start_date,
            end_date,
            threshold_decimal_bps=threshold,
            include_microstructure=include_microstructure,
            force_refresh=force_refresh,
            ouroboros_mode=ouroboros_mode,
            notify=True,
            max_days=max_days,
            market=market,
        )
        elapsed = (datetime.now(tz=UTC) - start_time).total_seconds() / 60
        logger.info(
            "Completed: %s @ %d - %d bars in %.1f min",
            symbol,
            threshold,
            bars,
            elapsed,
        )
        return bars
    except Exception:
        logger.exception("Failed: %s @ %d", symbol, threshold)
        raise


def show_status() -> None:
    """Show current cache status."""
    try:
        from opendeviationbar.clickhouse import OpenDeviationBarCache

        with OpenDeviationBarCache() as cache:
            result = cache.client.query("""
                SELECT symbol, threshold_decimal_bps, count(*) as bars,
                       formatDateTime(fromUnixTimestamp64Milli(min(close_time_ms)), '%Y-%m-%d', 'UTC') as earliest,
                       formatDateTime(fromUnixTimestamp64Milli(max(close_time_ms)), '%Y-%m-%d', 'UTC') as latest
                FROM opendeviationbar_cache.open_deviation_bars FINAL
                GROUP BY symbol, threshold_decimal_bps
                ORDER BY symbol, threshold_decimal_bps
            """)

            cached = {
                (row[0], row[1]): (row[2], row[3], row[4]) for row in result.result_rows
            }

        print("\n" + "=" * 90)
        print("CACHE STATUS")
        print("=" * 90)
        print(
            f"\n{'Symbol':<12} {'Thresh':<8} {'Status':<12} {'Bars':>12} {'Coverage':<25}"
        )
        print("-" * 90)

        from opendeviationbar.symbol_registry import get_thresholds_for_symbol

        all_thresholds = sorted({
            t for s in SYMBOLS for t in get_thresholds_for_symbol(s)
        })
        completed = 0
        remaining = 0

        for symbol in SYMBOLS:
            for thresh in all_thresholds:
                if (symbol, thresh) in cached:
                    bars, earliest, latest = cached[(symbol, thresh)]
                    status = "Done"
                    coverage = f"{earliest} to {latest}"
                    completed += 1
                else:
                    bars = 0
                    status = "Pending"
                    coverage = "-"
                    remaining += 1

                print(
                    f"{symbol:<12} {thresh:<8} {status:<12} {bars:>12,} {coverage:<25}"
                )

        total = completed + remaining
        print("-" * 90)
        print(
            f"Completed: {completed}/{total} jobs | Remaining: {remaining}/{total} jobs"
        )
        print()

    except (ImportError, ConnectionError, OSError, RuntimeError):
        logger.exception("Could not connect to ClickHouse")


def main() -> None:
    """Main entry point."""
    from opendeviationbar.logging import setup_service_logging

    setup_service_logging("populate-cache")

    parser = argparse.ArgumentParser(
        description="Populate opendeviationbar cache (bounded, single-job)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s --status                                      # Show current cache status
  %(prog)s --symbol BTCUSDT --threshold 250 \\
           --start-date 2025-01-01 --end-date 2025-12-31 # Single bounded job

For full historical backfill, use: mise run kintsugi:catchup
        """,
    )
    parser.add_argument(
        "--symbol", type=str, help="Symbol (requires --threshold, --start-date, --end-date)"
    )
    parser.add_argument(
        "--threshold", type=int, help="Threshold in dbps (requires --symbol)"
    )
    parser.add_argument(
        "--start-date",
        type=str,
        required=False,
        help="Start date (YYYY-MM-DD). REQUIRED for population jobs.",
    )
    parser.add_argument(
        "--end-date",
        type=str,
        required=False,
        help="End date (YYYY-MM-DD). REQUIRED for population jobs.",
    )
    parser.add_argument("--status", action="store_true", help="Show cache status")
    parser.add_argument(
        "--force-refresh",
        action="store_true",
        help="Wipe existing cache and checkpoint, start fresh",
    )
    parser.add_argument(
        "--include-microstructure",
        action="store_true",
        default=True,
        help="Include microstructure features (default: True)",
    )
    parser.add_argument(
        "--no-microstructure",
        action="store_true",
        help="Disable microstructure features",
    )
    parser.add_argument(
        "--ouroboros",
        type=str,
        default=None,
        choices=["day"],
        help="Ouroboros reset period (default: from OPENDEVIATIONBAR_OUROBOROS_MODE).",
    )
    parser.add_argument(
        "--max-days",
        type=int,
        default=_MAX_DAYS,
        help=f"Maximum allowed date range in days (default: {_MAX_DAYS})",
    )
    parser.add_argument(
        "--market",
        type=str,
        default=None,
        choices=["spot", "futures-um", "futures-cm", "um", "cm"],
        help="Market type override (default: auto-resolve from symbols.toml)",
    )

    args = parser.parse_args()

    include_micro = args.include_microstructure and not args.no_microstructure

    if args.status:
        show_status()
        return

    # Require all four parameters for population jobs
    if not (args.symbol and args.threshold and args.start_date and args.end_date):
        parser.error(
            "--symbol, --threshold, --start-date, and --end-date are all required. "
            "For full historical backfill, use: mise run kintsugi:catchup"
        )

    if args.symbol not in SYMBOLS:
        logger.error("Unknown symbol: %s", args.symbol)
        sys.exit(1)

    populate_job(
        args.symbol,
        args.threshold,
        args.start_date,
        end_date=args.end_date,
        force_refresh=args.force_refresh,
        include_microstructure=include_micro,
        ouroboros_mode=args.ouroboros,
        max_days=args.max_days,
        market=args.market,
    )


if __name__ == "__main__":
    main()
