"""GatherTradingData.xlsx에서 실제 매크로/이벤트/포지션 컨텍스트를 추출.

build_qualitative_sell_inputs_v1.py의 --context-json을 수동 작성하지 않고, 이미
GAS 하네스가 산출/수집해 둔 시트 값을 그대로 읽어 자동 조립한다(중복 수집 금지
원칙 — qualitative_sell_strategy_v1.yaml:data_sources 참조).

실측 확인된 시트/컬럼(2026-06-21):
  - macro 시트: Symbol='MRS_COMPUTED'.Close = market_risk_score(0~10, 하네스 산출).
    Symbol='^TNX'(US10Y_Yield).Ret20D = 20일 금리추세 proxy(국내 기준금리 시트 없음 —
    한국은행 금통위 일정은 event_calendar Type='BOK'로 별도 포착).
  - event_risk 시트: Date/DaysLeft/Event/Type/Impact(HIGH/MEDIUM/LOW)/Alert/AsOfDate.
  - event_calendar 시트: Date/Event/Type(EARNINGS/FOMC/BOK/...)/Impact/DaysLeft 등.
    Type='EARNINGS'에 종목명이 Event 텍스트에 포함된 행만 종목별 실적발표일로 매칭.
  - account_snapshot 시트: ticker/name/holding_quantity/parse_status='CAPTURE_READ_OK'.
"""
from __future__ import annotations

import argparse
import datetime as dt
import json
import sys
from pathlib import Path
from typing import Any

from openpyxl import load_workbook

ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

RATE_RISING_THRESHOLD_PCT = 2.0
RATE_FALLING_THRESHOLD_PCT = -2.0


def _read_sheet_rows(xlsx_path: Path, sheet: str) -> tuple[tuple, list[dict[str, Any]]]:
    """헤더 행을 탐색한다. 일부 시트(macro/event_risk)는 1행에 'updated: ...' 배너
    셀 1개만 있고 실제 헤더는 2행 — 비어있거나 단일 셀뿐인 선행 행은 건너뛴다."""
    wb = load_workbook(xlsx_path, read_only=True, data_only=True)
    ws = wb[sheet]
    rows_iter = ws.iter_rows(min_row=1, values_only=True)
    header: tuple = ()
    for row in rows_iter:
        non_empty = [c for c in row if c is not None]
        if len(non_empty) >= 2:
            header = row
            break
    rows = [dict(zip(header, row)) for row in rows_iter if any(c is not None for c in row)]
    return header, rows


def read_macro_pressure_and_regime(xlsx_path: Path) -> dict[str, Any]:
    """MRS_COMPUTED.Close(0~10) -> macro_pressure(-1~+1, 위험도 높을수록 매도압력).

    ^TNX Ret20D(%) -> rate_trend(RISING/FLAT/FALLING) — 국내 기준금리 시트가 없어
    미국채 10년물 20일 변화율을 proxy로 사용한다(국내 금리는 미 국채와 강한 동행성).
    """
    _, rows = _read_sheet_rows(xlsx_path, "macro")
    by_symbol = {row.get("Symbol"): row for row in rows}

    mrs_row = by_symbol.get("MRS_COMPUTED")
    macro_pressure = None
    market_risk_score = None
    if mrs_row is not None and isinstance(mrs_row.get("Close"), (int, float)):
        market_risk_score = float(mrs_row["Close"])
        macro_pressure = max(-1.0, min(1.0, (market_risk_score / 10.0) * 2.0 - 1.0))

    tnx_row = by_symbol.get("^TNX")
    rate_trend = None
    rate_ret20d_pct = None
    if tnx_row is not None and tnx_row.get("Ret20D") not in (None, ""):
        try:
            rate_ret20d_pct = float(tnx_row["Ret20D"])
        except (TypeError, ValueError):
            rate_ret20d_pct = None
        if rate_ret20d_pct is not None:
            if rate_ret20d_pct >= RATE_RISING_THRESHOLD_PCT:
                rate_trend = "RISING"
            elif rate_ret20d_pct <= RATE_FALLING_THRESHOLD_PCT:
                rate_trend = "FALLING"
            else:
                rate_trend = "FLAT"

    regime_row = by_symbol.get("REGIME_PRELIM")
    regime_prelim = regime_row.get("Close") if regime_row else None

    return {
        "macro_pressure": macro_pressure,
        "market_risk_score": market_risk_score,
        "rate_trend": rate_trend,
        "rate_ret20d_pct": rate_ret20d_pct,
        "regime_prelim": regime_prelim,
        "macro_pressure_source": "GatherTradingData.xlsx:macro",
    }


