"""
kis_trader/ws/program_cache.py — 키움 0w 종목프로그램매매 RAM 캐시
================================================================
키움 OpenAPI+ FID (종목프로그램매매 0w, KOA Studio 기준):
  202 매수수량, 204 매도수량, 206 순매수수량(부호), 207 순매수대금(부호)
  203 매수대금, 205 매도대금 (보조)
"""
from __future__ import annotations

import threading
import time
from dataclasses import dataclass, field
from typing import Any, Dict, Optional


def _abs_int(v: Any) -> int:
    try:
        return int(abs(float(str(v or "0").replace(",", ""))))
    except (TypeError, ValueError):
        return 0


def _signed_int(v: Any) -> int:
    try:
        s = str(v or "0").replace(",", "").strip()
        if not s or s in ("-", "+"):
            return 0
        return int(float(s))
    except (TypeError, ValueError):
        return 0


@dataclass
class ProgramSnapshot:
    """종목 1개 당일 프로그램매매 스냅샷."""
    code: str
    buy_qty: int = 0
    sell_qty: int = 0
    net_qty: int = 0
    buy_amt: int = 0
    sell_amt: int = 0
    net_amt: int = 0
    prev_net_qty: int = 0
    delta_net_qty: int = 0
    ts: float = 0.0
    source: str = "kiwoom_0w"
    snap_time: str = ""  # YYYYMMDDHHMMSS — DB·판정 스냅 재생용

    def is_net_buy(self) -> bool:
        return self.net_qty > 0

    def is_net_sell(self) -> bool:
        return self.net_qty < 0

    def sell_buy_qty_ratio(self) -> float:
        if self.buy_qty <= 0:
            return 999.0 if self.sell_qty > 0 else 0.0
        return self.sell_qty / float(self.buy_qty)

    def to_storage_dict(self) -> Dict[str, Any]:
        """``ws_program`` INSERT·백테 복원용."""
        return {
            "code": self.code,
            "buy_qty": self.buy_qty,
            "sell_qty": self.sell_qty,
            "net_qty": self.net_qty,
            "buy_amt": self.buy_amt,
            "sell_amt": self.sell_amt,
            "net_amt": self.net_amt,
            "source": self.source,
            "ts": self.ts,
            "snap_time": self.snap_time or "",
        }


def program_snapshot_from_storage(row: Dict[str, Any]) -> ProgramSnapshot:
    """DB 행 또는 storage dict → ``ProgramSnapshot``."""
    ts_raw = row.get("ts")
    try:
        ts = float(ts_raw) if ts_raw not in (None, "") else time.time()
    except (TypeError, ValueError):
        ts = time.time()
    st = str(row.get("snap_time") or "").strip()
    return ProgramSnapshot(
        code=str(row.get("code") or "").strip(),
        buy_qty=_abs_int(row.get("buy_qty")),
        sell_qty=_abs_int(row.get("sell_qty")),
        net_qty=_signed_int(row.get("net_qty")),
        buy_amt=_abs_int(row.get("buy_amt")),
        sell_amt=_abs_int(row.get("sell_amt")),
        net_amt=_signed_int(row.get("net_amt")),
        ts=ts,
        source=str(row.get("source") or "kiwoom_0w"),
        snap_time=st[:14] if st else "",
    )


def parse_kiwoom_0w_values(code: str, values: Dict[str, Any]) -> ProgramSnapshot:
    """키움 WS 0w ``values`` dict → ``ProgramSnapshot``."""
    buy_qty = _abs_int(values.get("202"))
    sell_qty = _abs_int(values.get("204"))
    net_qty = _signed_int(values.get("206"))
    if net_qty == 0 and (buy_qty > 0 or sell_qty > 0):
        net_qty = buy_qty - sell_qty

    buy_amt = _abs_int(values.get("203"))
    sell_amt = _abs_int(values.get("205"))
    net_amt = _signed_int(values.get("207"))
    if net_amt == 0 and (buy_amt > 0 or sell_amt > 0):
        net_amt = buy_amt - sell_amt

    return ProgramSnapshot(
        code=str(code).strip(),
        buy_qty=buy_qty,
        sell_qty=sell_qty,
        net_qty=net_qty,
        buy_amt=buy_amt,
        sell_amt=sell_amt,
        net_amt=net_amt,
        ts=time.time(),
        source="kiwoom_0w",
    )


class ProgramCache:
    """스레드 세이프 종목별 프로그램매매 캐시."""

    def __init__(self) -> None:
        self._data: Dict[str, ProgramSnapshot] = {}
        self._lock = threading.Lock()

    def update_from_kiwoom_0w(self, code: str, values: Dict[str, Any]) -> ProgramSnapshot:
        snap = parse_kiwoom_0w_values(code, values)
        with self._lock:
            prev = self._data.get(snap.code)
            if prev is not None:
                snap.prev_net_qty = prev.net_qty
                snap.delta_net_qty = snap.net_qty - prev.net_qty
            self._data[snap.code] = snap
        return snap

    def get(self, code: str, max_age_sec: float = 30.0) -> Optional[ProgramSnapshot]:
        c = str(code or "").strip()
        if not c:
            return None
        with self._lock:
            snap = self._data.get(c)
        if not snap:
            return None
        if max_age_sec > 0 and (time.time() - snap.ts) > max_age_sec:
            return None
        return snap

    def remove(self, code: str) -> None:
        c = str(code or "").strip()
        with self._lock:
            self._data.pop(c, None)
