#!/usr/bin/env python3
"""
mo_data.py — MoFin 统一数据层（纯 DB）

所有数据从 SQLite 读取。不做 JSON fallback。
JSON 文件已弃用，仅保留为历史备份。

用法:
  from mo_data import read_portfolio, read_decisions, read_watchlist

  pf = read_portfolio()   # 返回和 portfolio.json 一样的 dict 结构
  dec = read_decisions()  # 返回和 decisions.json 一样的 dict 结构
  wl = read_watchlist()   # 返回和 watchlist.json 一样的 dict 结构
"""

import sqlite3, json, sys
from datetime import datetime
from pathlib import Path

DB_PATH = '/home/hmo/MoFin/data/mofin.db'
SCRIPT_DIR = Path('/home/hmo/MoFin/scripts')


def _get_db():
    db = sqlite3.connect(DB_PATH)
    db.row_factory = sqlite3.Row
    return db


# ── portfolio ─────────────────────────────────────────────────────

def read_portfolio():
    """返回 portfolio.json 等价 dict。纯 DB。"""
    db = _get_db()
    rows = db.execute(
        "SELECT code, name, shares, cost, price, market_value, "
        "change_pct, currency, position_pct "
        "FROM holdings WHERE is_active=1"
    ).fetchall()
    holdings = []
    for r in rows:
        h = dict(r)
        h['_currency'] = h.get('currency', 'CNY')
        holdings.append(h)

    sum_row = db.execute("SELECT * FROM portfolio_summary WHERE id=1").fetchone()
    summary = dict(sum_row) if sum_row else {}

    db.close()

    return {
        "holdings": holdings,
        "total_assets": summary.get("total_assets", 0),
        "total_mv": summary.get("total_mv", 0),
        "stock_value": summary.get("stock_value", summary.get("total_mv", 0)),
        "cash": summary.get("cash", 0),
        "frozen_cash": summary.get("frozen_cash", 0),
        "position_pct": summary.get("position_pct", 0),
        "currency": summary.get("currency", "CNY"),
        "updated_at": summary.get("updated_at", ""),
    }


# ── decisions ─────────────────────────────────────────────────────

def _parse_json(val, default):
    if val:
        try: return json.loads(val)
        except: pass
    return default


def read_decisions():
    """返回 decisions.json 等价 dict。纯 DB。"""
    db = _get_db()
    rows = db.execute(
        "SELECT code, name, version, price, cost, shares, "
        "stop_loss, take_profit, entry_low, entry_high, "
        "currency, strategy_type, action, timing_signal, "
        "rr_ratio, tech_snapshot, stock_category, sector_context, "
        "status, trigger_json, changelog_json, source, reason, "
        "created_at, updated_at, "
        "avg_price, decision_timestamp, note, quality_check, "
        "quality_checked_at, quality_issues_json, position_advice, "
        "signal_factors_json, time_horizon, decision_type "
        "FROM holding_strategies WHERE status IN ('active','updated') "
        "ORDER BY code"
    ).fetchall()

    decisions = []
    for r in rows:
        d = dict(r)
        d['trigger'] = _parse_json(r['trigger_json'], {})
        d['changelog'] = _parse_json(r['changelog_json'], [])
        d['quality_issues'] = _parse_json(r['quality_issues_json'], {})
        d['signal_factors'] = _parse_json(r['signal_factors_json'], [])
        d['timestamp'] = r['decision_timestamp'] or r['created_at'] or ''
        d['type'] = r['decision_type'] or r['strategy_type'] or '持仓策略'
        decisions.append(d)

    db.close()

    return {
        "decisions": decisions,
        "total": len(decisions),
        "regenerated_at": datetime.now().strftime('%Y-%m-%d %H:%M'),
    }


# ── watchlist ─────────────────────────────────────────────────────

def read_watchlist():
    """返回 watchlist 等价 dict。纯 DB。
    从 holding_strategies（自选策略）读取，watchlist_stocks 已废弃。"""
    db = _get_db()
    # 主数据源：holding_strategies 自选策略
    rows = db.execute(
        "SELECT code, name, price, entry_low, entry_high, "
        "stop_loss, currency, updated_at "
        "FROM holding_strategies WHERE status='active' AND decision_type='自选策略'"
    ).fetchall()
    
    stocks = []
    seen = set()
    for r in rows:
        code = str(r["code"])
        if code in seen:
            continue
        seen.add(code)
        stocks.append({
            "code": code,
            "name": r["name"] or "",
            "price": r["price"] or 0,
            "entry_low": r["entry_low"] or 0,
            "entry_high": r["entry_high"] or 0,
            "stop_loss": r["stop_loss"] or 0,
            "currency": r["currency"] or "CNY",
            "added_at": r["updated_at"] or "",
            "analysis": {},
        })
    
    return {"stocks": stocks, "total": len(stocks)}

    db.close()

    return {
        "stocks": stocks,
        "updated_at": datetime.now().strftime('%Y-%m-%d %H:%M'),
    }


# ── 便捷别名 ───────────────────────────────────────────────────────

