from __future__ import annotations

from copy import deepcopy
from statistics import mean, median
from typing import Any

from backend.data.numbers import non_nan_number as _number


COMPONENT_WEIGHTS = {
    "breadth": 20,
    "limit_ecology": 25,
    "profit_effect": 30,
    "ladder_structure": 15,
    "liquidity": 10,
}

SENTIMENT_ENGINE_VERSION = 2


def _clamp(value: float, lower: float = 0.0, upper: float = 100.0) -> float:
    return min(upper, max(lower, value))


def _linear(value: float, low: float, high: float) -> float:
    if high <= low:
        return 50.0
    return _clamp((value - low) / (high - low) * 100)


def _percentile(value: float, history: list[float]) -> float:
    if not history:
        return 50.0
    below = sum(item < value for item in history)
    equal = sum(item == value for item in history)
    return _clamp((below + equal * 0.5) / len(history) * 100)


def _adaptive_score(value: float, fixed: float, history: list[float]) -> float:
    if len(history) < 20:
        return fixed
    return fixed * 0.25 + _percentile(value, history[-250:]) * 0.75


def _trade_date(payload: dict[str, Any]) -> str:
    meta = payload.get("meta") or {}
    return str(meta.get("trade_date") or payload.get("_snapshot_date") or "").replace("-", "")


def _deduplicate_snapshots(snapshots: list[dict[str, Any]]) -> list[dict[str, Any]]:
    by_trade_date: dict[str, dict[str, Any]] = {}
    for payload in snapshots:
        trade_date = _trade_date(payload)
        if trade_date:
            by_trade_date[trade_date] = payload
    return [by_trade_date[key] for key in sorted(by_trade_date)]


def _snapshot_stats(payload: dict[str, Any]) -> dict[str, Any]:
    overview = payload.get("overview") or {}
    meta = payload.get("meta") or {}
    limits = list(payload.get("limits") or [])
    broken = list(payload.get("broken") or [])
    down_limits = list(payload.get("down_limits") or [])
    yesterday = list(payload.get("yesterday_limits") or [])

    limit_up = len(limits) if limits else int(_number(overview.get("limit_up_count")))
    broken_count = len(broken) if broken else int(_number(overview.get("broken_count")))
    limit_down = len(down_limits) if down_limits else int(_number(overview.get("limit_down_count")))
    streaks = [max(1, int(_number(row.get("streak"), 1))) for row in limits]
    first_board = sum(streak == 1 for streak in streaks)
    second_board = sum(streak == 2 for streak in streaks)
    three_plus = sum(streak >= 3 for streak in streaks)
    max_height = max(streaks, default=0)
    present_levels = set(streaks)
    ladder_completeness = (
        sum(level in present_levels for level in range(1, max_height + 1)) / max_height * 100
        if max_height else 0.0
    )

    up_count = int(_number(overview.get("up_count")))
    down_count = int(_number(overview.get("down_count")))
    flat_count = int(_number(overview.get("flat_count")))
    active_count = up_count + down_count
    breadth_ratio = up_count / max(active_count, 1) * 100
    seal_rate = _number(overview.get("seal_rate"))
    if not seal_rate and limit_up + broken_count:
        seal_rate = limit_up / (limit_up + broken_count) * 100

    previous_limit_count = len(yesterday)
    previous_positive_count = sum(_number(row.get("current_change")) > 0 for row in yesterday)
    previous_positive_rate = previous_positive_count / max(previous_limit_count, 1) * 100
    advanced_count = sum(row.get("outcome") == "晋级" for row in yesterday)
    advance_rate = advanced_count / max(previous_limit_count, 1) * 100
    average_previous_change = (
        mean(_number(row.get("current_change")) for row in yesterday) if yesterday else 0.0
    )
    median_previous_change = (
        median(_number(row.get("current_change")) for row in yesterday) if yesterday else 0.0
    )
    severe_loss_count = sum(_number(row.get("current_change")) <= -5 for row in yesterday)
    severe_loss_rate = severe_loss_count / max(previous_limit_count, 1) * 100
    previous_down_count = sum(row.get("outcome") == "跌停" for row in yesterday)
    high_previous = [row for row in yesterday if int(_number(row.get("prior_streak"), 1)) >= 2]
    high_positive_rate = (
        sum(_number(row.get("current_change")) > 0 for row in high_previous)
        / max(len(high_previous), 1)
        * 100
    )

    amount_billion = _number(overview.get("amount_billion"))
    limit_amount_billion = sum(_number(row.get("amount_billion")) for row in limits)
    return {
        "trade_date": _trade_date(payload),
        "previous_trade_date": str(meta.get("previous_trade_date") or "").replace("-", ""),
        "up_count": up_count,
        "down_count": down_count,
        "flat_count": flat_count,
        "breadth_ratio": round(breadth_ratio, 1),
        "limit_up_count": limit_up,
        "first_board_count": first_board,
        "second_board_count": second_board,
        "three_plus_count": three_plus,
        "max_height": max_height,
        "ladder_completeness": round(ladder_completeness, 1),
        "broken_count": broken_count,
        "limit_down_count": limit_down,
        "seal_rate": round(seal_rate, 1),
        "previous_limit_count": previous_limit_count,
        "previous_positive_count": previous_positive_count,
        "previous_positive_rate": round(previous_positive_rate, 1),
        "advance_rate": round(advance_rate, 1),
        "average_previous_change": round(average_previous_change, 2),
        "median_previous_change": round(median_previous_change, 2),
        "severe_loss_count": severe_loss_count,
        "severe_loss_rate": round(severe_loss_rate, 1),
        "previous_down_count": previous_down_count,
        "high_positive_rate": round(high_positive_rate, 1),
        "amount_billion": round(amount_billion, 1),
        "limit_amount_billion": round(limit_amount_billion, 2),
    }


