#!/usr/bin/env python3
"""migrate_all.py — 一次性将全部生产 JSON 数据迁移到 mofin.db

运行方式:
    python3 migrate_all.py

迁移映射:
    portfolio.json       → holdings + holding_strategies (from analysis)
    watchlist.json       → watchlist_stocks + holding_strategies (strategy_type='watch')
    candidate_pool.json  → candidates + candidate_score_history
    decisions.json       → holding_strategies (changelog history)
    price_events.json    → price_events
    stock_sector_map.json → stock_sectors
    evaluation.json      → strategy_evaluations
    stock_profiles.json  → stocks (name, exchange, type)
"""

import json
import sys
from datetime import datetime
from pathlib import Path

from mofin_db import get_conn, init_all_tables

DATA_DIR = Path(__file__).parent / "data"


def load_json(name: str):
    path = DATA_DIR / name
    if not path.exists():
        print(f"  [SKIP] {name} not found")
        return None
    try:
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except Exception as e:
        print(f"  [FAIL] {name}: {e}")
        return None


def _normalize_code(code) -> str:
    """统一代码格式：整数→补零字符串"""
    s = str(code).strip()
    if s.isdigit():
        if len(s) == 5:
            return s
        if len(s) < 5:
            return s.zfill(5 if len(s) <= 4 else 6)
        return s.zfill(6)
    return s


def _guess_exchange(code: str) -> str:
    if len(code) == 5 and code.isdigit():
        return "HK"
    if code.startswith(("6", "5", "9")):
        return "SH"
    return "SZ"


def collect_all_stocks() -> dict:
    """从所有 JSON 文件中收集股票代码→名称映射"""
    all_stocks = {}

    def add(code, name):
        code = _normalize_code(code)
        if not code or code in all_stocks:
            return
        all_stocks[code] = {
            "name": str(name) if name else code,
            "exchange": _guess_exchange(code),
            "type": "H" if len(code) == 5 else "A",
        }

    # stock_profiles.json
    profiles = load_json("stock_profiles.json")
    if profiles:
        for p in profiles.get("profiles", []):
            add(p.get("code"), p.get("name"))

    # portfolio.json
    pf = load_json("portfolio.json")
    if pf:
        for h in pf.get("holdings", []):
            add(h.get("code"), h.get("name"))

    # watchlist.json
    wl = load_json("watchlist.json")
    if wl:
        for s in wl.get("stocks", []):
            add(s.get("code"), s.get("name"))

    # decisions.json
    dec = load_json("decisions.json")
    if dec:
        for d in dec.get("decisions", []):
            add(d.get("code"), d.get("name"))

    # candidate_pool.json
    cp = load_json("candidate_pool.json")
    if cp:
        for c in cp.get("candidates", []):
            add(c.get("code"), c.get("name"))

    # price_events.json
    pe = load_json("price_events.json")
    if pe:
        for e in pe.get("events", []):
            add(e.get("code"), e.get("name"))

    return all_stocks


def migrate_all_stocks(conn, all_stocks: dict) -> int:
    """将所有收集到的股票写入 stocks 表"""
    count = 0
    for code, info in all_stocks.items():
        try:
            conn.execute(
                "INSERT OR REPLACE INTO stocks (code, name, exchange, type, updated_at) VALUES (?, ?, ?, ?, ?)",
                (code, info["name"], info["exchange"], info["type"], datetime.now().isoformat()))
            count += 1
        except Exception as e:
            print(f"    stocks error {code}: {e}")
    conn.commit()
    return count