def read_next_macro_event(xlsx_path: Path, today: dt.date | None = None) -> dict[str, Any]:
    """event_risk 시트에서 오늘 이후 가장 가까운 HIGH 임팩트 이벤트일."""
    today = today or dt.date.today()
    _, rows = _read_sheet_rows(xlsx_path, "event_risk")
    candidates = []
    for row in rows:
        event_date = row.get("Date")
        if not isinstance(event_date, dt.datetime):
            continue
        event_date = event_date.date()
        if event_date < today or row.get("Impact") not in {"HIGH"}:
            continue
        candidates.append((event_date, row.get("Event"), row.get("Impact")))
    if not candidates:
        return {"next_macro_event_date": None, "macro_event_impact": None}
    candidates.sort(key=lambda item: item[0])
    event_date, event_name, impact = candidates[0]
    return {
        "next_macro_event_date": event_date.isoformat(),
        "macro_event_impact": impact,
        "macro_event_name": event_name,
        "macro_event_source": "GatherTradingData.xlsx:event_risk",
    }


def read_next_earnings_date(xlsx_path: Path, company_name: str, today: dt.date | None = None) -> dict[str, Any]:
    """event_calendar에서 Type='EARNINGS'이며 Event 텍스트에 종목명이 포함된 가장 빠른 미래 일정."""
    today = today or dt.date.today()
    _, rows = _read_sheet_rows(xlsx_path, "event_calendar")
    candidates = []
    name = (company_name or "").strip()
    if not name:
        return {"next_earnings_date": None, "earnings_event_impact": None}
    for row in rows:
        if row.get("Type") != "EARNINGS":
            continue
        event_text = str(row.get("Event") or "")
        if name not in event_text:
            continue
        event_date = row.get("Date")
        if isinstance(event_date, dt.datetime):
            event_date = event_date.date()
        elif isinstance(event_date, str):
            try:
                event_date = dt.date.fromisoformat(event_date)
            except ValueError:
                continue
        else:
            continue
        if event_date < today:
            continue
        candidates.append((event_date, row.get("Impact")))
    if not candidates:
        return {"next_earnings_date": None, "earnings_event_impact": None}
    candidates.sort(key=lambda item: item[0])
    event_date, impact = candidates[0]
    return {
        "next_earnings_date": event_date.isoformat(),
        "earnings_event_impact": impact,
        "earnings_source": "GatherTradingData.xlsx:event_calendar",
    }


def read_positions(xlsx_path: Path) -> list[dict[str, Any]]:
    """account_snapshot에서 실제 보유 종목 목록(CAPTURE_READ_OK, 보유수량>0)."""
    _, rows = _read_sheet_rows(xlsx_path, "account_snapshot")
    positions: dict[str, dict[str, Any]] = {}
    for row in rows:
        if row.get("parse_status") != "CAPTURE_READ_OK":
            continue
        ticker_raw = row.get("ticker")
        qty = row.get("holding_quantity") or 0
        if ticker_raw is None or not isinstance(qty, (int, float)) or qty <= 0:
            continue
        ticker = str(ticker_raw)
        ticker = ticker.zfill(6) if ticker.isdigit() else ticker
        entry = positions.setdefault(ticker, {"ticker": ticker, "name": row.get("name"), "holding_quantity": 0.0})
        entry["holding_quantity"] += float(qty)  # 소수주 분리 행 합산
    return list(positions.values())


def build_context_for_ticker(xlsx_path: Path, ticker: str, company_name: str) -> dict[str, Any]:
    today = dt.date.today()
    ctx: dict[str, Any] = {}
    ctx.update(read_macro_pressure_and_regime(xlsx_path))
    ctx.update(read_next_macro_event(xlsx_path, today))
    ctx.update(read_next_earnings_date(xlsx_path, company_name, today))
    return ctx


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--xlsx", type=Path, default=ROOT / "GatherTradingData.xlsx")
    ap.add_argument("--ticker", default=None)
    ap.add_argument("--name", default=None, help="실적발표 일정 매칭용 종목명(한글)")
    ap.add_argument("--list-positions", action="store_true")
    args = ap.parse_args()

    if args.list_positions:
        print(json.dumps(read_positions(args.xlsx), ensure_ascii=False, indent=2))
        return 0

    result = build_context_for_ticker(args.xlsx, args.ticker or "", args.name or "")
    print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
    return 0


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