def _sentiment_label(score: float) -> str:
    if score >= 80:
        return "情绪高涨"
    if score >= 60:
        return "情绪偏强"
    if score >= 40:
        return "情绪中性"
    if score >= 20:
        return "情绪偏弱"
    return "情绪冰点"


def _phase_signal(score: float, momentum: float, profit_score: float) -> str:
    if score < 25:
        return "修复" if momentum > 3 else "冰点"
    if score < 45:
        return "修复" if momentum > 3 else "退潮"
    if score >= 80:
        return "高潮" if momentum >= -2 and profit_score >= 60 else "分化"
    if score >= 65:
        return "分化" if momentum < -3 or profit_score < 50 else "发酵"
    if momentum < -5:
        return "退潮"
    return "发酵" if momentum >= 0 and profit_score >= 45 else "分化"


def _confirmed_phase(
    previous: dict[str, Any] | None,
    score: float,
    day_change: float,
    systemic_health: float,
    profit_score: float,
    ecology_score: float,
    phase_signal: str,
    extreme_ice: bool,
    fermentation_signal_count: int,
) -> tuple[str, str]:
    if previous is None:
        return phase_signal, "首个连续交易日，采用原始阶段信号"
    previous_phase = str(previous.get("phase") or phase_signal)
    if extreme_ice:
        return "冰点", "市场宽度与跌停数量触发极端冰点"

    recovery = day_change >= 6 and score >= 25 and systemic_health >= 24
    fermentation_confirmed = fermentation_signal_count >= 2
    climax_ready = (
        score >= 80
        and profit_score >= 60
        and systemic_health >= 60
        and ecology_score >= 70
    )

    if previous_phase == "冰点":
        return ("修复", "冰点后首次有效回升") if recovery else ("冰点", "冰点尚未形成有效修复")

    if previous_phase == "退潮":
        if score < 25:
            return "冰点", "退潮继续下探至冰点区间"
        return ("修复", "退潮后出现有效回升") if recovery else ("退潮", "退潮尚未形成有效修复")

    if previous_phase == "修复":
        if score < 25:
            return "冰点", "修复失败并重新跌入冰点区间"
        if day_change <= -6 and score < 45:
            return "退潮", "修复失败且温度显著回落"
        if fermentation_confirmed:
            return "发酵", "发酵条件连续两个交易日成立"
        return "修复", "修复延续，等待发酵确认"

    if previous_phase == "发酵":
        if score < 25:
            return "冰点", "发酵阶段出现极端情绪坍塌"
        if score < 45 and (day_change < 0 or systemic_health < 35):
            return "退潮", "发酵阶段温度与系统健康度同步转弱"
        if climax_ready:
            return "高潮", "温度、赚钱效应与涨停生态共同达到高潮条件"
        if phase_signal in {"分化", "退潮"} or day_change <= -6:
            return "分化", "发酵阶段出现降温或赚钱效应弱化"
        return "发酵", "发酵状态延续"

    if previous_phase == "高潮":
        if score < 25:
            return "冰点", "高潮后出现极端情绪坍塌"
        if climax_ready:
            return "高潮", "高潮条件继续成立"
        if score < 45 or systemic_health < 30:
            return "退潮", "高潮后风险快速释放"
        return "分化", "高潮条件消退，进入分化"

    if previous_phase == "分化":
        if score < 25:
            return "冰点", "分化继续恶化至冰点区间"
        if score < 45 or systemic_health < 30:
            return "退潮", "分化后温度或系统健康度继续下降"
        if fermentation_confirmed:
            return "发酵", "分化转强条件连续两个交易日成立"
        return "分化", "分化延续，等待方向确认"

    return phase_signal, "采用原始阶段信号"


