"""
多周期牛熊矩阵 — 方案一：独立多周期判定矩阵

在统一时间轴（1d bar 边界）上对齐全周期牛熊判定，
输出矩阵表格、统计信息和详细拆解。

用法:
    source .venv/bin/activate && python example/regime_display.py

定制:
    python example/regime_display.py --symbol ETHUSDT
    python example/regime_display.py --matrix-rows 50  # 输出最近 50 个时间点
"""

import asyncio
import sys
from datetime import datetime, timezone
from pathlib import Path

_project_root = Path(__file__).resolve().parent.parent.parent
if str(_project_root) not in sys.path:
    sys.path.insert(0, str(_project_root))

from engine.common.config import config
from engine.data.service import DataService, INTERVAL_MS, INTERVAL_TO_TABLE
from engine.indicators.regime import RegimeDetector

# ── 默认配置 ──

SYMBOL = "BTCUSDT"
TIMEFRAMES = ["1h", "4h", "1d", "1w"]
MATRIX_ROWS = 30
OUTPUT_FILE = None  # 运行时设置


def parse_args():
    """解析命令行参数"""
    args = sys.argv[1:]
    kwargs = {"symbol": SYMBOL, "matrix_rows": MATRIX_ROWS}
    i = 0
    while i < len(args):
        if args[i] == "--symbol" and i + 1 < len(args):
            kwargs["symbol"] = args[i + 1].upper()
            i += 2
        elif args[i] == "--matrix-rows" and i + 1 < len(args):
            kwargs["matrix_rows"] = int(args[i + 1])
            i += 2
        else:
            i += 1
    return kwargs


# ════════════════════════════════════════════════════════
# 1. 加载各周期数据并运行独立判定
# ════════════════════════════════════════════════════════


async def load_and_detect(
    ds: DataService, symbol: str, timeframe: str
) -> tuple[list[float], list[dict], datetime, datetime]:
    """加载指定周期全量数据，逐根运行 RegimeDetector，返回 (times, regimes, 数据起始, 数据结束)

    regimes[i] = {"time_ms": float, "close": float, "regime": str, "detail": dict}
    仅从第 220 根 bar 开始有效判定，之前为 unknown。
    """
    start, end = await ds.fetch_symbol_date_range(symbol, timeframe)
    all_klines = await ds.fetch_klines(
        symbol=symbol,
        interval=timeframe,
        start_time=start,
        end_time=end,
        limit=10_000_000,
    )

    det = RegimeDetector()
    regimes = []
    times = []
    for k in all_klines:
        times.append(k.open_time)
        det.update(k.close)
        if det.ready:
            regime = det.detect(k.close, len(det._e200) - 1)
            detail = det.detect_detail(k.close, len(det._e200) - 1)
        else:
            regime = "unknown"
            detail = {"final": "unknown"}
        regimes.append({
            "time_ms": k.open_time,
            "time_dt": datetime.fromtimestamp(k.open_time / 1000, tz=timezone.utc),
            "close": k.close,
            "regime": regime,
            "detail": detail,
            "ema200": det.ema200,
            "ath": det.ath,
        })

    return times, regimes, start, end


# ════════════════════════════════════════════════════════
# 2. 时间对齐 — 以日线为基准，查找各周期的即时判定
# ════════════════════════════════════════════════════════


def find_regime_at(
    regimes: list[dict], target_ms: float, interval_ms: float
) -> dict:
    """在指定周期的 regime 序列中，找到 target_ms 时刻的判定。

    规则：取开盘时间最接近且不晚于 target_ms 的那根 bar 的判定。
    若 target_ms 在该 bar 的 [open_time, open_time + interval_ms) 范围内，返回该 bar 的判定。
    """
    best = None
    for r in regimes:
        bar_time = r["time_ms"]
        if bar_time <= target_ms < bar_time + interval_ms:
            return r
        if bar_time > target_ms:
            break
        best = r
    return best


