#!/usr/bin/env python3
"""
幻化技能数据看板 — 数据生成脚本 V2
三级数据链路：东方财富实时 → Tushare 日线 → 成本价 fallback
Usage: python3 generate_data.py
"""
import json, requests, time, os, sys, subprocess
from datetime import datetime, timedelta

TODAY = datetime.now().strftime('%Y-%m-%d')
DATA_DIR = '/root/.hermes/www/data'
os.makedirs(DATA_DIR, exist_ok=True)
OUTPUT = os.path.join(DATA_DIR, f'{TODAY}.json')

# ═══════════════════════════════════════════
# Tier 1: 东方财富实时快照
# ═══════════════════════════════════════════
def east_single(secid):
    url = 'https://push2.eastmoney.com/api/qt/stock/get'
    params = {'secid': secid, 'fields': 'f43,f57,f58,f60,f170,f169', 'fltt': '2'}
    try:
        resp = requests.get(url, params=params, timeout=10)
        resp.raise_for_status()
        data = resp.json().get('data') or {}
        price_raw = data.get('f43')
        price = float(price_raw) / 100 if price_raw and '/' not in str(price_raw) else float(price_raw or 0)
        return {'code': data.get('f57',''), 'name': data.get('f58',''), 'price': price, 'change_pct': data.get('f170')}
    except:
        return {'code':'','name':'','price':None,'change_pct':None}

def to_secid(code):
    return f'1.{code}' if code.startswith(('6','5','9')) else f'0.{code}'

# ═══════════════════════════════════════════
# Tier 2: Tushare 日线数据
# ═══════════════════════════════════════════
_tushare_pro = None

def get_tushare():
    global _tushare_pro
    if _tushare_pro:
        return _tushare_pro
    token = os.environ.get('HUANHUA_TUSHARE_TOKEN') or os.environ.get('TUSHARE_TOKEN')
    if not token:
        try:
            r = subprocess.run(['python3','/root/.hermes/bin/get_tushare_token.py'],
                             capture_output=True, text=True, timeout=10)
            token = r.stdout.strip().split('\n')[-1].strip()
        except:
            pass
    if not token:
        return None
    import tushare as ts
    _tushare_pro = ts.pro_api(token)
    return _tushare_pro

def ts_code(code):
    return f'{code}.SH' if code.startswith(('6','5','9')) else f'{code}.SZ'

def tushare_prices(codes, lookback_days=10):
    """Pull latest close prices from Tushare daily."""
    pro = get_tushare()
    if not pro:
        print('  ⚠ Tushare token unavailable', file=sys.stderr)
        return {}, 'unavailable'
    
    end = datetime.now().strftime('%Y%m%d')
    start = (datetime.now() - timedelta(days=lookback_days)).strftime('%Y%m%d')
    ts_codes = [ts_code(c) for c in codes]
    
    try:
        df = pro.daily(ts_code=','.join(ts_codes), start_date=start, end_date=end)
        if len(df) == 0:
            return {}, 'empty'
        df = df.sort_values('trade_date')
        result = {}
        latest_date = df.trade_date.max()
        for _, row in df.iterrows():
            code = row.ts_code[:6]
            if code not in result or row.trade_date > result[code]['date']:
                result[code] = {'price': row.close, 'change_pct': row.pct_chg, 'date': row.trade_date}
        return result, latest_date
    except Exception as e:
        print(f'  ⚠ Tushare error: {e}', file=sys.stderr)
        return {}, 'error'

def get_indices(codes_list):
    """Pull index close prices — Tushare first, AKShare fallback."""
    pro = get_tushare()
    result = {}
    latest = ''
    
    # Tier 1: Tushare
    if pro:
        ts_map = {'000001':'000001.SH','399001':'399001.SZ','399006':'399006.SZ'}
        end = datetime.now().strftime('%Y%m%d')
        start = (datetime.now() - timedelta(days=30)).strftime('%Y%m%d')
        try:
            df = pro.index_daily(ts_code=','.join(ts_map.values()), start_date=start, end_date=end)
            if len(df) > 0:
                latest = df.trade_date.max()
                # Only accept data within 30 days
                latest_dt = datetime.strptime(latest, '%Y%m%d')
                if (datetime.now() - latest_dt).days < 30:
                    for _, row in df.iterrows():
                        c = row.ts_code[:6]
                        if c not in result or row.trade_date > result[c]['date']:
                            result[c] = {'price': row.close, 'change_pct': row.pct_chg, 'date': row.trade_date}
        except:
            pass
    
    # Tier 2: AKShare fallback
    if not result:
        try:
            import requests, ssl, warnings
            ssl._create_default_https_context = ssl._create_unverified_context
            warnings.filterwarnings('ignore')
            import akshare as ak
            
            ak_map = {'000001': 'sh000001', '399001': 'sz399001', '399006': 'sz399006'}
            for code, ak_sym in ak_map.items():
                if code in codes_list:
                    df_ak = ak.stock_zh_index_daily(symbol=ak_sym)
                    if len(df_ak) > 0:
                        row = df_ak.iloc[-1]
                        prev = df_ak.iloc[-2] if len(df_ak) > 1 else row
                        pct = (row['close'] / prev['close'] - 1) * 100 if prev['close'] else 0
                        date_str = str(row['date']).replace('-','')
                        result[code] = {'price': row['close'], 'change_pct': round(pct, 2), 'date': date_str}
                        if not latest or date_str > latest:
                            latest = date_str
            if result:
                print(f'  ✅ AKShare 指数兜底: {len(result)} 个')
        except Exception as e:
            print(f'  ⚠ AKShare 指数兜底失败: {e}', file=sys.stderr)
    
    return result, latest

