"""Breakout signal generators: ATR-based."""

from __future__ import annotations

import numpy as np

from pyalbedo.core.types import Direction, Signal
from pyalbedo.core.window import MarketDataWindow
from pyalbedo.indicators.volatility import ATR
from pyalbedo.signals.base import SignalGenerator


class ATRBreakout(SignalGenerator):
    """Long when close > recent_high - k*ATR (breakout up); short when close < recent_low + k*ATR."""

    def __init__(
        self,
        atr_period: int = 14,
        lookback: int = 20,
        k: float = 1.0,
    ) -> None:
        if atr_period < 1 or lookback < 1 or k <= 0:
            raise ValueError("atr_period and lookback >= 1, k > 0")
        self._atr_period = atr_period
        self._lookback = lookback
        self._k = k
        self._atr = ATR(period=atr_period)

    @property
    def name(self) -> str:
        return f"ATRBreakout({self._atr_period},{self._lookback},{self._k})"

    @property
    def required_lookback(self) -> int:
        return max(self._atr.lookback, self._lookback) + 1

    def generate(self, window: MarketDataWindow) -> Signal | None:
        if len(window) < self.required_lookback:
            return None
        atr_series = self._atr.calculate(window.high, window.low, window.close)
        atr = atr_series[-1]
        if np.isnan(atr) or atr <= 0:
            return None
        recent_high = np.max(window.high[-self._lookback :])
        recent_low = np.min(window.low[-self._lookback :])
        close = window.close[-1]
        upper_break = recent_high - self._k * atr
        lower_break = recent_low + self._k * atr
        if close > upper_break:
            strength = (close - upper_break) / atr if atr > 0 else 0
            strength = min(1.0, max(0.0, strength))
            return Signal(
                direction=Direction.LONG,
                strength=strength,
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={
                    "close": float(close),
                    "recent_high": float(recent_high),
                    "atr": float(atr),
                },
            )
        if close < lower_break:
            strength = (lower_break - close) / atr if atr > 0 else 0
            strength = min(1.0, max(0.0, strength))
            return Signal(
                direction=Direction.SHORT,
                strength=strength,
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={
                    "close": float(close),
                    "recent_low": float(recent_low),
                    "atr": float(atr),
                },
            )
        return None