def migrate_holdings(conn, portfolio_data: dict) -> tuple[int, int]:
    """portfolio.json → holdings + holding_strategies"""
    holdings = portfolio_data.get("holdings", [])
    h_count, s_count = 0, 0

    for h in holdings:
        code = _normalize_code(h.get("code", ""))
        name = h.get("name", "")
        shares = int(h.get("shares", 0))
        cost = h.get("cost", 0)
        pos_pct = h.get("position_pct", 0)

        if not code or not name:
            continue

        # holdings 表
        try:
            conn.execute(
                "INSERT OR REPLACE INTO holdings (code, name, shares, cost, position_pct, is_active) "
                "VALUES (?, ?, ?, ?, ?, 1)",
                (code, name, shares, cost, pos_pct))
            h_count += 1
        except Exception as e:
            print(f"    holdings error {code}: {e}")
            continue

        # holding_strategies (from analysis field)
        analysis = h.get("analysis", {})
        if analysis:
            sl = analysis.get("stop_loss")
            tp = analysis.get("take_profit")
            el = analysis.get("entry_low")
            eh = analysis.get("entry_high")
            reason = analysis.get("action", "")[:200] if analysis.get("action") else None
            reassessed = analysis.get("reassessed_at")
            try:
                conn.execute(
                    "INSERT INTO holding_strategies (code, version, stop_loss, take_profit, "
                    "entry_low, entry_high, strategy_type, source, reason, created_at) "
                    "VALUES (?, 1, ?, ?, ?, ?, 'holding', 'migrate', ?, ?)",
                    (code, sl, tp, el, eh, reason, reassessed or datetime.now().isoformat()))
                s_count += 1
            except Exception as e:
                print(f"    strategy error {code}: {e}")

    conn.commit()
    return h_count, s_count


def migrate_watchlist(conn, watchlist_data: dict) -> tuple[int, int]:
    """watchlist.json → watchlist_stocks + holding_strategies"""
    stocks = watchlist_data.get("stocks", [])
    w_count, s_count = 0, 0

    for s in stocks:
        code = _normalize_code(s.get("code", ""))
        name = s.get("name", "")
        if not code or not name:
            continue

        # watchlist_stocks
        try:
            conn.execute(
                "INSERT OR REPLACE INTO watchlist_stocks (code, name, added_at, is_active) "
                "VALUES (?, ?, ?, 1)",
                (code, name, s.get("added_at", datetime.now().isoformat())))
            w_count += 1
        except Exception as e:
            print(f"    watchlist error {code}: {e}")
            continue

        # holding_strategies (from strategy field)
        strategy = s.get("strategy", {})
        if strategy:
            buy_zone = strategy.get("buy_zone", [])
            el = buy_zone[0] if len(buy_zone) > 0 else None
            eh = buy_zone[1] if len(buy_zone) > 1 else None
            tp_zone = strategy.get("take_profit", [])
            tp = tp_zone[0] if isinstance(tp_zone, list) and len(tp_zone) > 0 else tp_zone
            try:
                conn.execute(
                    "INSERT INTO holding_strategies (code, version, stop_loss, take_profit, "
                    "entry_low, entry_high, strategy_type, source, created_at) "
                    "VALUES (?, 1, ?, ?, ?, ?, 'watch', 'migrate', ?)",
                    (code, strategy.get("stop_loss"), tp, el, eh,
                     strategy.get("updated_at", datetime.now().isoformat())))
                s_count += 1
            except Exception as e:
                print(f"    watch strategy error {code}: {e}")

    conn.commit()
    return w_count, s_count


def migrate_decisions(conn, decisions_data: dict) -> int:
    """decisions.json → holding_strategies (changelog history)"""
    decisions = decisions_data.get("decisions", [])
    count = 0

    for d in decisions:
        code = _normalize_code(d.get("code", ""))
        if not code:
            continue

        # 最新策略
        sl = d.get("stop_loss")
        el = d.get("entry_low")
        eh = d.get("entry_high")
        tp = d.get("take_profit")
        action = d.get("action", "")[:200] if d.get("action") else None
        try:
            conn.execute(
                "INSERT INTO holding_strategies (code, version, stop_loss, take_profit, "
                "entry_low, entry_high, strategy_type, source, reason, created_at) "
                "VALUES (?, 1, ?, ?, ?, ?, 'decision', 'migrate', ?, ?)",
                (code, sl, tp, el, eh, action, d.get("timestamp", datetime.now().isoformat())))
            count += 1
        except Exception as e:
            print(f"    decision error {code}: {e}")
            continue

        # changelog 历史版本
        changelog = d.get("changelog", [])
        for i, cl in enumerate(changelog):
            try:
                # 从 new_action 中提取止损/止盈/买入区
                new_action = cl.get("new_action", "")
                # 简单提取：损XX 盈XX 买XX~XX
                import re
                sl_match = re.search(r'损(\d+\.?\d*)', new_action)
                tp_match = re.search(r'盈(\d+\.?\d*)', new_action)
                entry_match = re.search(r'买(\d+\.?\d*)~(\d+\.?\d*)', new_action)

                conn.execute(
                    "INSERT INTO holding_strategies (code, version, stop_loss, take_profit, "
                    "entry_low, entry_high, strategy_type, source, reason, created_at) "
                    "VALUES (?, ?, ?, ?, ?, ?, 'decision', 'migrate', ?, ?)",
                    (code, i + 2,
                     float(sl_match.group(1)) if sl_match else None,
                     float(tp_match.group(1)) if tp_match else None,
                     float(entry_match.group(1)) if entry_match else None,
                     float(entry_match.group(2)) if entry_match else None,
                     cl.get("reason", "")[:200],
                     cl.get("date", datetime.now().isoformat())))
                count += 1
            except Exception:
                pass  # 解析失败跳过

    conn.commit()
    return count