def tushare_finance(codes):
    """Pull financial indicators for holdings."""
    pro = get_tushare()
    if not pro: return {}
    ts_codes = [ts_code(c) for c in codes]
    # Find latest quarter with data
    now = datetime.now()
    result = {}
    for period_offset in range(0, 3):  # Try current and previous 2 quarters
        period = (now - timedelta(days=90*period_offset)).strftime('%Y') + '0331'
        try:
            df = pro.fina_indicator(ts_code=','.join(ts_codes), period=period,
                                   fields='ts_code,end_date,roe,grossprofit_margin,debt_to_assets,eps')
            if len(df) == 0:
                period = period[:4] + '0630'
                df = pro.fina_indicator(ts_code=','.join(ts_codes), period=period,
                                       fields='ts_code,end_date,roe,grossprofit_margin,debt_to_assets,eps')
            if len(df) == 0:
                period = period[:4] + '0930'
                df = pro.fina_indicator(ts_code=','.join(ts_codes), period=period,
                                       fields='ts_code,end_date,roe,grossprofit_margin,debt_to_assets,eps')
            if len(df):
                for _, row in df.iterrows():
                    c = row.ts_code[:6]
                    if c not in result:
                        result[c] = {
                            'period': row.end_date,
                            'roe': round(row.roe, 1) if row.roe else None,
                            'gross_margin': round(row.grossprofit_margin, 1) if row.grossprofit_margin else None,
                            'debt_ratio': round(row.debt_to_assets, 1) if row.debt_to_assets else None,
                            'eps': round(row.eps, 4) if row.eps else None}
                missing = [c for c in codes if c not in result]
                if not missing:
                    break
                codes = missing
        except:
            pass
    return result

# ═══════════════════════════════════════════
# Symbols
# ═══════════════════════════════════════════
INDICES_DEF = {'000001':'上证指数','399001':'深证成指','399006':'创业板指'}
HOLDINGS_DEF = [
    {'code':'601012','name':'隆基绿能','shares':1500,'cost':14.365},
    {'code':'002027','name':'分众传媒','shares':1000,'cost':5.425},
]
WATCHLIST_DEF = ['300274','002920','002230','301025','002415','300015','002690','600436','603019']
ETFS_DEF = ['518880','159659','516860','561220','159857','513010','159915','512480','159807','511010',
            '512930','159551','515700','159867','159755','159840','159883','159875','510170','159934']

# Name fallback mapping (when API doesn't return names)
NAME_MAP = {
    '300274':'阳光电源','002920':'德赛西威','002230':'科大讯飞',
    '301025':'读客文化','002415':'海康威视','300015':'爱尔眼科',
    '002690':'美亚光电','600436':'片仔癀','603019':'中科曙光',
    '518880':'黄金ETF','159659':'AI人工智能','516860':'金融科技',
    '561220':'半导体ETF','159857':'光伏ETF','513010':'恒生科技',
    '159915':'创业板ETF','512480':'半导体ETF','159807':'科技ETF',
    '511010':'国债ETF','512930':'AI算力ETF','159551':'机器人ETF',
    '515700':'新能源车','159867':'医药ETF','159755':'电池ETF',
    '159840':'芯片ETF','159883':'医疗器械','159875':'稀土ETF',
    '510170':'商品ETF','159934':'黄金ETF',
}

