#!/usr/bin/env python3
"""Refresh live_account_state.json from the current positions CSV and target rules."""

from __future__ import annotations

import argparse
import csv
import datetime as dt
import json
from pathlib import Path


ROOT = Path(__file__).resolve().parent
DEFAULT_CSV = ROOT / "current_positions_from_screenshots_2026-06-07.csv"
STATE_PATH = ROOT / "live_account_state.json"
STATE_JS_PATH = ROOT / "live_account_state.js"
WEEKEND_FLOW_PATH = ROOT / "weekend_flow_state.json"

START_MAIN = 54_000_000
START_SUB = 49_000_000

TARGETS = {
    "NASA": {"t1": 35.0, "t2": 37.5, "hold_until": 37.5, "prior_high": 42.87, "invalid": 31.0, "action": "HOLD_TO_REBOUND_TRIM", "action_ko": "T1 25-35% 축소", "priority": 1, "name": "TEMA Space Innovation ETF", "bucket": "우주 ETF", "flow_key": "SPACE", "note": "SpaceX 노출 기대는 유지하되 이벤트 프리미엄이 커서 T1부터 현금화. 강한 장이면 T2까지 잔여만."},
    "RDW": {"t1": 20.5, "t2": 22.5, "hold_until": 25.0, "analyst_target": 25.0, "prior_high": 25.0, "invalid": 17.5, "action": "CONDITIONAL_HOLD_TO_ANALYST_TARGET", "action_ko": "$25 조건부 홀드", "priority": 2, "name": "Redwire", "bucket": "우주 고베타", "flow_key": "SPACE", "note": "사용자 입력 단기 애널리스트 목표가 $25를 상단 시나리오로 반영. 단, 프리마켓 갭다운/희석성 뉴스/우주 테마 약세면 T1-T2에서 먼저 현금화."},
    "MU": {"t1": 900.0, "t2": 970.0, "hold_until": 970.0, "prior_high": 1031.67, "invalid": 830.0, "action": "HOLD_QUALITY_CHECK", "action_ko": "T1 유지, T2 일부 익절", "priority": 3, "name": "Micron", "bucket": "반도체", "flow_key": "SEMIS", "note": "HBM/메모리 구조는 강하지만 금요일 반도체 청산이 컸음. SMH 회복 동반 시 T2까지 보유 가능."},
    "AAOI": {"t1": 185.0, "t2": 195.0, "hold_until": 195.0, "prior_high": 200.63, "invalid": 165.0, "action": "TRIM_FIRST", "action_ko": "T1 정리 우선", "priority": 4, "name": "Applied Optoelectronics", "bucket": "소형 고베타", "flow_key": "RISK_ON", "note": "소형 광통신 고베타. 시장이 강하지 않으면 T1 반등을 현금화 기회로 사용."},
    "AMD": {"t1": 475.0, "t2": 500.0, "hold_until": 500.0, "prior_high": 477.53, "invalid": 440.0, "action": "HOLD_OR_SMALL_TRIM", "action_ko": "T1 유지, T2 일부 축소", "priority": 5, "name": "AMD", "bucket": "반도체", "flow_key": "SEMIS", "note": "부계좌 보유라 레버리지 대체 불가. 반도체 리더십 회복 시 유지, 약하면 T2 전 축소."},
    "MRVL": {"t1": 286.0, "t2": 300.0, "hold_until": 300.0, "prior_high": 272.88, "invalid": 260.0, "action": "HOLD_RELATIVE_STRENGTH", "action_ko": "상대강도 홀드", "priority": 6, "name": "Marvell", "bucket": "반도체/AI 인프라", "flow_key": "SEMIS", "note": "AI 인프라 상대강도 후보. 단 Broadcom발 repricing이 이어지면 260 이탈을 엄격히 본다."},
    "GNTA": {"t1": 2.05, "t2": 2.3, "hold_until": 2.3, "prior_high": 2.08, "invalid": 1.75, "action": "CLEANUP", "action_ko": "정리 가능", "priority": 7, "name": "Genenta", "bucket": "소형 바이오", "flow_key": "RISK_ON", "note": "계좌 영향 작음. 복잡도 제거 목적의 정리 후보."},
}

