import sys
import time
import json
import webbrowser
from pathlib import Path

import click
import pandas as pd
import numpy as np

sys.path.insert(0, str(Path(__file__).parent))

from config import SCREENER_TOP_N, BASE_DIR


@click.group()
def cli():
    """A股超短期选股工具 — ML+LLM智能选股"""
    pass


@cli.command()
def tips():
    """查看使用技巧"""
    click.echo("""
╔══════════════════════════════════════════════════════════════╗
║              A股超短期选股系统 — 使用技巧                    ║
╚══════════════════════════════════════════════════════════════╝

【每日操作流程】

  1. 每天 9:00-9:25 运行选股：
     python3 cli.py screen --top 10

  2. 查看结果中的「买入概率」列：
     - 概率 > 0.65：强烈推荐买入
     - 概率 0.55-0.65：可以考虑买入
     - 概率 < 0.55：建议观望

  3. 从前5名中选择2-3只买入（分散风险）

  4. 第二天（T+1）开盘后观察：
     - 高开 > 3%：考虑止盈卖出
     - 低开 > 2%：考虑止损卖出
     - 平开：持有到收盘再决定

【关键命令】

  python3 cli.py screen --top 10          # 每日选股（含LLM报告）
  python3 cli.py screen --top 10 --no-llm # 快速选股（无LLM）
  python3 cli.py analyze -s 000001        # 分析单只股票
  python3 cli.py top                      # 查看最近选股结果
  python3 cli.py tips                     # 查看本说明

【输出文件】

  每次运行会自动生成HTML报告：
  - report_YYYYMMDD_HHMM.html  — 选股结果报告
  - analyze_代码_时间.html      — 个股分析报告
  双击HTML文件即可在浏览器中查看美观的图表报告。

【风险控制】

  - 每只股票仓位不超过总资金的30%
  - 设置止损线：亏损 > 5% 立即卖出
  - 不要在大盘暴跌时买入
  - 模型仅供参考，不保证盈利
""")


@cli.command()
@click.option("--top", "-n", default=SCREENER_TOP_N, help="输出前N只股票")
@click.option("--no-llm", is_flag=True, help="跳过LLM分析")
@click.option("--save", "-s", is_flag=True, help="保存结果到文件")
@click.option("--open", "open_browser", is_flag=True, help="自动打开浏览器查看报告")
@click.option("--mode", type=click.Choice(["auto", "pre", "confirm"], case_sensitive=False),
              default="auto",
              help="运行模式: auto自动(竞价前盘前/盘中确认), pre盘前规划, confirm盘中确认")
def screen(top, no_llm, save, open_browser, mode):
    """每日选股（盘前规划9:20 + 盘中确认10:00）"""
    from main import run_screener
    from report import generate_screen_report

    results, market_ctx, mode = run_screener(top_n=top, use_llm=not no_llm, mode=mode)
    if results is None:
        click.echo("选股失败，请检查网络连接和数据源")
        return

    llm_report = None
    if not no_llm:
        from llm.client import LLMClient
        from llm.analyzer import StockAnalyzer
        client = LLMClient()
        if client.is_available():
            analyzer = StockAnalyzer(client)
            llm_report = analyzer.generate_report(
                results.to_dict("records"), market_ctx
            )

    report_path = generate_screen_report(results, market_ctx, llm_report, mode=mode)
    click.echo(f"\nHTML报告已生成: {report_path}")

    _track_actual_returns()
    _save_predictions(results, mode=mode)

    if save:
        out_path = BASE_DIR / f"screen_{pd.Timestamp.now().strftime('%Y%m%d_%H%M')}.json"
        out_data = results.to_dict(orient="records") if isinstance(results, pd.DataFrame) else results
        out_path.write_text(json.dumps(out_data, ensure_ascii=False, indent=2, default=str))
        click.echo(f"JSON数据已保存: {out_path}")

    if open_browser:
        click.echo("正在打开浏览器...")
        webbrowser.open(f"file://{Path(report_path).resolve()}")
    else:
        click.echo(f"查看报告: 打开文件 {report_path}")