def migrate_candidates(conn, candidate_data: dict) -> tuple[int, int]:
    """candidate_pool.json → candidates + candidate_score_history"""
    candidates = candidate_data.get("candidates", [])
    c_count, s_count = 0, 0

    for c in candidates:
        code = _normalize_code(c.get("code", ""))
        name = c.get("name", "")
        if not code or not name:
            continue

        # candidates 表
        try:
            conn.execute(
                "INSERT OR REPLACE INTO candidates (code, name, sector, reason, entry_range, "
                "stop_loss, target, zhiwei_star, zhiwei_reviewed, zhiwei_reviewed_at, "
                "promoted, promoted_at, dropped, drop_reason, created_at) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                (code, name, c.get("sector"), c.get("reason"), c.get("entry_range"),
                 c.get("stop_loss"), c.get("target"), c.get("zhiwei_star"),
                 1 if c.get("zhiwei_reviewed") else 0, c.get("zhiwei_reviewed_at"),
                 1 if c.get("promoted") else 0, c.get("promoted_at"),
                 1 if c.get("dropped") else 0, c.get("drop_reason"),
                 c.get("added_at", datetime.now().isoformat())))
            c_count += 1
        except Exception as e:
            print(f"    candidate error {code}: {e}")
            continue

        # candidate_score_history
        score_history = c.get("score_history", [])
        for sh in score_history:
            try:
                conn.execute(
                    "INSERT INTO candidate_score_history (code, score, source, created_at) "
                    "VALUES (?, ?, 'xiaoguo', ?)",
                    (code, sh.get("score"), sh.get("date", datetime.now().isoformat())))
                s_count += 1
            except Exception:
                pass

    conn.commit()
    return c_count, s_count


def migrate_price_events(conn, events_data: dict) -> int:
    """price_events.json → price_events 表"""
    events = events_data.get("events", [])
    count = 0
    for e in events:
        try:
            conn.execute(
                "INSERT INTO price_events (code, name, event_type, price, trigger_value, event_label, date) "
                "VALUES (?, ?, ?, ?, ?, ?, ?)",
                (e.get("code"), e.get("name"), e.get("event_type"),
                 e.get("price"), e.get("trigger_value"), e.get("event_label"),
                 e.get("date", datetime.now().strftime("%Y-%m-%d"))))
            count += 1
        except Exception:
            pass
    conn.commit()
    return count


def migrate_evaluations(conn, eval_data: dict) -> int:
    """evaluation.json → strategy_evaluations"""
    strategies = eval_data.get("strategies", [])
    count = 0
    for s in strategies:
        code = _normalize_code(s.get("code", ""))
        if not code:
            continue
        theoretical = s.get("theoretical", {}).get("strategy", {})
        try:
            conn.execute(
                "INSERT INTO strategy_evaluations (code, eval_type, status, "
                "old_stop_loss, new_stop_loss, old_tp, new_tp, reason, created_at) "
                "VALUES (?, 'migrate', 'completed', NULL, ?, NULL, ?, ?, ?)",
                (code, theoretical.get("stop_loss"), theoretical.get("take_profit"),
                 s.get("updated_reason", "")[:200],
                 s.get("updated_at", datetime.now().isoformat())))
            count += 1
        except Exception:
            pass
    conn.commit()
    return count


def migrate_sectors(conn) -> int:
    """stock_sector_map.json → stock_sectors"""
    data = load_json("stock_sector_map.json")
    if not data:
        return 0
    count = 0
    for code, sectors in data.items():
        if code.startswith("_") or not isinstance(sectors, list):
            continue
        for sector in sectors:
            try:
                conn.execute(
                    "INSERT OR IGNORE INTO stock_sectors (code, sector_name, source) VALUES (?, ?, 'ths')",
                    (code, sector))
                count += 1
            except Exception:
                pass
    conn.commit()
    return count


