"""상승 추세 중 눌림목 매수 타점 (Ground Truth 보조)."""

from __future__ import annotations

import pandas as pd

from typing import Protocol

from deepcoin.ground_truth.zigzag import Pivot


class _LegLike(Protocol):
    """레그 최소 인터페이스."""

    buy_bar_index: int
    sell_bar_index: int


def find_pullback_buy_pivots(
    df: pd.DataFrame,
    legs: list[_LegLike],
    min_pullback_pct: float = 1.5,
    local_order: int = 10,
    swing_near_bars: int = 120,
    leg_end_margin_bars: int = 100,
) -> list[Pivot]:
    """각 스윙 레그 구간에서 돌파 직전 최적 눌림목 1개를 찾는다 (1단계 GT).

    빨간 원 유형(9/27, 7/8 등) — 스윙 매수 후·매도 전 구간의
    가장 깊은 되돌림 저점을 레그당 1개만 선택한다.

    Args:
        df: OHLCV DataFrame.
        legs: ZigZag 스윙 레그.
        min_pullback_pct: 직전 고점 대비 최소 되돌림(%).
        local_order: 국소 저점 판별 반경(봉).
        swing_near_bars: 스윙 매수 직후 구간 제외(봉).
        leg_end_margin_bars: 매도 직전 구간 제외(봉).

    Returns:
        시간순 Pivot(low) 리스트.
    """
    if not legs or len(df) < local_order * 2 + 10:
        return []

    highs = df["high"].astype(float).values
    lows = df["low"].astype(float).values
    pullbacks: list[Pivot] = []

    for leg in legs:
        full_start = leg.buy_bar_index + swing_near_bars
        full_end = leg.sell_bar_index - leg_end_margin_bars
        if full_end <= full_start + local_order * 2:
            continue

        leg_len = leg.sell_bar_index - leg.buy_bar_index
        pre_breakout_start = leg.sell_bar_index - int(leg_len * 0.35)
        start = max(full_start, pre_breakout_start)
        end = full_end

        best_idx = _best_pullback_in_range(
            df, lows, highs, leg.buy_bar_index, start, end, local_order, min_pullback_pct
        )
        if best_idx is None:
            best_idx = _best_pullback_in_range(
                df, lows, highs, leg.buy_bar_index, full_start, full_end, local_order, min_pullback_pct
            )
        if best_idx is None:
            continue

        pullbacks.append(
            Pivot(
                bar_index=best_idx,
                side="low",
                price=float(lows[best_idx]),
                datetime=pd.Timestamp(df.iloc[best_idx]["datetime"]),
            )
        )

    return pullbacks


def _best_pullback_in_range(
    df: pd.DataFrame,
    lows,
    highs,
    buy_bar_index: int,
    start: int,
    end: int,
    local_order: int,
    min_pullback_pct: float,
) -> int | None:
    """구간 내 최대 되돌림 국소 저점 bar_index를 반환한다."""
    if end <= start + local_order * 2:
        return None

    best_idx: int | None = None
    best_price = float("inf")

    for i in range(max(start, local_order), min(end, len(df) - local_order)):
        low_val = float(lows[i])
        window = lows[i - local_order : i + local_order + 1]
        if low_val > float(window.min()):
            continue

        region_high = float(highs[buy_bar_index : i + 1].max())
        if region_high <= 0:
            continue

        pullback_pct = (region_high - low_val) / region_high * 100.0
        if pullback_pct < min_pullback_pct:
            continue

        if low_val < best_price:
            best_price = low_val
            best_idx = i

    return best_idx