@cli.command()
@click.option("--top", "-n", default=10, help="显示前N只")
def top(top):
    """查看今日选股结果"""
    reports_dir = BASE_DIR / "reports"
    files = sorted(reports_dir.glob("screen_*.html"), reverse=True) if reports_dir.exists() else []
    if not files:
        click.echo("没有找到选股报告，请先运行 screen 命令")
        return
    click.echo(f"最新报告: {files[0]}")
    click.echo(f"查看报告: 打开文件 {files[0]}")


@cli.command()
@click.option("--symbol", "-s", required=True, help="股票代码")
@click.option("--no-llm", is_flag=True, help="跳过LLM分析")
@click.option("--open", "open_browser", is_flag=True, help="自动打开浏览器查看报告")
def analyze(symbol, no_llm, open_browser):
    """分析单只股票，给出买入概率和建议"""
    from data.sina_fetcher import sina_get_stock_hist
    from data.preprocessor import prepare_hist
    from features.technical import compute_technical_features, get_latest_features
    from models.ensemble import EnsembleModel
    from llm.analyzer import StockAnalyzer
    from llm.client import LLMClient
    from report import generate_analyze_report

    click.echo(f"\n正在分析 {symbol}...")

    hist = sina_get_stock_hist(symbol, days=120)
    if hist.empty:
        click.echo("无法获取历史数据，请检查股票代码")
        return

    hist = prepare_hist(hist)
    tech = compute_technical_features(hist)
    if tech.empty or len(tech) < 20:
        click.echo("数据不足，无法分析")
        return

    last = tech.iloc[-1]
    stock_name = _get_stock_name(symbol)

    # 计算ML预测概率
    buy_prob = _predict_buy_probability(tech, symbol)

    # 技术面分析
    tech_analysis = _analyze_technical(last, tech)

    # 生成建议
    advice = _generate_advice(buy_prob, tech_analysis, last)

    # 终端输出
    click.echo(f"\n{'='*60}")
    click.echo(f"  {symbol} {stock_name} 分析结果")
    click.echo(f"{'='*60}")
    click.echo(f"  买入概率: {buy_prob*100:.1f}%  {advice['rating']}")
    click.echo(f"  最新价:   {last.get('close', 0):.2f}")
    click.echo(f"  涨跌幅:   {last.get('change_pct', 0):.2f}%")
    click.echo(f"  止损位:   {advice['stop_loss']}")
    click.echo(f"  止盈位:   {advice['take_profit']}")
    click.echo(f"{'='*60}")

    # LLM分析
    llm_analysis = None
    if not no_llm:
        client = LLMClient()
        if client.is_available():
            click.echo("LLM深度分析中...")
            analyzer = StockAnalyzer(client)
            prompt = f"""分析股票{symbol} ({stock_name})：
当前价格: {last.get('close', 0):.2f}
RSI(14): {last.get('rsi_14', 0):.1f}
MACD差值: {last.get('macd_diff', 0):.4f}
5日量比: {last.get('vol_ratio_5', 0):.2f}
ML买入概率: {buy_prob*100:.1f}%
请给出：1) 买入理由 2) 风险提示 3) 操作建议"""
            llm_analysis = client.chat(prompt, temperature=0.3)
            click.echo(llm_analysis)

    # 生成HTML报告
    tech_indicators = {
        "close": last.get("close", 0),
        "change_pct": last.get("change_pct", 0),
        "rsi_14": last.get("rsi_14", 0),
        "macd_diff": last.get("macd_diff", 0),
        "bb_pct": last.get("bb_pct", 0),
        "vol_ratio_5": last.get("vol_ratio_5", 0),
        "atr_ratio": last.get("atr_ratio", 0),
        "adx_14": last.get("adx_14", 0),
        "mfi_14": last.get("mfi_14", 0),
        "obv_ret": last.get("obv_ret", 0),
    }
    report_path = generate_analyze_report(
        symbol, stock_name, buy_prob, tech_indicators, tech_analysis, advice, llm_analysis
    )
    click.echo(f"\nHTML报告已生成: {report_path}")

    if open_browser:
        click.echo("正在打开浏览器...")
        webbrowser.open(f"file://{Path(report_path).resolve()}")
    else:
        click.echo(f"查看报告: 打开文件 {report_path}")


