"""
Trading Memory Management System
交易记忆的分类存储、自动归档和检索
"""
import json, pathlib, time
from datetime import datetime, timezone, timedelta
from collections import defaultdict

PROJECT_ROOT = pathlib.Path(__file__).parent.absolute()
LOG_DIR  = PROJECT_ROOT / "logs"
MEM_DIR  = PROJECT_ROOT / "memory"
ARCHIVE  = LOG_DIR  / "archive"
SNAPSHOT = LOG_DIR  / "position_snapshots"

for d in [LOG_DIR, MEM_DIR, ARCHIVE, SNAPSHOT]:
    d.mkdir(parents=True, exist_ok=True)

# ============ 基础写入 ============
def _ndjson_append(file_path: pathlib.Path, record: dict, ts: str = None):
    file_path.parent.mkdir(parents=True, exist_ok=True)
    if ts is not None:
        record["ts"] = ts
    else:
        record.setdefault("ts", datetime.now(timezone.utc).isoformat())
    with open(file_path, "a", encoding="utf-8") as f:
        f.write(json.dumps(record, ensure_ascii=False) + "\n")

def _ndjson_read(file_path: pathlib.Path, limit=None, since_days=None):
    if not file_path.exists():
        return []
    records = []
    cutoff = None
    if since_days:
        cutoff = datetime.now(timezone.utc) - timedelta(days=since_days)
    try:
        for line in open(file_path, encoding="utf-8"):
            line = line.strip()
            if not line:
                continue
            try:
                rec = json.loads(line)
            except:
                continue
            if cutoff:
                try:
                    ts = datetime.fromisoformat(rec.get("ts","").replace("Z","+00:00"))
                    if ts < cutoff: continue
                except: continue
            records.append(rec)
    except: pass
    if limit:
        records = records[-limit:]
    return records

# ============ 交易账本 ============
TRADE_LEDGER  = LOG_DIR / "trading_ledger.ndjson"

def append_trade(ticket: int, side: str, entry: float, exit_price: float,
                 pnl: float, volume: float = 0.01,
                 duration_min: int = 0,
                 session: str = "UNKNOWN",
                 regime: str = "UNKNOWN",
                 confidence: float = 0,
                 strategy: str = "AUTO",
                 sl_hit: bool = False,
                 tp_hit: bool = False,
                 sl_price: float = 0,
                 tp_price: float = 0,
                 action: str = "CLOSE",
                 status: str = "FILLED",
                 ts: str = None):
    """
    交易完成（平仓）后调用，记录完整交易
    """
    rec = {
        "ticket": ticket, "side": side, "entry": entry,
        "exit": exit_price, "pnl": pnl, "volume": volume,
        "duration_min": duration_min,
        "session": session, "regime": regime,
        "confidence": confidence, "strategy": strategy,
        "sl_hit": sl_hit, "tp_hit": tp_hit,
        "sl_price": sl_price, "tp_price": tp_price,
        "action": action,
        "status": status,
    }
    _ndjson_append(TRADE_LEDGER, rec, ts=ts)
    return rec

def query_trades(limit=30, since_days=None, side=None, strategy=None):
    """
    查询历史交易
    query_trades(limit=10)  → 最近10笔
    query_trades(since_days=7) → 近7天
    query_trades(side="BUY")  → 只看多单
    """
    records = _ndjson_read(TRADE_LEDGER, since_days=since_days)
    if side:
        records = [r for r in records if r.get("side") == side]
    if strategy:
        records = [r for r in records if r.get("strategy") == strategy]
    if limit is not None:
        records = records[-limit:]
    return records

# ============ 信号账本 ============
SIGNAL_LEDGER = LOG_DIR / "signal_ledger.ndjson"

def append_signal(signal_id: str, action: str, side: str = None,
                  regime: str = "UNKNOWN", confidence: float = 0,
                  session: str = "UNKNOWN", agent_direction: str = "NEUTRAL",
                  governor: str = "APPROVED", result: str = "PENDING",
                  duration_sec: int = 0,
                  indicators: dict = None,
                  sl: float = 0, tp: float = 0, entry: float = 0):
    rec = {
        "signal_id": signal_id, "action": action, "side": side,
        "regime": regime, "confidence": confidence,
        "session": session, "agent_direction": agent_direction,
        "governor": governor, "result": result, "duration_sec": duration_sec,
        "indicators": indicators or {},
        "sl": sl, "tp": tp, "entry": entry,
    }
    _ndjson_append(SIGNAL_LEDGER, rec)
    return rec