FLOW_DEFAULT = {
    "status": "대기",
    "risk_on_score": 0,
    "summary": "무료 주말 플로우 데이터가 아직 갱신되지 않았습니다.",
    "direct_coverage": [],
    "proxies": [],
    "source_note": "Hyperliquid/Binance 무료 공개 API만 사용. 유료 데이터 없음.",
}

SINGLE_STOCK_LEVERAGE_PRODUCTS = {
    "AMD": [
        {"ticker": "AMUU", "provider": "Direxion", "direction": "LONG_2X", "daily_target": "200%", "note": "AMD 일일 +2배"},
        {"ticker": "AMDL", "provider": "GraniteShares", "direction": "LONG_2X", "daily_target": "200%", "note": "AMD 일일 +2배"},
    ],
    "MU": [
        {"ticker": "MUU", "provider": "Direxion", "direction": "LONG_2X", "daily_target": "200%", "note": "MU 일일 +2배"},
        {"ticker": "MULL", "provider": "GraniteShares", "direction": "LONG_2X", "daily_target": "200%", "note": "MU 일일 +2배"},
    ],
    "MRVL": [
        {"ticker": "MRVU", "provider": "Direxion", "direction": "LONG_2X", "daily_target": "200%", "note": "MRVL 일일 +2배"},
    ],
}

ANALYST_SIGNALS = {
    "AMD": {
        "score": 2,
        "summary": "Vivek Arya/BofA가 TipRanks 공개 기사 기준 Buy 유지 및 목표가 $500 상향. AI 반도체 top-pick basket에 AMD 포함.",
        "analysts": [
            {"name": "Vivek Arya", "firm": "BofA Securities", "strength": "AI 반도체", "rating": "Buy", "target": 500.0, "confidence": "상"},
            {"name": "Harsh Kumar", "firm": "Piper Sandler", "strength": "반도체", "rating": "Buy/Overweight 계열", "target": 300.0, "confidence": "중"},
        ],
        "action_impact": "AMD 2배 후보는 감시 우선. T1 회복 후 거래량이 붙으면 단일 2배 후보 승격 가능.",
        "source": "TipRanks/StockAnalysis 공개 페이지",
    },
    "MU": {
        "score": 2,
        "summary": "Vivek Arya/BofA가 TipRanks 공개 기사 기준 Buy 유지 및 목표가 $950 상향. HBM/메모리 구조적 강세 논리.",
        "analysts": [
            {"name": "Vivek Arya", "firm": "BofA Securities", "strength": "AI 반도체/메모리", "rating": "Buy", "target": 950.0, "confidence": "상"},
            {"name": "Vijay Rakesh", "firm": "Mizuho", "strength": "반도체", "rating": "Buy", "target": None, "confidence": "중"},
        ],
        "action_impact": "MU는 원주식 반등 확인 우선. $900 회복 후 거래량이 붙으면 단일 2배 감시 강도 상향.",
        "source": "TipRanks/StockAnalysis 공개 페이지",
    },
    "MRVL": {
        "score": 1,
        "summary": "Vivek Arya/BofA의 AI chip top-pick basket에 MRVL 포함. 다만 종목별 최신 목표가/적중률은 추가 확인 필요.",
        "analysts": [
            {"name": "Vivek Arya", "firm": "BofA Securities", "strength": "AI 반도체", "rating": "Top-pick basket", "target": None, "confidence": "중"},
            {"name": "Quinn Bolton", "firm": "Needham", "strength": "반도체/장비", "rating": "Watch", "target": None, "confidence": "중"},
        ],
        "action_impact": "MRVL은 현재 단일 2배 감시 1순위. 단, 정규장 초반 상대강도 확인 전 진입 금지.",
        "source": "TipRanks/StockAnalysis 공개 페이지",
    },
    "RDW": {
        "score": -2,
        "summary": "사용자 입력 단기 상단 $25는 유지하되, TipRanks 공개 forecast 평균 목표가는 $17.60으로 낮아 상단 시나리오 신뢰도 감점.",
        "analysts": [
            {"name": "TipRanks consensus", "firm": "TipRanks", "strength": "공개 컨센서스", "rating": "Mixed/Watch", "target": 17.6, "confidence": "중"},
        ],
        "action_impact": "RDW $25는 강한 뉴스/프리마켓/우주 테마 동반 회복 때만. 평상시에는 T1/T2 현금화 우선.",
        "source": "TipRanks 공개 forecast",
    },
    "NASA": {
        "score": 0,
        "summary": "ETF 구조상 단일 애널리스트 목표가보다 구성종목/우주 테마/SpaceX 이벤트 프리미엄을 우선 반영.",
        "analysts": [],
        "action_impact": "개별 애널 목표보다 테마 프리미엄과 유동성으로 판단.",
        "source": "내부 전술 규칙",
    },
    "AAOI": {
        "score": 0,
        "summary": "상위 TipRanks 애널리스트 공개 신호가 아직 충분하지 않아 차트/섹터/유동성 중심.",
        "analysts": [],
        "action_impact": "소형 고베타라 애널 신호보다 가격 행동 우선.",
        "source": "데이터 부족",
    },
    "GNTA": {
        "score": 0,
        "summary": "상위 TipRanks 애널리스트 공개 신호가 부족하고 계좌 영향도 작아 정리/복잡도 축소 후보.",
        "analysts": [],
        "action_impact": "기회비용 관점에서 정리 가능.",
        "source": "데이터 부족",
    },
}