def build_matrix(
    times_by_tf: dict[str, list[float]],
    regimes_by_tf: dict[str, list[dict]],
    reference_tf: str = "1d",
    num_rows: int = MATRIX_ROWS,
) -> list[dict]:
    """以 reference_tf 的 bar 时间点为基准，构建多周期判定矩阵。

    Returns:
        [{time_dt, time_str, 1h: regime, 4h: regime, 1d: regime, 1w: regime}, ...]
        按时间倒序，只包含各周期均有有效数据的时间点。
    """
    ref_regimes = regimes_by_tf[reference_tf]
    # 只取有效区间（所有周期都过了 220 根 bar 之后）
    ref_valid = [
        r for r in ref_regimes
        if r["regime"] != "unknown"
    ]

    matrix = []
    for ref_r in ref_valid[-num_rows:]:
        row = {
            "time_dt": ref_r["time_dt"],
            "time_str": ref_r["time_dt"].strftime("%Y-%m-%d %H:%M"),
            "time_ms": ref_r["time_ms"],
            f"{reference_tf}_close": ref_r["close"],
        }
        for tf in TIMEFRAMES:
            if tf == reference_tf:
                row[tf] = ref_r["regime"]
                row[f"{tf}_detail"] = ref_r["detail"]
                row[f"{tf}_ema200"] = ref_r.get("ema200", 0)
                row[f"{tf}_ath"] = ref_r.get("ath", 0)
            else:
                tf_regime = find_regime_at(
                    regimes_by_tf[tf],
                    ref_r["time_ms"],
                    INTERVAL_MS[tf],
                )
                if tf_regime:
                    row[tf] = tf_regime["regime"]
                    row[f"{tf}_detail"] = tf_regime["detail"]
                    row[f"{tf}_ema200"] = tf_regime.get("ema200", 0)
                    row[f"{tf}_ath"] = tf_regime.get("ath", 0)
                else:
                    row[tf] = "—"
        matrix.append(row)

    # 倒序（最新在前）
    return list(reversed(matrix))


# ════════════════════════════════════════════════════════
# 3. 统计分析
# ════════════════════════════════════════════════════════


def regime_icon(r: str) -> str:
    return {"bull": "🐂", "bear": "🐻", "sideways": "⚪", "unknown": "⏳", "—": "—"}.get(r, r)


def calc_alignment_stats(matrix: list[dict]) -> dict:
    """计算各周期判定对齐统计"""
    total = len(matrix)
    if total == 0:
        return {}

    stats = {}
    for tf in TIMEFRAMES:
        cnt_bull = sum(1 for r in matrix if r.get(tf) == "bull")
        cnt_bear = sum(1 for r in matrix if r.get(tf) == "bear")
        cnt_side = sum(1 for r in matrix if r.get(tf) == "sideways")
        stats[tf] = {
            "bull": cnt_bull,
            "bull_pct": cnt_bull / total * 100,
            "bear": cnt_bear,
            "bear_pct": cnt_bear / total * 100,
            "sideways": cnt_side,
            "sideways_pct": cnt_side / total * 100,
        }

    # 全周期一致（所有非 "—" 的周期判定相同）
    all_agree = 0
    for r in matrix:
        regimes = [r[tf] for tf in TIMEFRAMES if r.get(tf) not in ("—", "unknown")]
        if regimes and len(set(regimes)) == 1:
            all_agree += 1
    stats["all_agree"] = all_agree
    stats["all_agree_pct"] = all_agree / total * 100

    # 大小周期背离（1h 与 1w 相反）
    diverge = 0
    for r in matrix:
        r1h = r.get("1h")
        r1w = r.get("1w")
        if r1h and r1w and r1h != "—" and r1w != "—":
            if (r1h == "bull" and r1w == "bear") or (r1h == "bear" and r1w == "bull"):
                diverge += 1
    stats["diverge_1h_1w"] = diverge
    stats["diverge_1h_1w_pct"] = diverge / total * 100

    return stats


# ════════════════════════════════════════════════════════
# 4. 输出格式化
# ════════════════════════════════════════════════════════