@cli.command()
def cache_clear():
    """清除缓存"""
    from data.cache import clear_cache
    clear_cache()
    click.echo("缓存已清除")


@cli.command()
def cache_info():
    """查看缓存信息"""
    from config import CACHE_DIR
    files = list(CACHE_DIR.glob("*.pkl"))
    click.echo(f"缓存文件数: {len(files)}")
    for f in sorted(files)[:10]:
        size = f.stat().st_size / 1024
        click.echo(f"  {f.name}: {size:.1f}KB")


def _get_stock_name(symbol):
    try:
        from data.sina_fetcher import _get_sina_realtime
        df = _get_sina_realtime([symbol])
        if not df.empty:
            return df.iloc[0]["名称"]
    except Exception:
        pass
    return ""


def _predict_buy_probability(tech, symbol):
    try:
        model = EnsembleModel()
        if model.lgbm.load("ensemble_lgbm") and model.xgb.load("ensemble_xgb"):
            features = get_latest_features(tech)
            if features:
                model_features = model.lgbm.feature_names
                for f in model_features:
                    if f not in features:
                        features[f] = 0
                X = pd.DataFrame([features])[model_features].fillna(0)
                prob = model.predict_proba(X)[0]
                return float(prob)
    except Exception:
        pass
    last = tech.iloc[-1]
    score = 0.5
    rsi = last.get("rsi_14", 50)
    if 30 < rsi < 70:
        score += 0.05
    elif rsi < 30:
        score += 0.1
    elif rsi > 70:
        score -= 0.1
    macd = last.get("macd_diff", 0)
    if macd > 0:
        score += 0.05
    else:
        score -= 0.05
    vol = last.get("vol_ratio_5", 1)
    if 1 < vol < 2:
        score += 0.05
    elif vol > 2:
        score += 0.1
    return max(0.1, min(0.9, score))


def _analyze_technical(last, tech):
    analysis = {}
    rsi = last.get("rsi_14", 50)
    if rsi < 30:
        analysis["rsi_14"] = "超卖，可能反弹"
    elif rsi > 70:
        analysis["rsi_14"] = "超买，注意回调"
    else:
        analysis["rsi_14"] = "中性"

    macd = last.get("macd_diff", 0)
    if macd > 0:
        analysis["macd_diff"] = "多头，看涨"
    else:
        analysis["macd_diff"] = "空头，看跌"

    bb = last.get("bb_pct", 0.5)
    if bb < 0.2:
        analysis["bb_pct"] = "接近下轨，可能反弹"
    elif bb > 0.8:
        analysis["bb_pct"] = "接近上轨，注意回调"
    else:
        analysis["bb_pct"] = "中轨附近"

    vol = last.get("vol_ratio_5", 1)
    if vol > 1.5:
        analysis["vol_ratio_5"] = "放量，关注度高"
    elif vol < 0.7:
        analysis["vol_ratio_5"] = "缩量，观望为主"
    else:
        analysis["vol_ratio_5"] = "正常"

    atr = last.get("atr_ratio", 0.02)
    if atr > 0.03:
        analysis["atr_ratio"] = "高波动，风险大"
    elif atr < 0.015:
        analysis["atr_ratio"] = "低波动，平稳"
    else:
        analysis["atr_ratio"] = "正常波动"

    adx = last.get("adx_14", 25)
    if adx > 30:
        analysis["adx_14"] = "趋势强"
    elif adx < 20:
        analysis["adx_14"] = "无趋势，震荡"
    else:
        analysis["adx_14"] = "趋势一般"

    mfi = last.get("mfi_14", 50)
    if mfi > 80:
        analysis["mfi_14"] = "资金超买"
    elif mfi < 20:
        analysis["mfi_14"] = "资金超卖"
    else:
        analysis["mfi_14"] = "资金中性"

    obv = last.get("obv_ret", 0)
    if obv > 0.05:
        analysis["obv_ret"] = "资金持续流入"
    elif obv < -0.05:
        analysis["obv_ret"] = "资金持续流出"
    else:
        analysis["obv_ret"] = "资金平衡"

    return analysis


