"""
情绪因子详细运行过程演示。

用法:
    # 默认：000001.SZ，最近30天，指数范围
    python cli/demo_sentiment_detail.py

    # 指定股票代码和日期
    python cli/demo_sentiment_detail.py --ts_code 600519.SH --date 20260603
    python cli/demo_sentiment_detail.py --ts_code 000001.SZ,600519.SH,300750.SZ

    # 指定日期范围
    python cli/demo_sentiment_detail.py --start 20260501 --end 20260603

    # 分析指定指数成分股
    python cli/demo_sentiment_detail.py --scope-type index --scope-indexes 000300

    # 分析指定板块
    python cli/demo_sentiment_detail.py --scope-type sector --scope-sectors 银行,电力设备

    # 只使用特定新闻源
    python cli/demo_sentiment_detail.py --no-xwlb --no-mcp
    python cli/demo_sentiment_detail.py --source akshare

    # 跳过 Qwen API 调用（仅演示数据流）
    python cli/demo_sentiment_detail.py --no-qwen
"""

import sys, os, json, argparse
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import pandas as pd
import numpy as np
from datetime import datetime


def parse_args():
    p = argparse.ArgumentParser(description="情绪因子详细运行过程演示")
    p.add_argument("--ts_code", default="000001.SZ",
                   help="股票代码，多个用逗号分隔（默认: 000001.SZ）")
    p.add_argument("--date", default=None,
                   help="目标日期 YYYYMMDD（默认: 今天）")
    p.add_argument("--start", default=None,
                   help="起始日期 YYYYMMDD（默认: date-30天）")
    p.add_argument("--end", default=None,
                   help="结束日期 YYYYMMDD（默认: date 或今天）")
    p.add_argument("--scope-type", default=None,
                   choices=["index", "sector", "custom", "all"],
                   help="分析范围类型（覆盖 ts_code）")
    p.add_argument("--scope-indexes", default="000300",
                   help="指数代码，逗号分隔（默认: 000300）")
    p.add_argument("--scope-sectors", default="",
                   help="板块名称，逗号分隔")
    p.add_argument("--max-news", type=int, default=None,
                   help="最大新闻条数（默认: .env SENTIMENT_MAX_NEWS_PER_STOCK 或 30）")
    p.add_argument("--max-analyze", type=int, default=50,
                   help="Qwen 分析最大条数（默认: 50，控制成本）")
    p.add_argument("--no-xwlb", action="store_true", help="禁用新闻联播数据源")
    p.add_argument("--no-akshare", action="store_true", help="禁用东方财富数据源")
    p.add_argument("--no-mcp", action="store_true", help="禁用 MCP 数据源")
    p.add_argument("--source", default=None,
                   choices=["xwlb", "akshare", "mcp"],
                   help="仅使用指定数据源")
    p.add_argument("--no-qwen", action="store_true", help="跳过 Qwen 分析（仅演示数据流）")
    return p.parse_args()


