#!/usr/bin/env python3
"""Trend pullback fade entry."""

from __future__ import annotations

import time
import uuid
from typing import Any

from signal_generators.base import BaseSignalGenerator

try:
    from src.price_digits import round_price as _round_price
except ImportError:
    def _round_price(price, **_kwargs):
        return round(float(price), 2)


class PullbackFadeSignal(BaseSignalGenerator):
    """Pullback completion entry in trending markets."""

    SCHEMA_VERSION = "2.1.0"

    DEFAULT_PARAMS: dict[str, Any] = {
        "adx_threshold": 25,
        "ema_periods": [9, 21, 50],
        "fib_min": 0.38,
        "fib_max": 0.50,
        "swing_lookback": 20,
        "momentum_bars": 5,
        "atr_period": 14,
        "sl_atr_mult": 1.5,
        "tp_atr_mult": 2.5,
        "min_confidence": 0.55,
        "min_bars": 80,
    }

    @staticmethod
    def _ema(data: list[float], period: int) -> list[float | None]:
        if len(data) < period:
            return [None] * len(data)
        k = 2.0 / (period + 1)
        result = [None] * (period - 1) + [sum(data[:period]) / period]
        for value in data[period:]:
            result.append(value * k + result[-1] * (1 - k))
        return result

    @staticmethod
    def _atr(bars: list[dict], period: int = 14) -> float:
        if len(bars) < period + 1:
            return 0.0
        trs = [
            max(
                bars[i]["high"] - bars[i]["low"],
                abs(bars[i]["high"] - bars[i - 1]["close"]),
                abs(bars[i]["low"] - bars[i - 1]["close"]),
            )
            for i in range(1, len(bars))
        ]
        return sum(trs[-period:]) / period

    def analyze(self, bars: list[dict], params: dict | None = None) -> dict[str, Any]:
        _llm_dir = str((params or {}).get("_llm_direction", "") or "").upper()
        _llm_conf = float((params or {}).get("_llm_confidence", 0.0) or 0.0)
        _llm_active = _llm_dir in {"BUY", "SELL"} and _llm_conf >= 0.4

        p = {**self.DEFAULT_PARAMS, **(params or {})}
        trace_id = f"pullback_{uuid.uuid4().hex[:12]}"
        if len(bars) < p["min_bars"]:
            return self._hold(trace_id, "insufficient_bars", {})

        closes = [float(b["close"]) for b in bars]
        highs = [float(b["high"]) for b in bars]
        lows = [float(b["low"]) for b in bars]
        current_price = closes[-1]

        ema9 = self._ema(closes, 9)
        ema21 = self._ema(closes, 21)
        ema50 = self._ema(closes, 50)
        if ema9[-1] is None or ema21[-1] is None or ema50[-1] is None:
            return self._hold(trace_id, "insufficient_ema", {})

        bullish_trend = ema9[-1] > ema21[-1] > ema50[-1]
        bearish_trend = ema9[-1] < ema21[-1] < ema50[-1]
        if not bullish_trend and not bearish_trend:
            return self._hold(trace_id, "no_ema_alignment", {})

        swing_lookback = int(p["swing_lookback"])
        swing_high = max(highs[-swing_lookback:])
        swing_low = min(lows[-swing_lookback:])
        swing_range = swing_high - swing_low
        if swing_range <= 0:
            return self._hold(trace_id, "no_swing_range", {})

        recent_pivot_high = highs[-1]
        recent_pivot_low = lows[-1]
        lower_bound = max(len(highs) - swing_lookback - 1, 1)
        for idx in range(len(highs) - 2, lower_bound - 1, -1):
            if highs[idx] > highs[idx - 1] and highs[idx] > highs[idx + 1]:
                recent_pivot_high = highs[idx]
                break
        for idx in range(len(lows) - 2, lower_bound - 1, -1):
            if lows[idx] < lows[idx - 1] and lows[idx] < lows[idx + 1]:
                recent_pivot_low = lows[idx]
                break

        if bullish_trend and recent_pivot_high > swing_high:
            swing_high = recent_pivot_high
        elif bearish_trend and recent_pivot_low < swing_low:
            swing_low = recent_pivot_low

        swing_range = swing_high - swing_low
        if swing_range <= 0:
            return self._hold(trace_id, "no_swing_range", {})

        atr_value = self._atr(bars, int(p["atr_period"]))
        if atr_value <= 0:
            return self._hold(trace_id, "no_atr", {})

        action = "HOLD"
        side = "NONE"
        reason = ""
        momentum_bars = int(p["momentum_bars"])

        if bullish_trend:
            retrace = (swing_high - current_price) / swing_range
            if p["fib_min"] <= retrace <= p["fib_max"]:
                recent_range = max(highs[-momentum_bars:]) - min(lows[-momentum_bars:])
                prev_range = (
                    max(highs[-momentum_bars * 2:-momentum_bars]) - min(lows[-momentum_bars * 2:-momentum_bars])
                    if len(highs) >= momentum_bars * 2
                    else recent_range
                )
                if recent_range < prev_range * 0.8:
                    action = "BUY"
                    side = "BUY"
                    reason = f"Bullish pullback {retrace:.0%} fade"
        elif bearish_trend:
            retrace = (current_price - swing_low) / swing_range
            if p["fib_min"] <= retrace <= p["fib_max"]:
                recent_range = max(highs[-momentum_bars:]) - min(lows[-momentum_bars:])
                prev_range = (
                    max(highs[-momentum_bars * 2:-momentum_bars]) - min(lows[-momentum_bars * 2:-momentum_bars])
                    if len(highs) >= momentum_bars * 2
                    else recent_range
                )
                if recent_range < prev_range * 0.8:
                    action = "SELL"
                    side = "SELL"
                    reason = f"Bearish pullback {retrace:.0%} fade"

        if action == "HOLD":
            return self._hold(trace_id, "no_pullback_setup", {})

        confidence = min(0.80, p["min_confidence"] + 0.10)
        if side == "BUY" and _llm_active and _llm_dir == "SELL":
            confidence = max(0.30, confidence * 0.5)
        elif side == "SELL" and _llm_active and _llm_dir == "BUY":
            confidence = max(0.30, confidence * 0.5)

        entry = current_price
        sl = (
            _round_price(entry - atr_value * p["sl_atr_mult"])
            if side == "BUY"
            else _round_price(entry + atr_value * p["sl_atr_mult"])
        )
        tp = (
            _round_price(entry + atr_value * p["tp_atr_mult"])
            if side == "BUY"
            else _round_price(entry - atr_value * p["tp_atr_mult"])
        )

        return {
            "schema_version": self.SCHEMA_VERSION,
            "trace_id": trace_id,
            "strategy_id": self.get_strategy_id(),
            "model_version": None,
            "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S+08:00"),
            "payload": {
                "action": action,
                "side": side,
                "entry": round(entry, 3),
                "sl": sl,
                "tp": tp,
                "confidence": round(confidence, 3),
                "reason": reason,
                "indicators": {
                    "ema9": round(ema9[-1], 2),
                    "ema21": round(ema21[-1], 2),
                    "ema50": round(ema50[-1], 2),
                    "swing_high": swing_high,
                    "swing_low": swing_low,
                    "atr": round(atr_value, 3),
                },
            },
        }

    def _hold(self, trace_id: str, reason: str, indicators: dict) -> dict:
        return {
            "schema_version": self.SCHEMA_VERSION,
            "trace_id": trace_id,
            "strategy_id": self.get_strategy_id(),
            "model_version": None,
            "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S+08:00"),
            "payload": {
                "action": "HOLD",
                "confidence": 0.0,
                "reason": reason,
                "indicators": indicators,
            },
        }

    def get_strategy_id(self) -> str:
        return "pullback_fade"

    def get_params_schema(self) -> dict:
        return {
            "fib_min": {"type": "float", "default": 0.38, "range": [0.30, 0.50]},
            "fib_max": {"type": "float", "default": 0.50, "range": [0.45, 0.62]},
            "swing_lookback": {"type": "int", "default": 20, "range": [10, 40]},
            "momentum_bars": {"type": "int", "default": 5, "range": [3, 10]},
            "sl_atr_mult": {"type": "float", "default": 1.5, "range": [1.0, 2.5]},
            "tp_atr_mult": {"type": "float", "default": 2.5, "range": [1.5, 4.0]},
        }
