"""
多周期牛熊判定模块

基于 EMA200 斜率、价格 vs EMA200、ATH 回撤三法投票，支持单周期判定
和多周期并行分析。每根 bar O(1) 增量更新，适用于回测和实时分析。

用法:
    # 单周期
    det = RegimeDetector()
    for price in closes:
        det.update(price)
    regime = det.detect(closes[-1], len(closes) - 1)

    # 多周期
    mtf = MultiTimeframeRegime(['1h', '4h', '1d', '1w'])
    for price in closes:
        mtf.update(price)
    matrix = mtf.detect_all(closes[-1], len(closes) - 1)
"""

from .incremental import EmaInc


class RegimeDetector:
    """单周期牛熊判定器 — 四法投票

    四个独立判定维度：
        1. EMA200 斜率：近 20 根 bar 的 EMA200 变化率 → bull/bear/sideways
        2. 价格 vs EMA200：当前价在 EMA200 上方/下方 → bull/bear
        3. ATH 回撤：当前价距历史高点的回撤幅度 → bull/bear/sideways
        4. 窄幅盘整：近 20 根 bar 收盘价振幅 < 3% → sideways（否则 unknown，不投票）

    最终判定：四选二投票（>=2 票即定论）。
    """

    def __init__(self, range_period: int = 20, range_threshold: float = 0.03):
        self._ath = 0.0
        self._e200 = EmaInc(200)
        self._range_period = range_period
        self._range_threshold = range_threshold
        self._recent_high: list[float] = []
        self._recent_low: list[float] = []

    def update(self, price: float) -> None:
        """每根 bar 调一次：更新 ATH + EMA200 + 价格区间窗口"""
        if price > self._ath:
            self._ath = price
        self._e200.update(price)
        # 滚动窗口：维持最近 range_period 根 bar 的最高/最低（只用 close 做代理）
        self._recent_high.append(price)
        self._recent_low.append(price)
        if len(self._recent_high) > self._range_period:
            self._recent_high.pop(0)
            self._recent_low.pop(0)

    def detect(self, price: float, idx: int) -> str:
        """返回当前 bar 的牛熊判定：bull / bear / sideways"""
        r1 = self._ema200_slope(idx)
        r2 = self._price_vs_ema200(price, idx)
        r3 = self._ath_drawdown(price)
        r4 = self._price_range()

        b = sum(1 for r in [r1, r2, r3, r4] if r == "bull")
        br = sum(1 for r in [r1, r2, r3, r4] if r == "bear")
        sx = sum(1 for r in [r1, r2, r3, r4] if r == "sideways")

        if b >= 2:
            return "bull"
        if br >= 2:
            return "bear"
        if sx >= 2:
            return "sideways"
        return "sideways"

    def detect_detail(self, price: float, idx: int) -> dict:
        """返回详细判定（含各子法的结果 + 三态票数）"""
        r1 = self._ema200_slope(idx)
        r2 = self._price_vs_ema200(price, idx)
        r3 = self._ath_drawdown(price)
        r4 = self._price_range()
        b = sum(1 for r in [r1, r2, r3, r4] if r == "bull")
        br = sum(1 for r in [r1, r2, r3, r4] if r == "bear")
        sx = sum(1 for r in [r1, r2, r3, r4] if r == "sideways")
        if b >= 2:
            final = "bull"
        elif br >= 2:
            final = "bear"
        elif sx >= 2:
            final = "sideways"
        else:
            final = "sideways"
        return {
            "final": final,
            "ema200_slope": r1,
            "price_vs_ema200": r2,
            "ath_drawdown": r3,
            "price_range": r4,
            "bull_votes": b,
            "bear_votes": br,
            "sideways_votes": sx,
            "range_pct": self._range_pct(),
        }

    def _ema200_slope(self, idx: int) -> str:
        if idx < 220:
            return "unknown"
        e200 = self._e200
        if e200[idx - 20] == 0:
            return "unknown"
        slope = (e200[idx] - e200[idx - 20]) / e200[idx - 20]
        if slope > 0.002:
            return "bull"
        if slope < -0.002:
            return "bear"
        return "sideways"

    def _price_vs_ema200(self, price: float, idx: int) -> str:
        if idx < 210:
            return "unknown"
        e = self._e200[idx]
        if e == 0:
            return "unknown"
        return "bull" if price > e else "bear"

    def _ath_drawdown(self, price: float) -> str:
        if self._ath == 0:
            return "unknown"
        dd = (price - self._ath) / self._ath
        if dd > -0.20:
            return "bull"
        if dd < -0.40:
            return "bear"
        return "sideways"

    def _price_range(self) -> str:
        """窄幅盘整检测：近 range_period 根 bar 振幅 < 阈值 → sideways"""
        if len(self._recent_high) < self._range_period:
            return "unknown"
        highest = max(self._recent_high)
        lowest = min(self._recent_low)
        if highest == 0:
            return "unknown"
        range_pct = (highest - lowest) / highest
        return "sideways" if range_pct < self._range_threshold else "unknown"

    def _range_pct(self) -> float:
        """当前波动区间百分比（用于展示）"""
        if len(self._recent_high) < self._range_period:
            return 0.0
        highest = max(self._recent_high)
        if highest == 0:
            return 0.0
        return (highest - min(self._recent_low)) / highest

    @property
    def ema200(self) -> float:
        """最新 EMA200 值"""
        return self._e200.current

    @property
    def ath(self) -> float:
        """历史最高价"""
        return self._ath

    @property
    def ready(self) -> bool:
        """是否已有足够数据做判定"""
        return len(self._e200) >= 220