def _generate_advice(buy_prob, tech_analysis, last):
    if buy_prob >= 0.65:
        rating = "强烈推荐买入"
        emoji = "★★★"
    elif buy_prob >= 0.55:
        rating = "可以考虑买入"
        emoji = "★★☆"
    elif buy_prob >= 0.45:
        rating = "建议观望"
        emoji = "★☆☆"
    else:
        rating = "不建议买入"
        emoji = "☆☆☆"

    close = last.get("close", 0)
    stop_loss = close * 0.95
    take_profit = close * 1.05

    summary_parts = []
    if buy_prob >= 0.6:
        summary_parts.append("ML模型看好该股票")
    if tech_analysis.get("rsi_14", "").startswith("超卖"):
        summary_parts.append("RSI超卖有反弹空间")
    if tech_analysis.get("macd_diff", "").startswith("多头"):
        summary_parts.append("MACD看涨")
    if tech_analysis.get("vol_ratio_5", "").startswith("放量"):
        summary_parts.append("成交量放大")
    summary = "；".join(summary_parts) if summary_parts else "综合指标一般，建议谨慎"

    if buy_prob >= 0.65:
        buy_condition = "当前或回调至5日均线附近买入"
    elif buy_prob >= 0.55:
        buy_condition = "回调至10日均线附近买入"
    else:
        buy_condition = "等待更好的入场时机"

    return {
        "rating": f"{emoji} {rating}",
        "summary": summary,
        "buy_condition": buy_condition,
        "stop_loss": f"{stop_loss:.2f} (亏损5%)",
        "take_profit": f"{take_profit:.2f} (盈利5%)",
    }


@cli.command()
@click.option("--stocks", type=int, default=300, help="训练股票数量 (默认300)")
@click.option("--days", type=int, default=250, help="历史天数 (默认250)")
@click.option("--static", is_flag=True, help="使用静态股票池")
def train(stocks, days, static):
    """训练/更新ML模型"""
    from train import main as train_main
    import sys
    sys.argv = ["train"]
    if stocks != 300:
        sys.argv.extend(["--stocks", str(stocks)])
    if days != 250:
        sys.argv.extend(["--days", str(days)])
    if static:
        sys.argv.append("--static")
    train_main()


@cli.command()
def metrics():
    """查看模型训练历史和性能指标"""
    from config import BASE_DIR
    metrics_file = BASE_DIR / "training_data" / "model_metrics.json"
    if not metrics_file.exists():
        click.echo("没有找到训练指标，请先运行 train 命令")
        return
    history = json.loads(metrics_file.read_text())
    if not history:
        click.echo("训练指标为空")
        return
    click.echo("\n模型训练历史:")
    click.echo("-" * 80)
    click.echo(f"{'日期':<12} {'样本数':<8} {'正样本%':<8} {'LGBM_AUC':<10} {'XGB_AUC':<10} {'WF_AUC':<10}")
    click.echo("-" * 80)
    for entry in history[-10:]:
        click.echo(
            f"{entry['date']:<12} {entry['train_samples']:<8} "
            f"{entry['positive_ratio']*100:.1f}%{'':<4} "
            f"{entry['lgbm_auc']:<10} {entry['xgb_auc']:<10} {entry['wf_auc_mean']:<10}"
        )
    click.echo("-" * 80)