def read_portfolio_json():
    return read_portfolio()

def read_decisions_json():
    return read_decisions()

def read_watchlist_json():
    return read_watchlist()


# ── 统一价格获取（唯一入口，禁止各脚本自拉API）──

def get_price(code, max_age_minutes=5, use_stale_fallback=True):
    """获取单只股票最新价格。
    
    优先级: live_prices(DB) → stock_quote(API兜底)
    - live_prices 有且不超过 max_age_minutes → 直接返回
    - 没有或过期 → 调 stock_quote 拉，写回 live_prices
    - 都失败 → 返回 (None, None)
    
    返回 (price, change_pct)，两值都是 float 或 None。
    """
    from mofin_db import get_price_from_db
    from datetime import datetime, timedelta
    
    # 1. 先读 DB
    try:
        db_price, db_chg = get_price_from_db(code)
        if db_price is not None and db_price > 0:
            # 检查时效性
            conn = __import__('sqlite3').connect(str(DB_PATH))
            row = conn.execute(
                "SELECT updated_at FROM live_prices WHERE code=?",
                (str(code).strip(),)
            ).fetchone()
            conn.close()
            if row and row[0]:
                try:
                    updated = datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S")
                    age = (datetime.now() - updated).total_seconds() / 60
                    if age <= max_age_minutes:
                        return (db_price, db_chg)
                except:
                    pass
            else:
                return (db_price, db_chg)
    except Exception:
        pass
    
    # 2. DB 没有或过期 → 调 stock_quote
    if not use_stale_fallback:
        return (None, None)
    
    try:
        import subprocess, json
        r = subprocess.run(
            [sys.executable, str(SCRIPT_DIR / "stock_quote.py"), str(code)],
            capture_output=True, text=True, timeout=15
        )
        if r.returncode == 0:
            data = json.loads(r.stdout.strip())
            price = float(data.get("price", 0))
            chg = float(data.get("change_pct", 0))
            if price > 0:
                # 写回 live_prices
                try:
                    conn = __import__('sqlite3').connect(str(DB_PATH))
                    conn.execute("""
                        INSERT OR REPLACE INTO live_prices (code, price, change_pct, updated_at)
                        VALUES (?, ?, ?, datetime('now','localtime'))
                    """, (str(code).strip(), price, chg))
                    conn.commit()
                    conn.close()
                except:
                    pass
                return (price, chg)
    except Exception:
        pass
    
    return (None, None)


def get_prices_batch(codes, max_age_minutes=5):
    """批量获取价格，返回 {code: (price, change_pct)}"""
    from mofin_db import get_prices_batch_from_db
    
    result = {}
    need_api = []
    
    # 1. 批量读 DB
    try:
        db_prices = get_prices_batch_from_db(codes)
        for code in codes:
            cs = str(code).strip()
            if cs in db_prices:
                p, c = db_prices[cs]
                if p and p > 0:
                    result[cs] = (p, c)
                    continue
            need_api.append(cs)
    except:
        need_api = [str(c).strip() for c in codes]
    
    # 2. 缺失的调 API
    if need_api:
        try:
            import subprocess, json
            r = subprocess.run(
                [sys.executable, str(SCRIPT_DIR / "stock_quote.py")] + need_api,
                capture_output=True, text=True, timeout=30
            )
            if r.returncode == 0:
                for line in r.stdout.strip().split("\n"):
                    if not line:
                        continue
                    try:
                        data = json.loads(line)
                        code = str(data.get("code", "")).strip()
                        price = float(data.get("price", 0))
                        chg = float(data.get("change_pct", 0))
                        if code and price > 0:
                            result[code] = (price, chg)
                    except:
                        pass
        except:
            pass
    
    return result


# ── cash_log 写入 ──────────────────────────────────────────────────

def write_cash_log(cash_before, cash_after, frozen_before, frozen_after,
                   source, note, verified=0):
    """记录现金变更到 cash_log 表。"""
    change_amount = round(cash_after - cash_before, 2) if cash_after is not None and cash_before is not None else 0
    db = sqlite3.connect(DB_PATH)
    try:
        cur = db.execute(
            """INSERT INTO cash_log
               (timestamp, cash_before, cash_after, frozen_before, frozen_after,
                change_amount, source, note, verified)
               VALUES (datetime('now','localtime'), ?, ?, ?, ?, ?, ?, ?, ?)""",
            (cash_before, cash_after, frozen_before, frozen_after,
             change_amount, source, note, verified)
        )
        db.commit()
        return cur.lastrowid
    finally:
        db.close()


# ── 自检 ───────────────────────────────────────────────────────────

if __name__ == "__main__":
    pf = read_portfolio()
    print(f"portfolio: {len(pf.get('holdings',[]))} holdings, total_assets={pf.get('total_assets',0)}")

    dec = read_decisions()
    print(f"decisions: {len(dec.get('decisions',[]))} entries")

    wl = read_watchlist()
    print(f"watchlist: {len(wl.get('stocks',[]))} stocks")