def pct(target: float, current: float) -> float:
    return round((target / current - 1) * 100, 1)


def gain(value_krw: float, target: float, current: float) -> int:
    return round(value_krw * (target / current - 1))


def clamp(value: float, low: float, high: float) -> float:
    return max(low, min(high, value))


def probability_label(value: float) -> str:
    if value >= 60:
        return "높음"
    if value >= 40:
        return "중립+"
    if value >= 25:
        return "조건부"
    return "낮음"


def build_breakout_probability(current: float, hold_until: float, flow_score: float, action: str, analyst_score: float = 0) -> int:
    distance = max((hold_until / current - 1) * 100, 0)
    base = 52 - distance * 1.15 + flow_score * 4.0 + analyst_score * 3.0
    if "HOLD" in action:
        base += 4
    if "TRIM" in action or "CLEANUP" in action:
        base -= 5
    return int(round(clamp(base, 8, 74)))


def read_weekend_flow() -> dict:
    if not WEEKEND_FLOW_PATH.exists():
        return FLOW_DEFAULT
    try:
        data = json.loads(WEEKEND_FLOW_PATH.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return FLOW_DEFAULT
    return {**FLOW_DEFAULT, **data}


def flow_adjustment(flow: dict, flow_key: str) -> float:
    score = float(flow.get("risk_on_score") or 0)
    if flow_key == "SEMIS":
        return score * 0.8
    if flow_key == "SPACE":
        return score * 0.6
    return score * 0.5


def build_orders(csv_path: Path, weekend_flow: dict) -> list[dict]:
    grouped: dict[str, dict] = {}
    with csv_path.open(encoding="utf-8") as handle:
        for row in csv.DictReader(handle):
            ticker = row["ticker"].strip().upper()
            item = grouped.setdefault(
                ticker,
                {
                    "ticker": ticker,
                    "qty": 0.0,
                    "value_krw": 0.0,
                    "pnl_krw": 0.0,
                    "current_price": float(row["current_price"]),
                    "accounts": set(),
                },
            )
            item["qty"] += float(row["qty"])
            item["value_krw"] += float(row["value_krw"])
            item["pnl_krw"] += float(row["pnl_krw"])
            item["accounts"].add(row["account"].strip())

    orders = []
    for ticker, item in grouped.items():
        target = TARGETS.get(ticker)
        if not target:
            continue
        current = item["current_price"]
        value = item["value_krw"]
        hold_until = target.get("hold_until", target["t2"])
        flow_score = flow_adjustment(weekend_flow, target.get("flow_key", "RISK_ON"))
        analyst_signal = ANALYST_SIGNALS.get(ticker, {"score": 0, "summary": "애널리스트 신호 없음", "analysts": [], "action_impact": "가격 행동 우선", "source": "데이터 없음"})
        breakout_probability = build_breakout_probability(current, hold_until, flow_score, target["action"], analyst_signal.get("score", 0))
        prior_high = target.get("prior_high")
        order = {
            **target,
            "ticker": ticker,
            "account": "본/부" if item["accounts"] == {"main", "sub"} else ("본" if item["accounts"] == {"main"} else "부"),
            "qty": round(item["qty"], 4),
            "current_price": current,
            "value_krw": round(value),
            "pnl_krw": round(item["pnl_krw"]),
            "t1_gain_pct": pct(target["t1"], current),
            "t1_est_profit_krw": gain(value, target["t1"], current),
            "t2_gain_pct": pct(target["t2"], current),
            "t2_est_profit_krw": gain(value, target["t2"], current),
            "hold_until_gain_pct": pct(hold_until, current),
            "hold_until_est_profit_krw": gain(value, hold_until, current),
            "prior_high_gap_pct": pct(prior_high, current) if prior_high else None,
            "breakout_probability_pct": breakout_probability,
            "breakout_probability_label": probability_label(breakout_probability),
            "flow_score": round(flow_score, 1),
            "flow_note": weekend_flow.get("summary", FLOW_DEFAULT["summary"]),
            "analyst_score": analyst_signal.get("score", 0),
            "analyst_summary": analyst_signal.get("summary", ""),
            "analyst_action_impact": analyst_signal.get("action_impact", ""),
            "analyst_source": analyst_signal.get("source", ""),
            "analysts": analyst_signal.get("analysts", []),
            "target_basis": "사용자 입력 애널 목표" if target.get("analyst_target") else "전술 목표/최근 고점",
            "invalid_loss_pct": pct(target["invalid"], current),
            "invalid_est_loss_krw": gain(value, target["invalid"], current),
        }
        orders.append(order)
    return sorted(orders, key=lambda x: x["priority"])


def leverage_candidate_status(order: dict) -> tuple[str, str]:
    probability = order["breakout_probability_pct"]
    invalid_gap = abs(order["invalid_loss_pct"])
    if probability >= 60 and invalid_gap <= 5.0:
        return "조건부 후보", "본계좌 소액 전술 후보. 장초 30-60분 추세 유지와 브로커 호가 확인 후만 검토."
    if probability >= 48:
        return "감시", "추세는 살아있지만 확정 부족. 정규장 초반 거래량/상대강도 확인 전 진입 금지."
    return "대기", "상단까지 거리 또는 변동성이 커서 지금은 원주식/현금 관리가 우선."


def build_single_stock_leverage_candidates(orders: list[dict]) -> list[dict]:
    candidates = []
    for order in orders:
        products = SINGLE_STOCK_LEVERAGE_PRODUCTS.get(order["ticker"])
        if not products:
            continue
        status, instruction = leverage_candidate_status(order)
        candidates.append(
            {
                "underlying": order["ticker"],
                "products": products,
                "status": status,
                "account": "본계좌 전용",
                "first_trade_cash_required_krw": 10_000_000,
                "cash_prep_notice": "단일종목 2배도 최초 1회 거래 전 1,000만원 예수금 준비 필요. 후보 승격 가능성이 생기면 2-3일 전 준비 경보.",
                "breakout_probability_pct": order["breakout_probability_pct"],
                "flow_score": order["flow_score"],
                "analyst_score": order.get("analyst_score", 0),
                "analyst_summary": order.get("analyst_summary", ""),
                "trigger": (
                    f"{order['ticker']}가 T1 ${order['t1']} 돌파 후 VWAP/전일고가 위 유지, "
                    "섹터 지수 동반 회복, 호가 스프레드 과대 아님"
                ),
                "stop": f"기초종목 ${order['invalid']} 이탈 또는 2배 ETF -5%~-7% 손실",
                "time_limit": "당일 또는 1-2거래일. 매일 재검증 없이는 보유 금지.",
                "instruction": instruction,
                "risk": "일일 2배 목표라 횡보/급반전/변동성 장에서는 원주식 상승에도 손실 가능.",
            }
        )
    return sorted(candidates, key=lambda x: (x["status"] != "조건부 후보", -x["breakout_probability_pct"]))


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--positions-csv", type=Path, default=DEFAULT_CSV)
    parser.add_argument("--main-total", type=int, default=56_128_656)
    parser.add_argument("--sub-total", type=int, default=46_233_508)
    parser.add_argument("--mode", default="RECOVERY")
    parser.add_argument("--market-status", default="미장 휴장/주말")
    parser.add_argument("--verdict", default="패닉셀 금지. 월요일 첫 30-60분 저점 유지 확인 후 약한 축부터 현금화.")
    parser.add_argument("--next-check", default="월요일 프리마켓 16:50 KST / 장초 30분")
    args = parser.parse_args()

    total = args.main_total + args.sub_total
    start_total = START_MAIN + START_SUB
    total_pct = (total / start_total - 1) * 100
    weekend_flow = read_weekend_flow()
    orders = build_orders(args.positions_csv, weekend_flow)
    single_stock_leverage = build_single_stock_leverage_candidates(orders)

    state = {
        "state_version": dt.datetime.now().strftime("%Y%m%d-%H%M%S"),
        "updated_at": dt.datetime.now(dt.timezone.utc).astimezone().isoformat(timespec="seconds"),
        "market_status": args.market_status,
        "mode": args.mode,
        "verdict": args.verdict,
        "next_check": args.next_check,
        "kakao_summary": (
            f"[상황] {args.mode}\n"
            f"계좌: 합산 {total/100000000:.3f}억, 시작대비 {total_pct:+.2f}%, SOXL 현금 미준비\n"
            "시장: 우주+반도체 고베타 집중, 주말 뉴스 확인 필요\n"
            "행동: 첫 30-60분 저점 유지 확인 후 약한 축부터 축소\n"
            "금지: 패닉셀, 뉴스 전 물타기, SOXL/SOXS 추격\n"
            f"다음: {args.next_check}"
        ),
        "accounts": {
            "main": {"name": "본계좌", "start_krw": START_MAIN, "current_krw": args.main_total, "daily_target_krw": 2_000_000, "can_leverage": True},
            "sub": {"name": "부계좌", "start_krw": START_SUB, "current_krw": args.sub_total, "daily_target_krw": 2_000_000, "can_leverage": False},
        },
        "cash_plan": {
            "target_cash_krw": 12_000_000,
            "minimum_soxl_cash_krw": 10_000_000,
            "minimum_single_stock_2x_cash_krw": 10_000_000,
            "status": "미준비",
            "instruction": "SOXL/SOXS 및 단일종목 2배 최초 거래를 위해 강제 손절보다 반등 현금화로 1,000만-1,500만원 준비.",
        },
        "cost_policy": {
            "monthly_budget_krw": 0,
            "allowed_sources": ["Kakao 나에게 보내기", "Hyperliquid 무료 공개 API", "Binance 무료 공개 API", "Yahoo/무료 시세 대체 경로", "로컬 서버", "무료 터널"],
            "blocked_sources": ["유료 데이터 단말기", "유료 문자/알림 서비스", "유료 호스팅", "유료 API 구독"],
            "instruction": "비용이 발생하는 기능은 기본 비활성. 무료 공개 소스 실패 시 유료 전환하지 않고 '데이터 없음'으로 표시.",
        },
        "single_stock_leverage": {
            "policy": "강한 단일종목 추세가 확인될 때만 본계좌 전용 전술 후보로 검토. 최초 1회 거래 전 1,000만원 예수금 준비 필요.",
            "first_trade_cash_required_krw": 10_000_000,
            "cash_prep_days": "2-3일 전",
            "max_budget_krw": 5_000_000,
            "default_budget_krw": 2_000_000,
            "entry_gate": [
                "최초 거래 가능 상태 확인 및 1,000만원 예수금 준비",
                "기초종목이 T1 돌파 후 VWAP/전일고가 위에서 30-60분 유지",
                "섹터 ETF와 시장 지수가 같은 방향으로 동행",
                "TipRanks/StockAnalysis 공개 상위 애널리스트 신호가 중립 이상",
                "2배 ETF 거래량과 스프레드가 정상",
                "손절가와 시간제한을 주문 전 확정",
            ],
            "ban": [
                "손실 복구 목적 진입",
                "장초 갭상승 추격",
                "실적/공시 직전 무계획 오버나이트",
                "부계좌 매수",
                "1-2거래일 초과 보유를 장기투자로 전환",
            ],
            "candidates": single_stock_leverage,
        },
        "weekend_flow": weekend_flow,
        "market_context": [
            "반도체: Broadcom 이후 AI 반도체 기대치가 낮아지며 SOX/칩 대형주가 급락. 첫 반등은 추격보다 반등 품질 확인.",
            "MU/AMD/MRVL: 장기 AI/메모리 논리는 남아도 단기 포지션 청산이 커서 T1/T2를 나눠 수익 잠금.",
            "NASA/RDW: SpaceX/우주 테마 기대는 있으나 이벤트 프리미엄과 고베타가 커서 반등 시 현금화 우선.",
            "주말 플로우: Hyperliquid/Binance에 보유 주식 직접 선물은 없으면 BTC/ETH/SOL/BNB/LINK/AI 토큰을 위험선호 프록시로만 사용.",
            "애널리스트: TipRanks/StockAnalysis 공개 상위 애널리스트 신호를 보조 레이어로 반영. RDW는 공개 컨센서스가 낮아 $25 상단 신뢰도 감점.",
            "현금: SOXL/SOXS 및 단일종목 2배 최초 진입을 위해 강제 손절보다 반등 현금화로 1,000만-1,500만원 확보.",
            "비용: 모든 데이터/알림은 무료 경로만 사용. 유료 API가 필요한 순간에는 자동 결제 없이 경고로 표시."
        ],
        "orders": orders,
        "rules": {
            "now": [
                "장 열리기 전 신규매수 금지",
                "SOXL/SOXS 즉시 진입 금지",
                "단일종목 2배는 1,000만원 예수금 준비 전 제안만, 진입 금지",
                "월요일 첫 30-60분 저점 유지 확인",
                "반등 시 약한 축부터 현금 1,000만-1,500만원 확보",
            ],
            "ban": [
                "패닉셀",
                "뉴스 확인 전 물타기",
                "손실 복구 목적 레버리지 추격",
                "단일종목 2배 장초 추격",
                "부계좌 SOXL/SOXS",
            ],
        },
    }
    state_json = json.dumps(state, ensure_ascii=False, indent=2)
    STATE_PATH.write_text(state_json + "\n", encoding="utf-8")
    STATE_JS_PATH.write_text("window.LIVE_ACCOUNT_STATE = " + state_json + ";\n", encoding="utf-8")
    print(STATE_PATH)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