@cli.command()
@click.option("--days", type=int, default=5, help="回测天数")
def backtest(days):
    """回测最近N天选股结果"""
    from config import BASE_DIR
    pred_file = BASE_DIR / "training_data" / "predictions.json"
    if not pred_file.exists():
        click.echo("没有预测记录，请先运行选股")
        return
    predictions = json.loads(pred_file.read_text())
    if not predictions:
        click.echo("预测记录为空")
        return
    click.echo(f"\n最近 {days} 天预测回测:")
    click.echo("-" * 80)
    tracked = 0
    for entry in predictions[-days:]:
        date = entry.get("date", "")
        stocks = entry.get("stocks", [])
        click.echo(f"\n  {date} 选股结果:")
        for s in stocks[:5]:
            actual = s.get('actual_return')
            if actual is not None:
                actual_str = f"{actual:+.2%}"
                tracked += 1
            else:
                actual_str = "N/A"
            click.echo(f"    {s['code']} {s['name']:<8} 预测:{s['prob']:.1%} 实际:{actual_str}")
    click.echo("-" * 80)
    click.echo(f"已追踪收益率: {tracked} 条 | 运行 screen 命令自动回填")


@cli.command()
def versions():
    """查看模型版本历史"""
    from config import BASE_DIR
    version_dir = BASE_DIR / "saved_models" / "versions"
    if not version_dir.exists():
        click.echo("没有模型版本")
        return
    versions = sorted(set(
        f.name.split("_")[2].replace(".txt", "").replace(".json", "")
        for f in version_dir.iterdir()
        if f.name.startswith("ensemble_lgbm_") and "_meta" not in f.name
    ), reverse=True)
    click.echo(f"\n模型版本 ({len(versions)} 个):")
    for v in versions:
        click.echo(f"  {v}")


def _track_actual_returns():
    """回填历史预测的实际收益率"""
    from datetime import datetime
    pred_file = BASE_DIR / "training_data" / "predictions.json"
    if not pred_file.exists():
        return

    predictions = json.loads(pred_file.read_text())
    if not predictions:
        return

    today = datetime.now().strftime("%Y-%m-%d")

    has_old = any(
        entry.get("date") != today and
        any(s.get("actual_return") is None for s in entry.get("stocks", []))
        for entry in predictions
    )
    if not has_old:
        return

    try:
        from data.sina_fetcher import sina_get_market_snapshot
        snapshot = sina_get_market_snapshot(max_pages=30)
        if snapshot.empty:
            return
    except Exception:
        return

    price_map = {}
    for _, row in snapshot.iterrows():
        code = row.get("代码", "")
        price = row.get("最新价", 0)
        if code and price > 0:
            price_map[code] = float(price)

    updated = False
    for entry in predictions:
        if entry.get("date") == today:
            continue
        for stock in entry.get("stocks", []):
            if stock.get("actual_return") is not None:
                continue
            code = stock.get("code", "")
            buy_price = stock.get("price", 0)
            if code in price_map and buy_price > 0:
                current_price = price_map[code]
                ret = (current_price - buy_price) / buy_price
                stock["actual_return"] = round(ret, 4)
                updated = True

    if updated:
        pred_file.write_text(json.dumps(predictions, ensure_ascii=False, indent=2))


def _save_predictions(results, mode="confirm"):
    try:
        from datetime import datetime
        pred_file = BASE_DIR / "training_data" / "predictions.json"
        predictions = []
        if pred_file.exists():
            predictions = json.loads(pred_file.read_text())

        today = datetime.now().strftime("%Y-%m-%d")
        if mode == "confirm":
            predictions = [p for p in predictions if p.get("date") != today or p.get("session") != "confirm"]
        else:
            predictions = [p for p in predictions if p.get("date") != today or p.get("session") != "pre"]

        stocks = []
        for _, row in results.iterrows():
            stocks.append({
                "code": row.get("code", ""),
                "name": row.get("name", ""),
                "prob": float(row.get("buy_prob", 0)),
                "price": float(row.get("price", 0)),
                "actual_return": None,
            })

        predictions.append({
            "date": today,
            "timestamp": datetime.now().isoformat(),
            "session": mode,
            "stocks": stocks,
        })

        predictions = predictions[-30:]
        pred_file.write_text(json.dumps(predictions, ensure_ascii=False, indent=2))
    except Exception:
        pass


if __name__ == "__main__":
    cli()