def migrate_portfolio_summary(conn, pf: dict) -> int:
    """portfolio.json 顶层字段 → portfolio_summary"""
    try:
        conn.execute(
            "INSERT OR REPLACE INTO portfolio_summary (id, total_assets, stock_value, cash, position_pct, total_pnl, updated_at) "
            "VALUES (1, ?, ?, ?, ?, ?, ?)",
            (pf.get("total_assets"), pf.get("stock_value"), pf.get("cash"),
             pf.get("position_pct"), pf.get("total_pnl"), pf.get("updated_at", datetime.now().isoformat())))
        conn.commit()
        return 1
    except Exception:
        return 0


def migrate_advice_timeline(conn, dec: dict) -> int:
    """decisions.json advice_timeline[] → advice_timeline"""
    count = 0
    for d in dec.get("decisions", []):
        code = _normalize_code(d.get("code", ""))
        if not code:
            continue
        timeline = d.get("advice_timeline", [])
        for t in timeline:
            try:
                conn.execute(
                    "INSERT INTO advice_timeline (code, date, direction, price, summary, status, evaluated, result, evaluated_at, report_id) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (code, t.get("date"), t.get("direction"), t.get("price"),
                     t.get("summary"), t.get("status"),
                     1 if t.get("evaluated") else 0, t.get("result"),
                     t.get("evaluated_at"), t.get("report_id")))
                count += 1
            except Exception:
                pass
    conn.commit()
    return count


def migrate_accuracy_stats(conn, acc: dict) -> int:
    """accuracy_stats.json → accuracy_stats"""
    try:
        conn.execute(
            "INSERT OR REPLACE INTO accuracy_stats (id, period_start, period_end, total_advice, correct, wrong, partial, unknown, pending, ignored, evaluated, accuracy_pct, "
            "phase1_correct, phase1_wrong, phase1_pending, phase1_accuracy, "
            "phase2_correct, phase2_wrong, phase2_pending, phase2_accuracy, total_evaluated, updated_at) "
            "VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
            (acc.get("period_start"), acc.get("period_end"),
             acc.get("total_advice"), acc.get("correct"), acc.get("wrong"),
             acc.get("partial"), acc.get("unknown"), acc.get("pending"),
             acc.get("ignored"), acc.get("evaluated"), acc.get("accuracy_pct"),
             acc.get("phase1", {}).get("correct", 0) if isinstance(acc.get("phase1"), dict) else 0,
             acc.get("phase1", {}).get("wrong", 0) if isinstance(acc.get("phase1"), dict) else 0,
             acc.get("phase1", {}).get("pending", 0) if isinstance(acc.get("phase1"), dict) else 0,
             acc.get("phase1", {}).get("accuracy_pct", 0) if isinstance(acc.get("phase1"), dict) else 0,
             acc.get("phase2", {}).get("correct", 0) if isinstance(acc.get("phase2"), dict) else 0,
             acc.get("phase2", {}).get("wrong", 0) if isinstance(acc.get("phase2"), dict) else 0,
             acc.get("phase2", {}).get("pending", 0) if isinstance(acc.get("phase2"), dict) else 0,
             acc.get("phase2", {}).get("accuracy_pct", 0) if isinstance(acc.get("phase2"), dict) else 0,
             acc.get("total_evaluated"), acc.get("updated_at", datetime.now().isoformat())))
        conn.commit()
        return 1
    except Exception:
        return 0


def migrate_strategy_feedback(conn, sf: dict) -> int:
    """strategy_feedback.json → strategy_feedback"""
    count = 0
    for f in sf.get("feedback", []):
        code = _normalize_code(f.get("code", ""))
        if not code:
            continue
        pc = f.get("phase_check", {})
        try:
            conn.execute(
                "INSERT INTO strategy_feedback (code, name, evaluated_at, "
                "phase1_completed, phase1_result, phase1_completed_at, phase1_price, "
                "phase2_completed, phase2_result, phase2_completed_at, days_in_phase1, adjustments_json) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                (code, f.get("name"), f.get("evaluated_at"),
                 1 if pc.get("phase1_completed") else 0, pc.get("phase1_result"),
                 pc.get("phase1_completed_at"), pc.get("phase1_price_at_completion"),
                 1 if pc.get("phase2_completed") else 0, pc.get("phase2_result"),
                 pc.get("phase2_completed_at"), pc.get("days_in_phase1"),
                 json.dumps(f.get("adjustments", []), ensure_ascii=False)))
            count += 1
        except Exception:
            pass
    conn.commit()
    return count


