"""Crossover signal generators: MA, MACD, Stochastic."""

from __future__ import annotations

import numpy as np

from pyalbedo.core.types import Direction, Signal
from pyalbedo.core.window import MarketDataWindow
from pyalbedo.indicators.moving_averages import EMA
from pyalbedo.indicators.oscillators import MACD, Stochastic
from pyalbedo.signals.base import SignalGenerator


class MACrossover(SignalGenerator):
    """Long when fast MA crosses above slow MA; short when crosses below."""

    def __init__(self, fast_period: int = 12, slow_period: int = 26) -> None:
        if fast_period >= slow_period or fast_period < 1 or slow_period < 1:
            raise ValueError("fast_period < slow_period and both >= 1")
        self._fast_period = fast_period
        self._slow_period = slow_period
        self._ema_fast = EMA(fast_period)
        self._ema_slow = EMA(slow_period)

    @property
    def name(self) -> str:
        return f"MACrossover({self._fast_period},{self._slow_period})"

    @property
    def required_lookback(self) -> int:
        return self._slow_period + 2

    def generate(self, window: MarketDataWindow) -> Signal | None:
        if len(window) < self.required_lookback:
            return None
        close = window.close
        fast = self._ema_fast.calculate(close)
        slow = self._ema_slow.calculate(close)
        if np.isnan(fast[-2]) or np.isnan(slow[-2]) or np.isnan(fast[-1]) or np.isnan(slow[-1]):
            return None
        crossed_above = fast[-2] <= slow[-2] and fast[-1] > slow[-1]
        crossed_below = fast[-2] >= slow[-2] and fast[-1] < slow[-1]
        if crossed_above:
            separation = (fast[-1] - slow[-1]) / slow[-1] if slow[-1] != 0 else 0
            strength = min(1.0, abs(separation) * 100)
            return Signal(
                direction=Direction.LONG,
                strength=strength,
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={
                    "fast_ema": float(fast[-1]),
                    "slow_ema": float(slow[-1]),
                    "separation_pct": float(separation * 100),
                },
            )
        if crossed_below:
            separation = (slow[-1] - fast[-1]) / slow[-1] if slow[-1] != 0 else 0
            strength = min(1.0, abs(separation) * 100)
            return Signal(
                direction=Direction.SHORT,
                strength=strength,
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={
                    "fast_ema": float(fast[-1]),
                    "slow_ema": float(slow[-1]),
                    "separation_pct": float(separation * 100),
                },
            )
        return None


class MACDCrossover(SignalGenerator):
    """Long when MACD line crosses above signal line; short when below."""

    def __init__(
        self,
        fast_period: int = 12,
        slow_period: int = 26,
        signal_period: int = 9,
    ) -> None:
        self._macd = MACD(fast_period=fast_period, slow_period=slow_period, signal_period=signal_period)

    @property
    def name(self) -> str:
        return "MACDCrossover"

    @property
    def required_lookback(self) -> int:
        return self._macd.lookback + 2

    def generate(self, window: MarketDataWindow) -> Signal | None:
        if len(window) < self.required_lookback:
            return None
        macd_arr = self._macd.calculate(window.close)
        line = macd_arr[:, 0]
        signal = macd_arr[:, 1]
        if np.isnan(line[-2]) or np.isnan(signal[-2]) or np.isnan(line[-1]) or np.isnan(signal[-1]):
            return None
        crossed_above = line[-2] <= signal[-2] and line[-1] > signal[-1]
        crossed_below = line[-2] >= signal[-2] and line[-1] < signal[-1]
        if crossed_above:
            hist = line[-1] - signal[-1]
            strength = min(1.0, abs(hist) * 10)  # scale
            return Signal(
                direction=Direction.LONG,
                strength=strength,
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={"macd": float(line[-1]), "signal": float(signal[-1]), "histogram": float(hist)},
            )
        if crossed_below:
            hist = signal[-1] - line[-1]
            strength = min(1.0, abs(hist) * 10)
            return Signal(
                direction=Direction.SHORT,
                strength=strength,
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={"macd": float(line[-1]), "signal": float(signal[-1]), "histogram": float(-(signal[-1] - line[-1]))},
            )
        return None


class StochasticCrossover(SignalGenerator):
    """Long when %K crosses above %D; short when %K crosses below %D."""

    def __init__(self, k_period: int = 14, d_period: int = 3) -> None:
        self._stoch = Stochastic(k_period=k_period, d_period=d_period)

    @property
    def name(self) -> str:
        return "StochasticCrossover"

    @property
    def required_lookback(self) -> int:
        return self._stoch.lookback + 2

    def generate(self, window: MarketDataWindow) -> Signal | None:
        if len(window) < self.required_lookback:
            return None
        arr = self._stoch.calculate(window.high, window.low, window.close)
        k = arr[:, 0]
        d = arr[:, 1]
        if np.isnan(k[-2]) or np.isnan(d[-2]) or np.isnan(k[-1]) or np.isnan(d[-1]):
            return None
        crossed_above = k[-2] <= d[-2] and k[-1] > d[-1]
        crossed_below = k[-2] >= d[-2] and k[-1] < d[-1]
        if crossed_above:
            strength = min(1.0, (k[-1] - d[-1]) / 100.0)
            return Signal(
                direction=Direction.LONG,
                strength=max(0.0, strength),
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={"percent_k": float(k[-1]), "percent_d": float(d[-1])},
            )
        if crossed_below:
            strength = min(1.0, (d[-1] - k[-1]) / 100.0)
            return Signal(
                direction=Direction.SHORT,
                strength=max(0.0, strength),
                timestamp=window.last_timestamp,
                source=self.name,
                metadata={"percent_k": float(k[-1]), "percent_d": float(d[-1])},
            )
        return None