class MultiTimeframeRegime:
    """多周期牛熊并行分析

    管理多个 RegimeDetector 实例，每个对应一个周期。
    所有检测器共用同一价格序列更新，保证 O(1)/bar 的增量计算。

    Attributes:
        timeframes: 周期列表（如 ['1h', '4h', '1d', '1w']）
        detectors: 周期 → RegimeDetector 映射
        _prices: 价格序列（所有检测器共享）
    """

    def __init__(self, timeframes: list[str]):
        self.timeframes = timeframes
        self.detectors: dict[str, RegimeDetector] = {
            tf: RegimeDetector() for tf in timeframes
        }
        self._prices: list[float] = []

    def update(self, price: float) -> None:
        """更新所有周期检测器"""
        self._prices.append(price)
        for det in self.detectors.values():
            det.update(price)

    def detect_all(self) -> dict[str, str]:
        """返回各周期当前判定（需至少 220 根 bar）"""
        n = len(self._prices)
        if n < 220:
            return {tf: "unknown" for tf in self.timeframes}
        price = self._prices[-1]
        return {
            tf: self.detectors[tf].detect(price, n - 1)
            for tf in self.timeframes
        }

    def detect_all_detail(self) -> dict[str, dict]:
        """返回各周期详细判定"""
        n = len(self._prices)
        if n < 220:
            return {tf: {"final": "unknown"} for tf in self.timeframes}
        price = self._prices[-1]
        return {
            tf: self.detectors[tf].detect_detail(price, n - 1)
            for tf in self.timeframes
        }

    def detect_at(self, idx: int) -> dict[str, str]:
        """返回指定 bar 位置各周期的判定"""
        if idx < 0 or idx >= len(self._prices):
            raise IndexError(f"idx {idx} out of range [0, {len(self._prices)})")
        price = self._prices[idx]
        result = {}
        for tf in self.timeframes:
            if idx < 220:
                result[tf] = "unknown"
            else:
                result[tf] = self.detectors[tf].detect(price, idx)
        return result

    def score(self) -> float:
        """加权综合评分 [-1, 1]

        各周期赋分：bull=+1, sideways=0, bear=-1
        权重：周期越长权重越大

        Returns:
            -1.0 (强熊) ~ +1.0 (强牛)
        """
        n = len(self._prices)
        if n < 220:
            return 0.0

        # 周期权重：1h=1, 4h=2, 1d=3, 1w=4, 1mon=5
        tf_weights = {
            "1m": 1, "3m": 1, "5m": 1, "15m": 2, "30m": 2,
            "1h": 3, "2h": 3, "4h": 4, "6h": 4, "8h": 4,
            "1d": 5, "1w": 6, "1mon": 7,
        }

        score_map = {"bull": 1.0, "sideways": 0.0, "bear": -1.0, "unknown": 0.0}
        regimes = self.detect_all()
        total_weight = 0.0
        weighted_score = 0.0

        for tf, regime in regimes.items():
            w = tf_weights.get(tf, 1)
            weighted_score += score_map[regime] * w
            total_weight += w

        if total_weight == 0:
            return 0.0
        return weighted_score / total_weight

    @property
    def ready(self) -> bool:
        """所有检测器是否都已就绪"""
        return len(self._prices) >= 220