def build_sentiment_history(snapshots: list[dict[str, Any]]) -> list[dict[str, Any]]:
    payloads = _deduplicate_snapshots(snapshots)
    raw_rows = [_snapshot_stats(payload) for payload in payloads]
    results: list[dict[str, Any]] = []

    for index, stats in enumerate(raw_rows):
        previous = raw_rows[:index]
        limit_history = [float(row["limit_up_count"]) for row in previous]
        down_limit_history = [float(row["limit_down_count"]) for row in previous]
        height_history = [float(row["max_height"]) for row in previous]
        three_plus_history = [float(row["three_plus_count"]) for row in previous]
        amount_history = [float(row["amount_billion"]) for row in previous[-20:] if row["amount_billion"]]

        breadth_score = _clamp(float(stats["breadth_ratio"]))
        limit_strength = _adaptive_score(
            float(stats["limit_up_count"]),
            _linear(float(stats["limit_up_count"]), 10, 100),
            limit_history,
        )
        down_relief = 100 - _adaptive_score(
            float(stats["limit_down_count"]),
            _linear(float(stats["limit_down_count"]), 0, 50),
            down_limit_history,
        )
        seal_quality = _linear(float(stats["seal_rate"]), 35, 90)
        systemic_health = breadth_score * 0.60 + down_relief * 0.40
        systemic_gate = 1.0 if systemic_health >= 35 else 0.35 + systemic_health / 35 * 0.65
        ecology_base_score = limit_strength * 0.35 + seal_quality * 0.35 + down_relief * 0.30
        # Systemic risk is applied once to the final temperature. Reapplying it here
        # would count market breadth and limit-down pressure twice.
        limit_ecology_score = ecology_base_score

        if stats["previous_limit_count"]:
            positive_score = float(stats["previous_positive_rate"])
            average_change_score = _clamp(50 + float(stats["average_previous_change"]) * 6)
            median_change_score = _clamp(50 + float(stats["median_previous_change"]) * 7)
            advance_score = _clamp(float(stats["advance_rate"]) * 2.5)
            severe_loss_safety = _clamp(100 - float(stats["severe_loss_rate"]) * 3)
            down_safety = _clamp(100 - float(stats["previous_down_count"]) / stats["previous_limit_count"] * 700)
            tail_safety_score = severe_loss_safety * 0.70 + down_safety * 0.30
            profit_effect_score = (
                positive_score * 0.30
                + median_change_score * 0.25
                + average_change_score * 0.10
                + advance_score * 0.20
                + tail_safety_score * 0.15
            )
        else:
            profit_effect_score = 50.0

        max_height_score = _adaptive_score(
            float(stats["max_height"]),
            _linear(float(stats["max_height"]), 1, 7),
            height_history,
        )
        continuation_rate = (
            (float(stats["second_board_count"]) + float(stats["three_plus_count"]))
            / max(float(stats["limit_up_count"]), 1)
            * 100
        )
        three_plus_density = float(stats["three_plus_count"]) / max(float(stats["limit_up_count"]), 1) * 100
        three_plus_score = _adaptive_score(
            float(stats["three_plus_count"]),
            _clamp(three_plus_density * 5),
            three_plus_history,
        )
        ladder_structure_score = (
            max_height_score * 0.30
            + _clamp(continuation_rate * 3) * 0.25
            + three_plus_score * 0.25
            + float(stats["ladder_completeness"]) * 0.20
        )

        amount_baseline = mean(amount_history) if amount_history else float(stats["amount_billion"] or 1)
        amount_ratio = float(stats["amount_billion"]) / max(amount_baseline, 1)
        amount_score = _clamp(50 + (amount_ratio - 1) * 100)
        limit_amount_share = float(stats["limit_amount_billion"]) / max(float(stats["amount_billion"]), 1) * 100
        liquidity_score = amount_score * 0.70 + _clamp(limit_amount_share * 20) * 0.30

        component_scores = {
            "breadth": breadth_score,
            "limit_ecology": limit_ecology_score,
            "profit_effect": profit_effect_score,
            "ladder_structure": ladder_structure_score,
            "liquidity": liquidity_score,
        }
        raw_score = sum(component_scores[key] * weight / 100 for key, weight in COMPONENT_WEIGHTS.items())
        score = round(
            raw_score * systemic_gate
        )
        extreme_ice = float(stats["breadth_ratio"]) <= 15 and float(stats["limit_down_count"]) >= 100
        if extreme_ice:
            score = min(score, 15)
        elif float(stats["breadth_ratio"]) <= 25 and float(stats["limit_down_count"]) >= 50:
            score = min(score, 24)
        previous_scores: list[float] = []
        expected_date = str(stats.get("previous_trade_date") or "")
        for prior_result in reversed(results):
            if not expected_date or str(prior_result.get("trade_date") or "") != expected_date:
                break
            previous_scores.append(float(prior_result["score"]))
            expected_date = str(prior_result.get("previous_trade_date") or "")
            if len(previous_scores) == 3:
                break
        momentum = score - mean(previous_scores) if previous_scores else 0.0
        direction = "升温" if momentum > 3 else "降温" if momentum < -3 else "持平"
        normalization = "历史百分位" if len(previous) >= 20 else "固定锚点"
        previous_result = (
            results[-1]
            if results and str(stats.get("previous_trade_date") or "") == str(results[-1].get("trade_date") or "")
            else None
        )
        day_change = score - float(previous_result["score"]) if previous_result else 0.0
        ema_score = round(
            score if not previous_result
            else score * 0.5 + float(previous_result.get("ema_score", previous_result["score"])) * 0.5,
            1,
        )
        phase_signal = _phase_signal(score, momentum, profit_effect_score)
        fermentation_ready = (
            phase_signal == "发酵"
            and score >= 45
            and profit_effect_score >= 45
            and systemic_health >= 35
            and not extreme_ice
        )
        previous_fermentation_count = int(previous_result.get("fermentation_signal_count") or 0) if previous_result else 0
        fermentation_signal_count = previous_fermentation_count + 1 if fermentation_ready else 0
        phase, transition_reason = _confirmed_phase(
            previous_result,
            score,
            day_change,
            systemic_health,
            profit_effect_score,
            limit_ecology_score,
            phase_signal,
            extreme_ice,
            fermentation_signal_count,
        )
        previous_phase = str(previous_result.get("phase") or "") if previous_result else ""
        if phase not in {"修复", "分化"}:
            fermentation_signal_count = 0
        elif phase == "分化" and previous_phase != "分化":
            fermentation_signal_count = 0

        components = {
            "breadth": {
                "label": "市场宽度",
                "score": round(breadth_score, 1),
                "weight": COMPONENT_WEIGHTS["breadth"],
                "summary": f"上涨占比 {stats['breadth_ratio']:.1f}%",
            },
            "limit_ecology": {
                "label": "涨停生态",
                "score": round(limit_ecology_score, 1),
                "weight": COMPONENT_WEIGHTS["limit_ecology"],
                "summary": (
                    f"涨停 {stats['limit_up_count']} · 跌停 {stats['limit_down_count']} · "
                    f"封板 {stats['seal_rate']:.1f}%"
                ),
            },
            "profit_effect": {
                "label": "赚钱效应",
                "score": round(profit_effect_score, 1),
                "weight": COMPONENT_WEIGHTS["profit_effect"],
                "summary": (
                    f"昨涨停红盘 {stats['previous_positive_rate']:.1f}% · "
                    f"中位 {stats['median_previous_change']:+.2f}% · "
                    f"重亏 {stats['severe_loss_rate']:.1f}%"
                    if stats["previous_limit_count"] else "缺少前一交易日样本"
                ),
            },
            "ladder_structure": {
                "label": "连板结构",
                "score": round(ladder_structure_score, 1),
                "weight": COMPONENT_WEIGHTS["ladder_structure"],
                "summary": f"最高 {stats['max_height']} 板 · 三板以上 {stats['three_plus_count']} 家",
            },
            "liquidity": {
                "label": "成交活跃度",
                "score": round(liquidity_score, 1),
                "weight": COMPONENT_WEIGHTS["liquidity"],
                "summary": f"成交 {stats['amount_billion']:.1f} 亿 · 均值比 {amount_ratio:.2f}",
            },
        }
        results.append(
            {
                **stats,
                "score": score,
                "ema_score": ema_score,
                "label": _sentiment_label(score),
                "phase": phase,
                "phase_signal": phase_signal,
                "transition_reason": transition_reason,
                "fermentation_signal_count": fermentation_signal_count,
                "day_change": round(day_change, 1),
                "direction": direction,
                "momentum": round(momentum, 1),
                "normalization": "250日历史百分位" if len(previous) >= 20 else normalization,
                "history_days": len(previous) + 1,
                "systemic_health": round(systemic_health, 1),
                "risk_multiplier": round(systemic_gate, 3),
                "components": components,
            }
        )
    return results