def main():
    args = parse_args()

    date = args.date or datetime.now().strftime("%Y%m%d")
    start = args.start or (datetime.strptime(date, "%Y%m%d") - pd.Timedelta(days=30)).strftime("%Y%m%d")
    end = args.end or date

    print("=" * 72)
    print("  情绪因子详细运行过程")
    print("=" * 72)
    print("  日期: {} ~ {} (目标: {})".format(start, end, date))

    # ═══════════════════════════════════════════════════════════════
    # Step 0: 初始化
    # ═══════════════════════════════════════════════════════════════
    print("\n" + "-" * 72)
    print("Step 0: 初始化引擎")
    print("-" * 72)

    from data.data_manager import DataManager
    from factors.sentiment.qwen_client import QwenClient
    from factors.sentiment.news_source import NewsSource, align_news_to_trading_days

    dm = DataManager()
    dm.init_db()
    client = QwenClient()

    has_api = (bool(client.api_key) or bool(client.local_base_url)) and not args.no_qwen
    use_xwlb = not args.no_xwlb and (args.source is None or args.source == "xwlb")
    use_akshare = not args.no_akshare and (args.source is None or args.source == "akshare")
    use_mcp = not args.no_mcp and (args.source is None or args.source == "mcp")

    print("  Qwen API: {}".format("DashScope/{}".format(client.model) if (has_api and not client.local_base_url) else (
        "本地 Ollama/{}".format(client.local_model) if (has_api and client.local_base_url) else "跳过（--no-qwen 或未配置）")))
    print("  数据源: {}/{}/{}".format(
        "xwlb" if use_xwlb else "xwlb(off)",
        "akshare" if use_akshare else "akshare(off)",
        "mcp" if use_mcp else "mcp(off)",
    ))
    max_news = args.max_news or int(os.getenv("SENTIMENT_MAX_NEWS_PER_STOCK", "30"))
    print("  最大新闻: {} 条 (SENTIMENT_MAX_NEWS_PER_STOCK={})".format(
        max_news, os.getenv("SENTIMENT_MAX_NEWS_PER_STOCK", "未设置")))

    # 分析范围
    if args.scope_type:
        from factors.sentiment.sentiment_engine import SentimentEngine
        # 临时覆盖环境变量
        os.environ["SENTIMENT_SCOPE_TYPE"] = args.scope_type
        if args.scope_indexes:
            os.environ["SENTIMENT_SCOPE_INDEXES"] = args.scope_indexes
        if args.scope_sectors:
            os.environ["SENTIMENT_SCOPE_SECTORS"] = args.scope_sectors
        sent_tmp = SentimentEngine(dm)
        ts_codes = sent_tmp.get_scope_stocks()
        print("  分析范围: {} ({})".format(args.scope_type, len(ts_codes)))
        if len(ts_codes) > 10:
            print("  股票示例: {}... (共 {} 只)".format(", ".join(ts_codes[:10]), len(ts_codes)))
        else:
            print("  股票: {}".format(", ".join(ts_codes)))
    else:
        ts_codes = [c.strip() for c in args.ts_code.split(",") if c.strip()]

    # ═══════════════════════════════════════════════════════════════
    # Step 1: 分别从三个数据源获取新闻
    # ═══════════════════════════════════════════════════════════════
    print("\n" + "-" * 72)
    print("Step 1: 获取新闻 ({} 只股票)".format(len(ts_codes)))
    print("-" * 72)

    all_raw = []
    for ts_code in ts_codes:
        print("\n  --- {} ---".format(ts_code))

        xwlb_raw = pd.DataFrame()
        ak_raw = pd.DataFrame()
        mcp_raw = pd.DataFrame()

        if use_xwlb:
            try:
                xwlb_src = NewsSource(use_akshare=False, use_mcp=False)
                xwlb_raw = xwlb_src.fetch(ts_code, start=start, end=end, max_news=max_news * 3)
                print("    新闻联播(DB xwlb_daily_ext): {} 条 (news_date范围: {}-1~{}-1)".format(
                    len(xwlb_raw), start, end))
            except Exception as e:
                print("    新闻联播: 获取失败 ({})".format(e))

        if use_akshare:
            try:
                ak_src = NewsSource(use_xwlb=False, use_mcp=False)
                ak_raw = ak_src.fetch(ts_code, start=start, end=end, max_news=max_news)
                print("    东方财富(AkShare stock_news_em): {} 条".format(len(ak_raw)))
            except Exception as e:
                print("    东方财富: 获取失败 ({})".format(e))

        if use_mcp:
            try:
                mcp_src = NewsSource(use_akshare=False, use_xwlb=False, use_mcp=True)
                mcp_raw = mcp_src.fetch(ts_code, start=start, end=end, max_news=max_news)
                print("    MCP(trendradar-news): {} 条".format(len(mcp_raw)))
            except Exception as e:
                print("    MCP: 获取失败 ({})".format(e))

        all_raw.append((ts_code, xwlb_raw, ak_raw, mcp_raw))

    # 合并所有股票的结果
    frames = []
    for _, x, a, m in all_raw:
        for df in [x, a, m]:
            if not df.empty:
                frames.append(df)
    raw_news = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()

    if not raw_news.empty:
        raw_news = raw_news.drop_duplicates(subset=["title", "date"])
        raw_news = raw_news.sort_values("date", ascending=False)

    print("\n  [汇总] 合并去重后: {} 条新闻".format(len(raw_news)))
    if raw_news.empty:
        print("  (无新闻数据)")
        return

    src_counts = raw_news["source"].value_counts()
    for src, cnt in src_counts.items():
        if src == "xwlb":
            label = "新闻联播(DB)"
        elif src.startswith("akshare"):
            label = "东方财富(AkShare)"
        elif src.startswith("mcp"):
            label = "MCP(trendradar)"
        else:
            label = src
        print("    {}: {} 条".format(label, cnt))

    # ═══════════════════════════════════════════════════════════════
    # Step 2: 新闻详情（按来源分开展示）
    # ═══════════════════════════════════════════════════════════════
    print("\n" + "-" * 72)
    print("Step 2: 新闻详情（按数据源分开展示）")
    print("-" * 72)

    def show_news(label, df, limit=6):
        if df.empty:
            print("\n  [{}] (无数据)".format(label))
            return
        print("\n  [{}] {} 条".format(label, len(df)))
        for i, (_, row) in enumerate(df.head(limit).iterrows()):
            title = str(row["title"])[:80]
            content_preview = str(row["content"])[:100].replace("\n", " ")
            print("\n  [{}/{}] {} | {}".format(i + 1, len(df), row["date"], title))
            if content_preview:
                print("        内容: {}...".format(content_preview))
            url = row.get("url", "")
            if url:
                print("        链接: {}".format(url[:100]))

    show_news("新闻联播 (xwlb_daily_ext)", raw_news[raw_news["source"] == "xwlb"], limit=6)
    show_news("东方财富 (AkShare stock_news_em)",
              raw_news[raw_news["source"].str.startswith("akshare")], limit=6)
    show_news("MCP (trendradar-news)",
              raw_news[raw_news["source"].str.startswith("mcp")], limit=6)

    # ═══════════════════════════════════════════════════════════════
    # Step 3: 日期对齐
    # ═══════════════════════════════════════════════════════════════
    print("\n" + "-" * 72)
    print("Step 3: 日期对齐到交易日")
    print("-" * 72)

    # 交易日历：优先用指定股票 DB 缓存，否则 fallback 到 000001.SZ
    first_code = ts_codes[0]
    price = _get_trading_calendar(dm, first_code)
    if price is None:
        print("  {} 无 DB 缓存, fallback 到 000001.SZ".format(first_code))
        price = _get_trading_calendar(dm, "000001.SZ")
    if price is None:
        print("  无交易日历可用")
        return
    print("\n  交易日历: {} ~ {} ({} 条)".format(price.index[0], price.index[-1], len(price)))

    daily_idx = pd.to_datetime(price.index, format="%Y%m%d", errors="coerce")
    aligned_news = align_news_to_trading_days(raw_news, daily_idx)

    for label, prefix in [("新闻联播", "xwlb"), ("东方财富", "akshare"), ("MCP", "mcp")]:
        df = aligned_news[aligned_news["source"].str.startswith(prefix) if prefix != "xwlb"
                          else (aligned_news["source"] == "xwlb")]
        if df.empty:
            continue
        dates = sorted(df["date"].unique())
        print("\n  [{}] {} 条 → {} 个交易日 ({})".format(label, len(df), len(dates),
                                                         " +1day偏移" if prefix == "xwlb" else " 直接对齐"))
        print("    日期: {} ~ {}".format(dates[0], dates[-1]))
        row = df.iloc[0]
        print("    示例: {} | {}...".format(row["date"], str(row["title"])[:60]))

    # ═══════════════════════════════════════════════════════════════
    # Step 4: Qwen 情绪分析
    # ═══════════════════════════════════════════════════════════════
    print("\n" + "-" * 72)
    print("Step 4: Qwen 情绪分析")
    print("-" * 72)

    if not has_api:
        print("\n  [SKIP] Qwen API 跳过 (--no-qwen 或未配置)")
        print("  使用模拟数据演示因子计算逻辑...")
        sentiment_results = _mock_sentiment(aligned_news)
    else:
        max_analyze = min(len(aligned_news), args.max_analyze)
        analyze_news = aligned_news.head(max_analyze)

        print("\n  逐条分析 {} 条新闻...".format(max_analyze))
        sentiment_results = []
        for i, (_, row) in enumerate(analyze_news.iterrows()):
            title = str(row["title"])
            content = str(row["content"]) if len(str(row["content"])) > 20 else ""
            text = "{}\n{}".format(title, content)

            result = client.analyze_sentiment(text)
            sentiment_results.append({
                "date": row["date"],
                "title": title,
                "sentiment_score": result.get("sentiment_score", 0),
                "confidence": result.get("confidence", 0),
                "impact_duration": result.get("impact_duration", "short"),
                "key_topics": json.dumps(result.get("key_topics", [])),
                "source": row.get("source", ""),
            })

            s = result["sentiment_score"]
            icon = "(+)" if s > 0.2 else ("(-)" if s < -0.2 else "(o)")
            print("  [{}/{}] {} {:+.1f} c={:.2f} | {}...".format(
                i + 1, max_analyze, icon, s,
                result["confidence"], title[:60]))

    sent_df = pd.DataFrame(sentiment_results)
    if not sent_df.empty:
        print("\n  情绪分析汇总 ({} 条):".format(len(sent_df)))
        print("    平均情绪: {:+.3f}".format(sent_df["sentiment_score"].mean()))
        pos = (sent_df["sentiment_score"] > 0.1).sum()
        neu = ((sent_df["sentiment_score"] >= -0.1) & (sent_df["sentiment_score"] <= 0.1)).sum()
        neg = (sent_df["sentiment_score"] < -0.1).sum()
        print("    正面(>0.1): {}  中性(-0.1~0.1): {}  负面(<-0.1): {}".format(pos, neu, neg))
        if "source" in sent_df.columns:
            for src in sent_df["source"].unique():
                src_df = sent_df[sent_df["source"] == src]
                label = src[:20]
                print("    [{}] {} 条, 平均情绪: {:+.3f}".format(label, len(src_df), src_df["sentiment_score"].mean()))

    # ═══════════════════════════════════════════════════════════════
    # Step 5: 因子计算 + 结果输出
    # ═══════════════════════════════════════════════════════════════
    print("\n" + "-" * 72)
    print("Step 5-6: 因子计算 + 结果输出")
    print("-" * 72)

    from factors.sentiment.sentiment_factor import (
        NewsSentimentFactor,
        SentimentConfidenceFactor,
        SentimentMomentumFactor,
    )

    if sent_df.empty:
        print("  (无情绪数据)")
        return

    factors = [
        NewsSentimentFactor(window=5, decay=0.3, sentiment_df=sent_df),
        SentimentConfidenceFactor(window=5, sentiment_df=sent_df),
        SentimentMomentumFactor(period=5, sentiment_df=sent_df),
    ]

    factor_results = {}
    for f in factors:
        series = f.calculate(price)
        factor_results[f.name] = series
        stats = series.dropna()
        if not stats.empty:
            print("  {}: mean={:+.4f}  std={:.4f}  valid={}/{}".format(
                f.name, stats.mean(), stats.std(), len(stats), len(series)))
        else:
            print("  {}: (全NaN)".format(f.name))

    factor_df = pd.DataFrame(factor_results)
    valid = factor_df.dropna(how="all")

    if valid.empty:
        print("\n  (无有效因子值)")
        return

    recent = valid.tail(20)
    print("\n  === 最近 {} 个交易日情绪因子值 ({}) ===".format(len(recent), first_code))
    print("  {:<12s} {:>12s} {:>12s} {:>12s}".format("交易日", "news_sent_5", "news_conf_5", "sent_delta_5"))
    print("  {} {} {} {}".format("-" * 12, "-" * 12, "-" * 12, "-" * 12))
    for idx, row in recent.iterrows():
        ns = "{:+.4f}".format(row["news_sent_5"]) if not pd.isna(row["news_sent_5"]) else "        NaN"
        nc = "{:+.4f}".format(row["news_conf_5"]) if not pd.isna(row["news_conf_5"]) else "        NaN"
        sd = "{:+.4f}".format(row["sent_delta_5"]) if not pd.isna(row["sent_delta_5"]) else "        NaN"
        print("  {:<12s} {:>12s} {:>12s} {:>12s}".format(idx, ns, nc, sd))

    latest = valid.iloc[-1]
    print("\n  === 最新交易日 ({}) ===".format(valid.index[-1]))
    print("  news_sent_5  : {:+.4f}  (加权情绪, >0偏正面)".format(latest["news_sent_5"]))
    print("  news_conf_5  : {:+.4f}  (置信度加权)".format(latest["news_conf_5"]))

    # 情绪贡献明细
    print("\n  === 情绪贡献明细 (最近3天) ===")
    latest_date = valid.index[-1]
    nearby = sent_df[
        (sent_df["date"] >= str(int(latest_date) - 3)) &
        (sent_df["date"] <= latest_date)
    ]
    if not nearby.empty:
        for _, row in nearby.head(30).iterrows():
            s = row["sentiment_score"]
            impact = "(+)" if s > 0.2 else ("(-)" if s < -0.2 else "(o)")
            src = str(row.get("source", ""))
            src_s = "xwlb" if src == "xwlb" else ("ak" if src.startswith("akshare") else "mcp")
            print("  {} [{:+.1f}] [{}] {}...".format(
                impact, s, src_s, str(row["title"])[:70]))
    else:
        print("  (无最近3天新闻)")

    try:
        from reports.storage import save_report
        first = ts_codes[0] if ts_codes else "unknown"
        lines = ["## 情绪因子详细演示", "股票: {}".format(", ".join(ts_codes[:5])),
                 "数据源: {}条新闻".format(len(raw_news)),
                 "情绪: news_sent_5={}".format(
                     latest["news_sent_5"] if "news_sent_5" in latest else "N/A")]
        save_report("\n".join(lines), "情绪因子详细演示", subject_type="stock", subject_code=first)
        print("  报告已存入 DB")
    except Exception:
        pass

    print("\n" + "=" * 72)
    print("  情绪因子演示完成")
    print("=" * 72)