def query_signals(limit=50, since_days=None, result=None):
    records = _ndjson_read(SIGNAL_LEDGER, since_days=since_days)
    if result:
        records = [r for r in records if r.get("result") == result]
    return records[-limit:]

# ============ 风险事件 ============
RISK_LEDGER = LOG_DIR / "risk_events.ndjson"

def append_risk_event(event_type: str, reason: str,
                     price_bid: float = 0, price_ask: float = 0,
                     spread: float = 0, threshold: float = 0,
                     action: str = "HOLD",
                     details: str = ""):
    rec = {
        "type": event_type,       # governor_block / sl_hit / tp_hit / extreme_spread / kill_switch
        "reason": reason,
        "price_bid": price_bid, "price_ask": price_ask,
        "spread": spread, "threshold": threshold,
        "action": action, "details": details,
    }
    _ndjson_append(RISK_LEDGER, rec)
    return rec

def query_risk_events(limit=50, since_days=None, event_type=None):
    records = _ndjson_read(RISK_LEDGER, since_days=since_days)
    if event_type:
        records = [r for r in records if r.get("type") == event_type]
    return records[-limit:]

# ============ 策略统计 ============
STRATEGY_STATS = LOG_DIR / "strategy_stats.ndjson"

def append_strategy_stat(date: str = None,
                        regime: str = "UNKNOWN",
                        total_signals: int = 0,
                        approved: int = 0, blocked: int = 0,
                        total_trades: int = 0,
                        wins: int = 0, losses: int = 0,
                        win_rate: float = 0,
                        total_pnl: float = 0,
                        avg_confidence: float = 0,
                        avg_spread: float = 0,
                        max_win: float = 0,
                        max_loss: float = 0):
    if date is None:
        date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    rec = {
        "date": date, "regime": regime,
        "total_signals": total_signals, "approved": approved, "blocked": blocked,
        "total_trades": total_trades, "wins": wins, "losses": losses,
        "win_rate": round(win_rate, 3),
        "total_pnl": round(total_pnl, 2),
        "avg_confidence": round(avg_confidence, 3),
        "avg_spread": round(avg_spread, 1),
        "max_win": round(max_win, 2), "max_loss": round(max_loss, 2),
    }
    _ndjson_append(STRATEGY_STATS, rec)
    return rec

def query_strategy_stats(limit=30, since_days=None):
    return _ndjson_read(STRATEGY_STATS, limit=limit, since_days=since_days)

# ============ Agent 决策记忆 ============
AGENT_DIR = LOG_DIR / "agent_decisions"

def append_agent_decision(direction: str, reason: str, confidence: float,
                          indicators: dict = None):
    today = datetime.now(timezone.utc).strftime("%Y%m%d")
    rec = {
        "direction": direction,
        "reason": reason,
        "confidence": confidence,
        "indicators": indicators or {},
        "date": today,
    }
    file_path = AGENT_DIR / f"decision_{today}.ndjson"
    _ndjson_append(file_path, rec)
    return rec

def query_agent_decisions(limit=10, since_days=None):
    records = _ndjson_read(AGENT_DIR, limit=limit, since_days=since_days)
    return records[-limit:]

# ============ 持仓快照 ============
def save_position_snapshot(positions: list, equity: float = 0, balance: float = 0):
    today = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M")
    file_path = SNAPSHOT / f"snapshot_{today}.json"
    data = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "positions": positions,
        "equity": equity, "balance": balance,
    }
    file_path.parent.mkdir(parents=True, exist_ok=True)
    file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
    return file_path

def query_position_snapshots(limit=10):
    files = sorted(SNAPSHOT.glob("snapshot_*.json"), reverse=True)
    results = []
    for f in files[:limit]:
        try:
            results.append(json.loads(f.read_text()))
        except: pass
    return results