def render_matrix_table(matrix: list[dict]) -> str:
    """渲染矩阵表格为 Markdown 格式"""
    header = "| 时间 |"
    sep = "|------|"
    for tf in TIMEFRAMES:
        header += f" {tf} |"
        sep += "----|"
    header += " BTC Close |"
    sep += "-----------|"

    lines = [header, sep]
    for row in matrix:
        line = f"| {row['time_str']} |"
        for tf in TIMEFRAMES:
            r = row.get(tf, "—")
            line += f" {regime_icon(r)} {r} |"
        close_str = f"{row.get('1d_close', 0):.2f}"
        line += f" {close_str} |"
        lines.append(line)

    return "\n".join(lines)


def render_detail_table(matrix: list[dict], tf: str, n: int = 5) -> str:
    """渲染单个周期的详细判定表（最近 n 条）"""
    lines = [
        f"### {tf} 详细判定（最近 {n} 个时间点）",
        "",
        "| 时间 | 最终 | EMA200斜率 | 价格vsEMA200 | ATH回撤 | 窄幅盘整 | 振幅% | 牛票 | 熊票 | 震票 | EMA200 | ATH |",
        "|------|------|-----------|-------------|---------|---------|------|------|------|------|--------|-----|",
    ]

    detail_key = f"{tf}_detail"
    for row in matrix[:n]:
        d = row.get(detail_key, {})
        f_icon = regime_icon(d.get("final", "—"))
        ema200_val = row.get(f"{tf}_ema200", 0)
        ath_val = row.get(f"{tf}_ath", 0)
        range_pct = d.get("range_pct", 0) * 100
        lines.append(
            f"| {row['time_str']} | {f_icon} {d.get('final', '—')} | "
            f"{d.get('ema200_slope', '—')} | {d.get('price_vs_ema200', '—')} | "
            f"{d.get('ath_drawdown', '—')} | {d.get('price_range', '—')} | "
            f"{range_pct:.1f}% | {d.get('bull_votes', '—')} | "
            f"{d.get('bear_votes', '—')} | {d.get('sideways_votes', '—')} | "
            f"{ema200_val:.2f} | {ath_val:.2f} |"
        )

    return "\n".join(lines)


def render_current_status(regimes_by_tf: dict[str, list[dict]]) -> str:
    """渲染当前最新状态"""
    lines = [
        "## 当前各周期牛熊状态",
        "",
        "| 周期 | 判定 | EMA200斜率 | 价格vsEMA200 | ATH回撤 | 窄幅盘整 | 振幅% | 牛票 | 熊票 | 震票 | EMA200 | Close |",
        "|------|------|-----------|-------------|---------|---------|------|------|------|------|--------|-------|",
    ]

    for tf in TIMEFRAMES:
        regs = regimes_by_tf[tf]
        if not regs or regs[-1]["regime"] == "unknown":
            lines.append(f"| {tf} | ⏳ 数据不足 | — | — | — | — | — | — | — | — | — | — |")
            continue
        r = regs[-1]
        d = r["detail"]
        icon = regime_icon(r["regime"])
        range_pct = d.get("range_pct", 0) * 100
        lines.append(
            f"| {tf} | {icon} **{r['regime']}** | "
            f"{d.get('ema200_slope', '—')} | {d.get('price_vs_ema200', '—')} | "
            f"{d.get('ath_drawdown', '—')} | {d.get('price_range', '—')} | "
            f"{range_pct:.1f}% | {d.get('bull_votes', '—')} | "
            f"{d.get('bear_votes', '—')} | {d.get('sideways_votes', '—')} | "
            f"{r.get('ema200', 0):.2f} | {r['close']:.2f} |"
        )

    return "\n".join(lines)