# ═══════════════════════════════════════════
# Main
# ═══════════════════════════════════════════
def main():
    all_codes = list(INDICES_DEF.keys()) + [h['code'] for h in HOLDINGS_DEF] + WATCHLIST_DEF + ETFS_DEF
    HOLDING_CODES = [h['code'] for h in HOLDINGS_DEF]
    
    # ── Tier 1: Eastmoney ──
    print(f'📡 Tier 1: 东方财富 {len(all_codes)} 只标的...')
    east_ok = 0
    snapshots = {}
    for i, code in enumerate(all_codes):
        snap = east_single(to_secid(code))
        if snap['code']:
            snapshots[snap['code']] = snap
            east_ok += 1
        else:
            snapshots[code] = {'code':code,'name':'','price':None,'change_pct':None}
        if (i+1) % 10 == 0:
            print(f'  ... {i+1}/{len(all_codes)} ({east_ok} ok)')
        time.sleep(0.15)
    
    east_ratio = east_ok / len(all_codes) if all_codes else 0
    print(f'  东方财富成功率: {east_ok}/{len(all_codes)} ({east_ratio:.0%})')
    
    # ── Tier 2: Tushare fallback for missing ──
    ts_prices = {}
    ts_latest_date = ''
    ts_finance = {}
    idx_prices = {}
    data_source = 'eastmoney_realtime' if east_ratio > 0.5 else 'tushare_daily'
    
    if east_ratio < 0.5:
        print(f'📡 Tier 2: Tushare 日线兜底...')
        stock_codes = HOLDING_CODES + WATCHLIST_DEF
        ts_prices, ts_latest_date = tushare_prices(stock_codes)
        if ts_prices:
            print(f'  Tushare 最新交易日: {ts_latest_date}, {len(ts_prices)} 只')
            data_source = f'tushare_daily_{ts_latest_date}'
        else:
            data_source = 'cost_basis_fallback'
            print('  Tushare 不可用，回退到成本价')
        
        # Index from Tushare / AKShare fallback
        idx_prices, _ = get_indices(list(INDICES_DEF.keys()))
        if idx_prices:
            print(f'  指数: {len(idx_prices)} 个')
        
        # Financial data from Tushare
        ts_finance = tushare_finance(HOLDING_CODES)
        if ts_finance:
            print(f'  财务数据: {len(ts_finance)} 只')
    
    # ── Merge data ──
    # Price priority: Eastmoney > Tushare > Cost basis
    def get_price(code):
        s = snapshots.get(code, {})
        p = s.get('price')
        if p is not None and p != 0:
            return p, s.get('change_pct'), 'eastmoney'
        ts = ts_prices.get(code, {})
        if ts.get('price'):
            return ts['price'], ts.get('change_pct'), 'tushare'
        # Cost basis or fallback
        for h in HOLDINGS_DEF:
            if h['code'] == code:
                return h['cost'], None, 'cost_basis'
        return 0, None, 'missing'
    
    # ── Build indices ──
    indices = []
    for code, name in INDICES_DEF.items():
        p, pct, src = get_price(code)
        # Index data from Tushare fallback
        idx_info = idx_prices.get(('000001' if code=='000001' else code), {})
        if not p and idx_info.get('price'):
            p, pct = idx_info['price'], idx_info.get('change_pct')
            src = 'tushare_idx'
        if p:
            indices.append({
                'name': name,
                'value': f'{p:.2f}',
                'change': f'{float(pct):+.2f}%' if pct is not None else '-'})
    
    # ── Build holdings ──
    holdings = []
    up = 0
    for h in HOLDINGS_DEF:
        price, pct, src = get_price(h['code'])
        if not price:
            price = h['cost']
            src = 'cost_basis'
        pct = pct or 0
        pl_pct = (price - h['cost']) / h['cost'] * 100 if h['cost'] > 0 else 0
        if pct > 0: up += 1
        holdings.append({
            'code': h['code'], 'name': h['name'], 'shares': h['shares'],
            'close': round(price, 2),
            'change': f'{float(pct):+.1f}%' if pct else '-',
            'cost': h['cost'],
            'pl_pct': f'{pl_pct:+.1f}%',
            'score': max(50, min(80, int(50 + pl_pct * 2))),
            'signal': {'eastmoney': '实时行情', 'tushare': f'Tushare日线({ts_latest_date})', 
                       'cost_basis': '成本价(休市)'}.get(src, src)})
    
    # ── Build watchlist ──
    watchlist = []
    for code in WATCHLIST_DEF:
        price, pct, src = get_price(code)
        if not price:
            price = 0
        pct = pct or 0
        if pct > 0: up += 1
        watchlist.append({
            'code': code, 
            'name': snapshots.get(code, {}).get('name', '') or NAME_MAP.get(code, ''),
            'close': round(price, 2),
            'change': f'{float(pct):+.1f}%' if pct else '-',
            'score': 60,
            'signal': f'{"+" if pct and float(pct)>0 else ""}{float(pct):.1f}%' if pct else '-'})
    
    # ── Build ETFs ──
    etfs = []
    for code in ETFS_DEF:
        price, pct, src = get_price(code)
        if not price:
            price = 0
        pct = pct or 0
        etfs.append({
            'code': code, 
            'name': snapshots.get(code, {}).get('name', '') or NAME_MAP.get(code, ''),
            'close': round(price, 3) if price and price > 0 else '--',
            'change': f'{float(pct):+.1f}%' if pct and price and price > 0 else '--',
            'signal': {'eastmoney': '实时', 'tushare': '日线', 'cost_basis': '休市', 'missing': '休市'}.get(src, src)})
    
    # ── Market signal ──
    total = len(holdings) + len(watchlist)
    ratio = up / total if total else 0.5
    ms = '偏强' if ratio > 0.6 else ('震荡' if ratio > 0.4 else '偏弱')
    
    # ── Finance ──
    current_prices = {h['code']: h['close'] for h in holdings}
    total_cost = sum(h['cost'] * h['shares'] for h in HOLDINGS_DEF)  # 26972.50
    
    fin_holdings = []
    for h in HOLDINGS_DEF:
        cp = current_prices.get(h['code'], h['cost'])
        mv = cp * h['shares']
        pl = mv - h['cost'] * h['shares']
        pl_pct = (cp / h['cost'] - 1) * 100 if h['cost'] else 0
        
        fin_row = {
            'code': h['code'], 'name': h['name'],
            'shares': h['shares'], 'cost': h['cost'],
            'currentPrice': round(cp, 2),
            'costTotal': round(h['cost'] * h['shares'], 2),
            'marketValue': round(mv, 2),
            'pl': round(pl, 2),
            'pl_pct': f'{pl_pct:+.1f}%'
        }
        # Enrich with Tushare financials
        fin = ts_finance.get(h['code'])
        if fin:
            fin_row.update(fin)
        fin_holdings.append(fin_row)
    
    total_market = sum(fh['marketValue'] for fh in fin_holdings)
    
    # ── Generate JSON ──
    data = {
        'generated_at': datetime.now().isoformat(),
        'data_date': ts_latest_date or TODAY,
        'data_source': data_source,
        'summary': {
            'date': TODAY,
            'market_signal': ms,
            'score_avg': 65,
            'alerts_total': total,
            'alerts_active': up,
            'top_movers': sorted(
                holdings + watchlist,
                key=lambda x: float(str(x.get('change','0%')).replace('%','').replace('+','').replace('-','') or 0),
                reverse=True)[:2],
            'sectors': ['光伏','AI算力','传媒']},
        'intraday': [],
        'daily': {
            'time': datetime.now().strftime('%H:%M'),
            'indices': indices,
            'holdings': holdings,
            'watchlist': watchlist,
            'etfs': etfs,
            'analysis': (
                f'数据源: {data_source} · '
                f'{up}/{total} 上涨 · 市场{ms} · '
                f'{"实时行情" if east_ratio > 0.5 else f"Tushare日线({ts_latest_date})"}')},
        'evening': {
            'time': '--',
            'news': [],
            'summary': '晚间事件待 Tushare 新闻接口盘后采集。'},
        'finance': {
            'initialCash': 29591.26,
            'holdings': fin_holdings,
            'marginLiability': 0,
            'totalCost': total_cost,
            'totalMarket': round(total_market, 2),
            'totalPL': round(total_market - total_cost, 2),
            'totalPLPct': f'{(total_market/total_cost - 1)*100:+.1f}%' if total_cost else '0%',
            'fundamentals': ts_finance,
            'history': {
                'daily': [{
                    'date': ts_latest_date or TODAY,
                    'value': round(total_market, 2),
                    'pl': round(total_market - total_cost, 2),
                    'pl_pct': f'{(total_market/total_cost - 1)*100:+.1f}%' if total_cost else '0%'
                }]}}}
    
    with open(OUTPUT, 'w') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    
    print(f'\n✅ Written {OUTPUT}')
    print(f'   Data source: {data_source}')
    print(f'   指数: {len(indices)} | 持仓: {len(holdings)} | 自选: {len(watchlist)} | ETF: {len(etfs)}')
    print(f'   上涨: {up}/{total} | 市场信号: {ms}')
    for fh in fin_holdings:
        print(f'   {fh["name"]}: ¥{fh["currentPrice"]:.2f} x{fh["shares"]} = ¥{fh["marketValue"]:,.2f} ({fh["pl_pct"]})')
    print(f'   总市值: ¥{total_market:,.2f} | 总成本: ¥{total_cost:,.2f} | 盈亏: ¥{total_market-total_cost:+,.2f}')

if __name__ == '__main__':
    main()