# ============ 记忆总览（会话开始前调用）============
def get_memory_summary(days=7):
    trades = query_trades(limit=None, since_days=days)
    signals = query_signals(limit=20, since_days=days)
    risks = query_risk_events(limit=10, since_days=days)
    stats = query_strategy_stats(limit=7, since_days=days)
    agent_decisions = query_agent_decisions(limit=5, since_days=days)

    # 计算关键指标
    if trades:
        total_pnl = sum(t.get("pnl", 0) for t in trades)
        wins_list = [t for t in trades if t.get("pnl", 0) > 0]
        losses_list = [t for t in trades if t.get("pnl", 0) < 0]
        win_rate = len(wins_list) / max(len(trades), 1)
        max_win = max((t.get("pnl", 0) for t in trades), default=0)
        max_loss = min((t.get("pnl", 0) for t in trades), default=0)
        sl_hits = sum(1 for t in trades if t.get("sl_hit"))
        tp_hits = sum(1 for t in trades if t.get("tp_hit"))
    else:
        total_pnl = win_rate = max_win = max_loss = sl_hits = tp_hits = 0
        wins_list = losses_list = []

    if signals:
        approved = sum(1 for s in signals if s.get("result") in ("FILLED", "APPROVED"))
        blocked = sum(1 for s in signals if s.get("governor") != "APPROVED")
        avg_conf = sum(s.get("confidence", 0) for s in signals) / max(len(signals), 1)
    else:
        approved = blocked = avg_conf = 0

    # 最新方向决策
    latest_direction = agent_decisions[-1] if agent_decisions else {}

    return {
        "period_days": days,
        "total_trades": len(trades),
        "wins": len(wins_list), "losses": len(losses_list),
        "win_rate": round(win_rate, 3),
        "total_pnl": round(total_pnl, 2),
        "max_win": round(max_win, 2), "max_loss": round(max_loss, 2),
        "sl_hits": sl_hits, "tp_hits": tp_hits,
        "approved_signals": approved, "blocked_signals": blocked,
        "avg_confidence": round(avg_conf, 3),
        "risk_events": len(risks),
        "latest_agent_direction": latest_direction.get("direction", "NEUTRAL"),
        "latest_agent_reason": latest_direction.get("reason", ""),
        "recent_trades": trades[-5:],
        "recent_risks": risks[-5:],
    }

# ============ 自动归档（每日调用）============
def auto_archive():
    """每日自动归档：压缩旧持仓快照"""
    cutoff = datetime.now(timezone.utc) - timedelta(days=90)
    moved = 0
    archive_dir = ARCHIVE / "positions"
    archive_dir.mkdir(parents=True, exist_ok=True)
    for f in SNAPSHOT.glob("snapshot_*.json"):
        try:
            mtime = datetime.fromtimestamp(f.stat().st_mtime, tz=timezone.utc)
            if mtime < cutoff:
                dest = archive_dir / f.name
                f.rename(dest)
                moved += 1
        except: pass
    return moved

# ============ CLI ============
if __name__ == "__main__":
    import argparse
    ap = argparse.ArgumentParser()
    ap.add_argument("--summary", action="store_true", help="记忆总览")
    ap.add_argument("--trades", action="store_true", help="最近交易")
    ap.add_argument("--risks", action="store_true", help="风险事件")
    ap.add_argument("--stats", action="store_true", help="策略统计")
    ap.add_argument("--signals", action="store_true", help="信号记录")
    ap.add_argument("--archive", action="store_true", help="执行归档")
    ap.add_argument("--days", type=int, default=7, help="查询天数")
    ap.add_argument("--limit", type=int, default=10)
    args = ap.parse_args()

    if args.summary:
        print(json.dumps(get_memory_summary(days=args.days), indent=2, ensure_ascii=False))
    elif args.trades:
        for t in query_trades(limit=args.limit, since_days=args.days):
            print(f"  {t['ts'][:10]} | {t.get('side','?')} | entry={t.get('entry')} exit={t.get('exit')} pnl={t.get('pnl')} sl_hit={t.get('sl_hit')} tp_hit={t.get('tp_hit')}")
    elif args.risks:
        for r in query_risk_events(limit=args.limit, since_days=args.days):
            print(f"  {r['ts'][:10]} | {r['type']} | {r['reason']}")
    elif args.stats:
        for s in query_strategy_stats(limit=args.limit, since_days=args.days):
            print(f"  {s['date']} | regime={s['regime']} trades={s['total_trades']} win_rate={s['win_rate']} pnl={s['total_pnl']}")
    elif args.signals:
        for s in query_signals(limit=args.limit, since_days=args.days):
            print(f"  {s['ts'][:10]} | {s.get('action')} {s.get('side')} conf={s.get('confidence')} result={s.get('result')}")
    elif args.archive:
        moved = auto_archive()
        print(f"归档完成：{moved} 个旧快照已移动")
    else:
        # 默认输出总览
        print(json.dumps(get_memory_summary(days=args.days), indent=2, ensure_ascii=False))