def migrate_klines(conn) -> tuple[int, int, int]:
    """multi_tf_cache.json → stock_daily + stock_weekly + stock_monthly + stock_fundamentals"""
    data = load_json("multi_tf_cache.json")
    if not data:
        return 0, 0, 0

    daily_count, weekly_count, monthly_count = 0, 0, 0

    for code, stock in data.items():
        code = _normalize_code(code)
        if not code or code.startswith("_"):
            continue

        name = code  # 从 stocks 表或 profiles 获取名称
        try:
            row = conn.execute("SELECT name FROM stocks WHERE code = ?", (code,)).fetchone()
            if row:
                name = row[0]
        except Exception:
            pass

        # K线数据
        for period, table, key in [
            ("daily", "stock_daily", "daily"),
            ("weekly", "stock_weekly", "weekly"),
            ("monthly", "stock_monthly", "monthly"),
        ]:
            bars = stock.get(key, [])
            if not bars or isinstance(bars, dict):
                continue
            rows = []
            for b in bars:
                if not isinstance(b, dict):
                    continue
                d = b.get("date", "")
                if not d:
                    continue
                if period == "daily":
                    rows.append((code, d, b.get("open"), b.get("close"),
                                 b.get("high"), b.get("low"), b.get("volume"),
                                 b.get("amount")))
                else:
                    rows.append((code, d, b.get("open"), b.get("close"),
                                 b.get("high"), b.get("low"), b.get("volume")))

            if rows:
                try:
                    if period == "daily":
                        conn.executemany(
                            "INSERT OR REPLACE INTO stock_daily (code, date, open, close, high, low, volume, amount) "
                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", rows)
                        daily_count += len(rows)
                    elif period == "weekly":
                        conn.executemany(
                            "INSERT OR REPLACE INTO stock_weekly (code, date, open, close, high, low, volume) "
                            "VALUES (?, ?, ?, ?, ?, ?, ?)", rows)
                        weekly_count += len(rows)
                    else:
                        conn.executemany(
                            "INSERT OR REPLACE INTO stock_monthly (code, date, open, close, high, low, volume) "
                            "VALUES (?, ?, ?, ?, ?, ?, ?)", rows)
                        monthly_count += len(rows)
                except Exception:
                    pass

        # 基本面
        fundamentals = stock.get("fundamentals")
        if fundamentals and isinstance(fundamentals, dict):
            try:
                conn.execute(
                    "INSERT OR REPLACE INTO stock_fundamentals (code, pe, pb, eps, mcap_total, mcap_flow, updated_at) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?)",
                    (code, fundamentals.get("pe"), fundamentals.get("pb"),
                     fundamentals.get("eps"), fundamentals.get("mcap_total"),
                     fundamentals.get("mcap_flow"), datetime.now().isoformat()))
            except Exception:
                pass

    conn.commit()
    return daily_count, weekly_count, monthly_count


def main():
    print("=" * 60)
    print("  MoFin 数据迁移 → mofin.db")
    print("=" * 60)

    conn = get_conn()
    conn.execute("PRAGMA foreign_keys=OFF")  # 迁移期间关闭外键（部分代码格式不一致）
    init_all_tables(conn)

    totals = {}

    # 1. stocks (必须先迁，从所有源收集)
    all_stocks = collect_all_stocks()
    n = migrate_all_stocks(conn, all_stocks)
    totals["stocks"] = n
    print(f"\n[1/8] stocks: {n} 只 (from all sources)")

    # 2. holdings + strategies
    pf = load_json("portfolio.json")
    if pf:
        h, s = migrate_holdings(conn, pf)
        totals["holdings"] = h
        totals["holding_strategies"] = s
        print(f"[2/8] holdings: {h} 只, strategies: {s} 条")

    # 3. watchlist
    wl = load_json("watchlist.json")
    if wl:
        w, s = migrate_watchlist(conn, wl)
        totals["watchlist"] = w
        totals["watch_strategies"] = s
        print(f"[3/8] watchlist: {w} 只, strategies: {s} 条")

    # 4. decisions → strategies
    dec = load_json("decisions.json")
    if dec:
        n = migrate_decisions(conn, dec)
        totals["decision_strategies"] = n
        print(f"[4/8] decisions → strategies: {n} 条")

    # 5. candidates
    cp = load_json("candidate_pool.json")
    if cp:
        c, s = migrate_candidates(conn, cp)
        totals["candidates"] = c
        totals["score_history"] = s
        print(f"[5/8] candidates: {c} 只, score_history: {s} 条")

    # 6. price_events
    pe = load_json("price_events.json")
    if pe:
        n = migrate_price_events(conn, pe)
        totals["price_events"] = n
        print(f"[6/8] price_events: {n} 条")

    # 7. evaluations
    ev = load_json("evaluation.json")
    if ev:
        n = migrate_evaluations(conn, ev)
        totals["evaluations"] = n
        print(f"[7/8] evaluations: {n} 条")

    # 8. sectors
    n = migrate_sectors(conn)
    totals["sector_mappings"] = n
    print(f"[8/8] stock_sectors: {n} 条映射")

    # 9. portfolio_summary
    pf = load_json("portfolio.json")
    if pf:
        n = migrate_portfolio_summary(conn, pf)
        totals["portfolio_summary"] = n
        print(f"[9/12] portfolio_summary: {n} 条")

    # 10. advice_timeline
    dec = load_json("decisions.json")
    if dec:
        n = migrate_advice_timeline(conn, dec)
        totals["advice_timeline"] = n
        print(f"[10/12] advice_timeline: {n} 条")

    # 11. accuracy_stats
    acc = load_json("accuracy_stats.json")
    if acc:
        n = migrate_accuracy_stats(conn, acc)
        totals["accuracy_stats"] = n
        print(f"[11/12] accuracy_stats: {n} 条")

    # 12. strategy_feedback
    sf = load_json("strategy_feedback.json")
    if sf:
        n = migrate_strategy_feedback(conn, sf)
        totals["strategy_feedback"] = n
        print(f"[12/13] strategy_feedback: {n} 条")

    # 13. K线数据
    dc, wc, mc = migrate_klines(conn)
    totals["daily_klines"] = dc
    totals["weekly_klines"] = wc
    totals["monthly_klines"] = mc
    print(f"[13/13] K-lines: daily={dc}, weekly={wc}, monthly={mc}")

    conn.commit()

    # ── 验证 ──
    print(f"\n{'='*60}")
    print(f"  迁移完成 — 数据库验证")
    print(f"{'='*60}")
    tables = {
        "stocks": "SELECT COUNT(*) FROM stocks",
        "holdings": "SELECT COUNT(*) FROM holdings",
        "holding_strategies": "SELECT COUNT(*) FROM holding_strategies",
        "watchlist_stocks": "SELECT COUNT(*) FROM watchlist_stocks",
        "candidates": "SELECT COUNT(*) FROM candidates",
        "candidate_score_history": "SELECT COUNT(*) FROM candidate_score_history",
        "price_events": "SELECT COUNT(*) FROM price_events",
        "strategy_evaluations": "SELECT COUNT(*) FROM strategy_evaluations",
        "stock_sectors": "SELECT COUNT(*) FROM stock_sectors",
        "portfolio_summary": "SELECT COUNT(*) FROM portfolio_summary",
        "advice_timeline": "SELECT COUNT(*) FROM advice_timeline",
        "accuracy_stats": "SELECT COUNT(*) FROM accuracy_stats",
        "strategy_feedback": "SELECT COUNT(*) FROM strategy_feedback",
        "stock_daily": "SELECT COUNT(*) FROM stock_daily",
        "stock_weekly": "SELECT COUNT(*) FROM stock_weekly",
        "stock_monthly": "SELECT COUNT(*) FROM stock_monthly",
        "stock_fundamentals": "SELECT COUNT(*) FROM stock_fundamentals",
    }
    for name, sql in tables.items():
        n = conn.execute(sql).fetchone()[0]
        print(f"  {name:<25} {n:>6} 行")

    conn.close()
    print(f"\n[OK] Migration complete. JSON files untouched, safe to re-run.")


if __name__ == "__main__":
    main()
