"""
05/06 운영 시작 전 coins.db 누락 봉을 증분 보완합니다.

마지막 저장 시각이 현재보다 OPS_SYNC_MAX_LAG_BARS 이상 뒤처진 간격만
01_download.download_symbol 과 동일 경로로 API 수집합니다.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime

import pandas as pd

from config import (
    COIN_NAME,
    DB_PATH,
    DOWNLOAD_MONTHS,
    OPS_SYNC_MAX_LAG_BARS,
    OPS_SYNC_ON_START,
    SYMBOL,
)
from deepcoin.data.downloader import (
    download_jobs,
    download_symbol,
    get_last_timestamp,
    get_row_count,
    interval_label,
)
from deepcoin.ops.monitor import Monitor

# 프로세스당 1회 상세 로그(루프마다 ensure 호출 시 스팸 방지)
_logged_fresh_once: bool = False


@dataclass
class OpsSyncResult:
    """운영 전 DB 동기화 결과."""

    synced: list[int] = field(default_factory=list)
    skipped_fresh: list[int] = field(default_factory=list)
    errors: dict[int, str] = field(default_factory=dict)
    disabled: bool = False

    @property
    def ok(self) -> bool:
        """치명적 오류 없이 완료."""
        return not self.errors or len(self.synced) > 0 or len(self.skipped_fresh) > 0


def lag_minutes_since_last(
    last_ts: pd.Timestamp | None, interval_minutes: int
) -> float | None:
    """
    마지막 봉 시각 대비 경과 분.

    Returns:
        last_ts 없으면 None.
    """
    if last_ts is None:
        return None
    now = pd.Timestamp.now()
    last = pd.Timestamp(last_ts)
    if last.tzinfo is not None and now.tzinfo is None:
        last = last.tz_localize(None)
    return max(0.0, (now - last).total_seconds() / 60.0)


def is_interval_stale(
    symbol: str,
    interval: int,
    *,
    max_lag_bars: int = OPS_SYNC_MAX_LAG_BARS,
    db_path: str = DB_PATH,
) -> tuple[bool, str]:
    """
    간격별 DB가 운영에 필요한 최신성을 만족하는지 판단.

    Returns:
        (stale 여부, 사유 문자열)
    """
    rows = get_row_count(symbol, interval, db_path=db_path)
    last = get_last_timestamp(symbol, interval, db_path=db_path)
    if rows == 0 or last is None:
        return True, "데이터 없음"
    lag = lag_minutes_since_last(last, interval)
    if lag is None:
        return True, "마지막 시각 없음"
    threshold = float(interval * max_lag_bars)
    if lag > threshold:
        return True, f"지연 {lag:.0f}분 > 허용 {threshold:.0f}분"
    return False, f"최신 (마지막 {last.strftime('%Y-%m-%d %H:%M:%S')})"


def list_stale_intervals(
    symbol: str = SYMBOL,
    *,
    max_lag_bars: int = OPS_SYNC_MAX_LAG_BARS,
    db_path: str = DB_PATH,
) -> list[tuple[int, str, str]]:
    """
    갱신이 필요한 (interval, label, reason) 목록.

    Returns:
        download_jobs 순서와 동일하게 stale 항목만.
    """
    out: list[tuple[int, str, str]] = []
    for interval, label in download_jobs():
        stale, reason = is_interval_stale(
            symbol, interval, max_lag_bars=max_lag_bars, db_path=db_path
        )
        if stale:
            out.append((interval, label, reason))
    return out


def ensure_ops_candles(
    symbol: str = SYMBOL,
    months: int | None = None,
    *,
    force: bool = False,
    verbose_download: bool = False,
) -> OpsSyncResult:
    """
    05/06 실행 직전 누락·지연 봉을 coins.db에 증분 적재합니다.

    Args:
        symbol: 코인 코드.
        months: 보관 개월( None 이면 DOWNLOAD_MONTHS ).
        force: True면 최신 간격도 전부 재수집.
        verbose_download: True면 download_symbol API 진행 로그 출력.

    Returns:
        OpsSyncResult
    """
    global _logged_fresh_once
    result = OpsSyncResult()
    if not OPS_SYNC_ON_START and not force:
        result.disabled = True
        print("[ops_sync] OPS_SYNC_ON_START=0 — 건너뜀")
        return result

    months = months or DOWNLOAD_MONTHS
    jobs = download_jobs()
    stale = list_stale_intervals(symbol) if not force else [
        (iv, lb, "force") for iv, lb in jobs
    ]
    fresh = [
        iv for iv, _ in jobs if iv not in {s[0] for s in stale}
    ]
    result.skipped_fresh = fresh

    if not stale:
        if not _logged_fresh_once or force:
            print(
                f"[ops_sync] {COIN_NAME} ({symbol}) DB 최신 · "
                f"간격 {len(fresh)}개 (증분 다운로드 없음)"
            )
            for iv in fresh:
                last = get_last_timestamp(symbol, iv)
                if last is not None:
                    print(f"  [{interval_label(iv)}] ~ {last}")
            _logged_fresh_once = True
        return result

    print(
        f"[ops_sync] {COIN_NAME} ({symbol}) 누락 보완 · "
        f"갱신 {len(stale)}개 / 최신 {len(fresh)}개 (백필 생략·증분만)"
    )
    monitor = Monitor(cooldown_file=None)
    started = datetime.now()
    for interval, label, reason in stale:
        print(f"\n[ops_sync] --- {label} --- ({reason})")
        try:
            download_symbol(
                monitor,
                symbol,
                interval,
                months,
                verbose=verbose_download,
                skip_backfill=True,
            )
            result.synced.append(interval)
        except Exception as exc:
            result.errors[interval] = str(exc)
            print(f"  [ops_sync] 오류 interval={interval}: {exc}")

    elapsed = datetime.now() - started
    _logged_fresh_once = True
    print(f"\n[ops_sync] 완료 (소요: {elapsed}) · 갱신={result.synced} 오류={len(result.errors)}")
    return result