def render_stats(stats: dict) -> str:
    """渲染统计信息"""
    lines = [
        "## 周期判定分布统计",
        "",
        "| 周期 | 🐂 牛市 | 占比 | 🐻 熊市 | 占比 | ⚪ 震荡 | 占比 |",
        "|------|--------|------|--------|------|--------|------|",
    ]
    for tf in TIMEFRAMES:
        s = stats.get(tf, {})
        lines.append(
            f"| {tf} | {s.get('bull', 0)} | {s.get('bull_pct', 0):.1f}% | "
            f"{s.get('bear', 0)} | {s.get('bear_pct', 0):.1f}% | "
            f"{s.get('sideways', 0)} | {s.get('sideways_pct', 0):.1f}% |"
        )

    lines.append("")
    lines.append("## 周期一致性统计")
    lines.append("")
    lines.append(f"- **全周期一致**（所有周期判定相同）：{stats.get('all_agree', 0)} 天 ({stats.get('all_agree_pct', 0):.1f}%)")
    lines.append(f"- **1h ↔ 1w 背离**（1h 与 1w 方向相反）：{stats.get('diverge_1h_1w', 0)} 天 ({stats.get('diverge_1h_1w_pct', 0):.1f}%)")

    return "\n".join(lines)


# ════════════════════════════════════════════════════════
# 主流程
# ════════════════════════════════════════════════════════


async def main():
    kw = parse_args()
    symbol = kw["symbol"]
    matrix_rows = kw["matrix_rows"]
    global OUTPUT_FILE
    OUTPUT_FILE = (
        Path(__file__).resolve().parent.parent
        / "backtest"
        / f"REGIME_MATRIX_{symbol}.md"
    )

    out: list[str] = []

    def w(line: str = ""):
        out.append(line)
        print(line)

    w(f"# 多周期牛熊判定矩阵 — {symbol}")
    w()
    w(f"> 生成时间：{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC")
    w(f"> 判定方法：EMA200斜率 + 价格vsEMA200 + ATH回撤 + 窄幅盘整，四选二投票")
    w(f"> 时间对齐基准：日线(1d) bar 边界")
    w()

    # ── 加载数据并运行判定 ──
    ds = DataService(config.db)
    await ds.connect()

    try:
        times_by_tf: dict[str, list[float]] = {}
        regimes_by_tf: dict[str, list[dict]] = {}
        date_ranges: dict[str, tuple[datetime, datetime]] = {}

        for tf in TIMEFRAMES:
            print(f"  加载 {tf} 数据...", end=" ", flush=True)
            times, regimes, data_start, data_end = await load_and_detect(ds, symbol, tf)
            times_by_tf[tf] = times
            regimes_by_tf[tf] = regimes
            date_ranges[tf] = (data_start, data_end)
            valid_count = sum(1 for r in regimes if r["regime"] != "unknown")
            print(f"{len(regimes)} 根 bar，{valid_count} 根有效（>220热身）")

        # ── 数据范围 ──
        w()
        w("## 数据覆盖范围")
        w()
        w("| 周期 | 数据条数 | 有效条数 | 起始时间 | 结束时间 |")
        w("|------|---------|---------|---------|---------|")
        for tf in TIMEFRAMES:
            regs = regimes_by_tf[tf]
            valid = sum(1 for r in regs if r["regime"] != "unknown")
            s, e = date_ranges[tf]
            w(f"| {tf} | {len(regs)} | {valid} | {s.strftime('%Y-%m-%d')} | {e.strftime('%Y-%m-%d')} |")
        w()

        # ── 当前状态 ──
        w(render_current_status(regimes_by_tf))
        w()

        # ── 历史矩阵 ──
        matrix = build_matrix(times_by_tf, regimes_by_tf, "1d", num_rows=matrix_rows)

        w(f"## 历史牛熊矩阵（最近 {len(matrix)} 个日线时间点）")
        w()
        w("> 以日线 bar 的 open_time 为基准，查找各周期在该时刻的即时判定。")
        w()
        w(render_matrix_table(matrix))
        w()

        # ── 各周期详细拆解 ──
        w("## 各周期详细拆解")
        w()
        for tf in TIMEFRAMES:
            w(render_detail_table(matrix, tf, n=min(5, len(matrix))))
            w()

        # ── 统计 ──
        stats = calc_alignment_stats(matrix)
        w(render_stats(stats))
        w()

    finally:
        await ds.close()

    # ── 写出文件 ──
    OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        f.write("\n".join(out) + "\n")

    print(f"\n✓ 结果已保存到: {OUTPUT_FILE}")


if __name__ == "__main__":
    asyncio.run(main())
