"""build_rebalance_engine_v2.py — REBALANCE_ENGINE_V2 (Score-Based Allocation)

개선 사항:
  1. Equal Weight 탈피: SS001_Norm_Score 를 가중치로 사용하여 종목별 목표 비중 동적 산출.
  2. 리스크 예산 연동: 신호가 강한 종목에 더 많은 비중을 배분.
  3. 로드맵 WBS-3.2 완결.

로직:
  ticker_target_pct = bucket_target_pct * (SS001_Score / sum(Scores_in_bucket))
"""
from __future__ import annotations

import argparse
import json
import math
import os
from pathlib import Path
from typing import Any

# V1 모듈 재사용을 위해 sys.path 추가 (필요시)
import sys
ROOT = Path(__file__).resolve().parents[1]
sys.path.append(str(ROOT))

# V1에서 유틸리티 함수 및 설정 상속 (직접 정의)
TEMP = ROOT / "Temp"
DEFAULT_JSON = ROOT / "GatherTradingData.json"
DEFAULT_OUT  = TEMP / "rebalance_engine_v2.json"
FORMULA_ID   = "REBALANCE_ENGINE_V2"

BUCKET_CONFIG: dict[str, dict] = {
    "Core":      {"target": 66.0, "min": 60.0, "max": 72.0},
    "Satellite": {"target": 17.5, "min": 10.0, "max": 25.0},
    "Cash":      {"target": 16.5, "min": 10.0, "max": 22.0},
}

CORE_TICKERS_BASE: set[str] = {"005930", "000660", "000270"}

REGIME_BANDS: dict[str, dict] = {
    "RISK_ON":            {"label": "RISK_ON ±15%p",    "expand": 15.0, "contract": 15.0},
    "NEUTRAL":            {"label": "NEUTRAL ±5%p",     "expand":  5.0, "contract":  5.0},
    "RISK_OFF":           {"label": "RISK_OFF +2/−10%p", "expand":  2.0, "contract": 10.0},
    "_DEFAULT":           {"label": "NEUTRAL ±5%p",     "expand":  5.0, "contract":  5.0},
}

TX_COST_ROUNDTRIP  = 0.007
COST_BENEFIT_THRESHOLD = 0.005
MIN_ACTIONABLE_DRIFT_PCT = 1.2
STAGE_RATIOS = [0.30, 0.30, 0.40]

def _load(path: Path) -> Any:
    if not path.exists(): return {}
    try: return json.loads(path.read_text(encoding="utf-8"))
    except: return {}

def _f(v: Any, default: float = 0.0) -> float:
    try: return float(v)
    except: return default

def _detect_force_signal(row: dict) -> str:
    combined = " ".join([str(row.get(k) or "").upper() for k in ["Sell_Reason", "Final_Action", "Sell_Action"]])
    if "ABS_FLOOR" in combined: return "ABS_FLOOR"
    if any(k in combined for k in ["TIME_STOP", "TIME_EXIT"]): return "TIME_STOP"
    return ""

def _extract_df_rows(payload: Any) -> list[dict]:
    return payload.get("data", {}).get("data_feed", [])

def _extract_portfolio_totals(payload: Any) -> tuple[float, float]:
    settings = payload.get("data", {}).get("settings", {})
    return _f(settings.get("total_asset_krw")), _f(settings.get("settlement_cash_d2_krw"))

def _extract_regime(payload: Any) -> str:
    macro = payload.get("data", {}).get("macro", [])
    for r in macro:
        if str(r.get("Symbol")) == "REGIME_PRELIM":
            return str(r.get("Close")).upper()
    return "NEUTRAL"

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--json", default=str(DEFAULT_JSON))
    ap.add_argument("--out", default=str(DEFAULT_OUT))
    args = ap.parse_args()

    payload = _load(Path(args.json))
    df_rows = _extract_df_rows(payload)
    regime = _extract_regime(payload)
    total_asset, cash_krw = _extract_portfolio_totals(payload)
    
    # 1. 종목별 데이터 추출 (점수 포함)
    holdings = []
    bucket_scores: dict[str, float] = {}
    
    for row in df_rows:
        ticker = str(row.get("Ticker", ""))
        mv = _f(row.get("Account_Market_Value"))
        if mv <= 0: continue
        
        # 신호 점수 추출 (SS001_Norm_Score)
        score = _f(row.get("SS001_Norm_Score"), 50.0) # 기본값 50
        bucket = "Core" if ticker in CORE_TICKERS_BASE else "Satellite"
        
        holdings.append({
            "ticker": ticker,
            "name": row.get("Name"),
            "bucket": bucket,
            "weight_pct": _f(row.get("Weight_Pct")),
            "score": score,
            "close": _f(row.get("Close")),
            "qty": _f(row.get("Account_Holding_Qty")),
            "force_signal": _detect_force_signal(row)
        })
        bucket_scores[bucket] = bucket_scores.get(bucket, 0.0) + score

    # 2. 버킷별 목표 비중 및 종목별 목표 비중 계산 (고도화 로직)
    ticker_rows = []
    band = REGIME_BANDS.get(regime, REGIME_BANDS["_DEFAULT"])
    
    for h in holdings:
        bucket_target = BUCKET_CONFIG[h["bucket"]]["target"]
        total_score_in_bucket = bucket_scores[h["bucket"]]
        
        # [V2 핵심] 점수 비례 목표 비중 산출
        if total_score_in_bucket > 0:
            target_pct = round(bucket_target * (h["score"] / total_score_in_bucket), 2)
        else:
            target_pct = round(bucket_target / 1, 2) # fallback

        current_pct = h["weight_pct"]
        drift = round(current_pct - target_pct, 2)
        
        # 밴드 설정
        b_min = round(target_pct - band["contract"], 2)
        b_max = round(target_pct + band["expand"], 2)
        
        # 액션 결정
        action = "HOLD"
        if h["force_signal"]:
            action = "SELL"
        elif drift > MIN_ACTIONABLE_DRIFT_PCT:
            action = "SELL"
        elif drift < -MIN_ACTIONABLE_DRIFT_PCT:
            action = "BUY"
            
        ticker_rows.append({
            "ticker": h["ticker"],
            "name": h["name"],
            "bucket": h["bucket"],
            "score": h["score"],
            "target_pct": target_pct,
            "current_pct": current_pct,
            "drift_pct": drift,
            "action": action,
            "reason": h["force_signal"] or ("DRIFT" if action != "HOLD" else "OK")
        })

    # 3. 결과 요약
    core_pct = sum(h["current_pct"] for h in ticker_rows if h["bucket"] == "Core")
    sat_pct = sum(h["current_pct"] for h in ticker_rows if h["bucket"] == "Satellite")
    cash_pct = round(cash_krw / total_asset * 100, 2) if total_asset > 0 else 0

    summary = {
        "regime": regime,
        "total_asset": total_asset,
        "core_pct": core_pct,
        "satellite_pct": sat_pct,
        "cash_pct": cash_pct,
        "allocation_method": "SS001_SCORE_WEIGHTED"
    }

    out = {
        "formula_id": FORMULA_ID,
        "summary": summary,
        "tickers": ticker_rows
    }
    
    Path(args.out).parent.mkdir(parents=True, exist_ok=True)
    Path(args.out).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"REBALANCE_ENGINE_V2 Complete. Allocation: {summary['allocation_method']}")
    return 0

if __name__ == "__main__":
    main()