def latest_contiguous_history(series: list[dict[str, Any]]) -> list[dict[str, Any]]:
    if not series:
        return []
    contiguous = [series[-1]]
    for row in reversed(series[:-1]):
        expected_previous = str(contiguous[0].get("previous_trade_date") or "")
        if not expected_previous or expected_previous != str(row.get("trade_date") or ""):
            break
        contiguous.insert(0, row)
    return contiguous


def apply_sentiment_to_dashboard(
    dashboard: dict[str, Any],
    historical_snapshots: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    result = deepcopy(dashboard)
    history = list(historical_snapshots or [])
    history.append(result)
    series = build_sentiment_history(history)
    target_date = _trade_date(result)
    sentiment = next((row for row in reversed(series) if row["trade_date"] == target_date), None)
    if not sentiment:
        return result
    overview = dict(result.get("overview") or {})
    overview.update(
        {
            "sentiment_score": sentiment["score"],
            "sentiment_trend_score": sentiment["ema_score"],
            "sentiment_label": sentiment["label"],
            "sentiment_phase": sentiment["phase"],
            "sentiment_direction": sentiment["direction"],
            "sentiment_components": sentiment["components"],
            "sentiment_engine_version": SENTIMENT_ENGINE_VERSION,
        }
    )
    result["overview"] = overview
    return result