def _get_trading_calendar(dm, ts_code):
    """获取交易日历：优先 DB 缓存；无缓存则尝试 sync_daily 补齐。"""
    try:
        from database.dao import get_latest_trade_date
        if not get_latest_trade_date(ts_code):
            print("  {} 无 DB 缓存，尝试 sync_daily 补齐...".format(ts_code))
            try:
                n = dm.sync_daily(ts_code)
                print("  sync_daily 完成: {} 条".format(n))
            except Exception as e:
                print("  sync_daily 失败: {}".format(e))
                return None
        daily = dm.get_daily(ts_code)
        if daily is not None and not daily.empty:
            daily = daily.set_index("trade_date").sort_index()
            if len(daily) > 0:
                return daily
    except Exception as e:
        print("  获取交易日历异常: {}".format(e))
    return None


def _mock_sentiment(news_df):
    results = []
    for _, row in news_df.iterrows():
        title = str(row["title"]).lower()
        pos_words = ["利好", "增长", "突破", "创新高", "盈利", "上升", "支持", "回购", "增持", "分红"]
        neg_words = ["利空", "下跌", "亏损", "处罚", "减持", "诉讼", "退市", "警告", "暴跌", "违约"]
        pos = sum(1 for w in pos_words if w in title)
        neg = sum(1 for w in neg_words if w in title)
        if pos > neg:
            score = min(0.9, 0.1 + pos * 0.2)
        elif neg > pos:
            score = max(-0.9, -0.1 - neg * 0.2)
        else:
            score = np.random.uniform(-0.15, 0.15)
        results.append({
            "date": row["date"], "title": row["title"],
            "sentiment_score": round(score, 1),
            "confidence": round(np.random.uniform(0.5, 0.9), 2),
            "impact_duration": "short", "key_topics": json.dumps([]),
            "source": row.get("source", ""),
        })
    return results


if __name__ == "__main__":
    main()
