"""
kis_trader/execution/account_cash.py — 가용 예수금 메모리 캐시 + kv_store 영속
================================================================================
- 매수체크 루프마다 REST 호출하지 않음.
- main._fetch_asset_snapshot() / 장 시작·heartbeat 에서 API 동기화.
- 체결 후 메모리에서 증감, kv_store 에 주기적·체결 시 저장 (재시작 복구).

kv_store 키 (잡다한 런타임 상태 — 별도 테이블 불필요):
  account.available_cash   : 주문가능 예수금(원, 정수 문자열)
  account.cash_updated_at  : ISO 시각
  account.cash_source      : api | trade_delta | startup
"""
from __future__ import annotations

import threading
import time
from datetime import datetime as dt
from typing import Any, Dict, Optional

from ..utils.env import get_env_float, get_env_int
from ..utils.logger import LOG_YELLOW, LOG_RESET, get_logger

logger = get_logger("kis_trader.account_cash")

KV_AVAILABLE_CASH = "account.available_cash"
KV_CASH_UPDATED_AT = "account.cash_updated_at"
KV_CASH_SOURCE = "account.cash_source"


class AccountCashLedger:
    """프로세스 내 가용 예수금 — OrderManager 매수 직전 부족 시에만 qty 조정에 사용."""

    def __init__(self, db) -> None:
        # TradeDBExt — get_kv/set_kv 는 raw TradeDB
        self._db = db
        self._raw = getattr(db, "raw", db)
        self._lock = threading.Lock()
        self._cash: float = 0.0
        self._has_baseline: bool = False
        self._last_persist_ts: float = 0.0
        self._load_from_kv()

    def _load_from_kv(self) -> None:
        try:
            v = self._raw.get_kv(KV_AVAILABLE_CASH)
            if v is None or str(v).strip() == "":
                return
            cash = float(str(v).replace(",", "").strip())
            if cash > 0:
                with self._lock:
                    self._cash = cash
                    self._has_baseline = True
                logger.info(
                    "📂 [예수금캐시] kv_store 복원: %s원 (updated=%s)",
                    f"{cash:,.0f}",
                    self._raw.get_kv(KV_CASH_UPDATED_AT) or "?",
                )
        except Exception as e:
            logger.debug("예수금 kv 복원 실패: %s", e)

    def available_cash(self) -> float:
        with self._lock:
            return float(self._cash)

    def has_baseline(self) -> bool:
        with self._lock:
            return self._has_baseline and self._cash > 0

    def sync_from_snapshot(self, snap: Dict[str, Any], source: str = "api") -> None:
        """KIS inquire-balance 파싱 결과(cash=D+2 or dnca) → 메모리·kv."""
        try:
            cash = float(snap.get("cash", 0) or 0)
        except (TypeError, ValueError):
            return
        if cash <= 0:
            return
        with self._lock:
            self._cash = cash
            self._has_baseline = True
        self._persist(source=source, force=False)

    def invalidate_baseline(self, reason: str = "startup_sync_failed") -> None:
        """
        stale kv baseline 사용 중단.
        - 재시작 직후 API 동기화 실패 시 이전 계좌/구좌의 캐시 오염을 막기 위해 사용.
        - _cash 값은 남겨두되 baseline 플래그를 내려 clamp 로직이 개입하지 않게 한다.
        """
        with self._lock:
            self._has_baseline = False
        try:
            self._raw.set_kv(KV_CASH_SOURCE, reason)
        except Exception:
            pass

    def apply_trade_delta(self, delta_krw: float, source: str = "trade_delta") -> None:
        """체결 후 낙관적 증감 (매수 음수, 매도 양수). baseline 없으면 스킵."""
        if abs(delta_krw) < 1:
            return
        with self._lock:
            if not self._has_baseline:
                return
            self._cash = max(0.0, self._cash + delta_krw)
        self._persist(source=source, force=True)

    def _persist(self, source: str, force: bool) -> None:
        interval = max(5, get_env_int("ACCOUNT_CASH_PERSIST_SEC", 60))
        now = time.time()
        if not force and (now - self._last_persist_ts) < interval:
            return
        with self._lock:
            cash = self._cash
        try:
            self._raw.set_kv(KV_AVAILABLE_CASH, str(int(round(cash))))
            self._raw.set_kv(KV_CASH_UPDATED_AT, dt.now().strftime("%Y-%m-%d %H:%M:%S"))
            self._raw.set_kv(KV_CASH_SOURCE, source)
            self._last_persist_ts = now
        except Exception as e:
            logger.debug("예수금 kv 저장 실패: %s", e)

    def clamp_buy_qty_if_insufficient(
        self,
        qty: int,
        price_ref: float,
        code: str,
        name: str,
        strategy_id: str,
    ) -> tuple[int, Optional[str]]:
        """
        기존 전략이 계산한 qty 를 우선.
        주문금액(수수료 버퍼 포함) > 가용예수금 일 때만 예수금 % 로 상한 재계산.

        Returns:
            (adjusted_qty, reason) — qty=0 이면 매수 스킵, reason=insufficient_cash
        """
        if qty <= 0 or price_ref <= 0:
            return qty, None
        if not self.has_baseline():
            return qty, None

        fee_buf = max(1.0, get_env_float("ORDER_CASH_FEE_BUFFER", 1.01))
        order_amt = qty * price_ref * fee_buf
        available = self.available_cash()

        if order_amt <= available:
            return qty, None

        pct = max(0.01, min(1.0, get_env_float("ORDER_CASH_PCT", 0.95)))
        cap_cash = available * pct
        divide = get_env_int("ORDER_CASH_DIVIDE_BY_MAX_STOCKS", 1)
        if divide > 0:
            max_stocks = max(1, get_env_int("MAX_STOCKS", 4))
            cap_cash = cap_cash / max_stocks

        unit_cost = price_ref * fee_buf
        max_qty = int(cap_cash / unit_cost) if unit_cost > 0 else 0
        if max_qty < 1:
            logger.warning(
                "%s🚫 [예수금부족] [%s] %s %s: 필요 %s원 > 가용 %s원 → 1주도 불가%s",
                LOG_YELLOW, strategy_id, name, code,
                f"{order_amt:,.0f}", f"{available:,.0f}", LOG_RESET,
            )
            return 0, "insufficient_cash"

        if max_qty < qty:
            logger.warning(
                "%s💰 [예수금조정] [%s] %s %s: qty %d→%d "
                "(주문 %s원 > 가용 %s원, cap=%s원×%.0f%%)%s",
                LOG_YELLOW, strategy_id, name, code, qty, max_qty,
                f"{order_amt:,.0f}", f"{available:,.0f}",
                f"{cap_cash:,.0f}", pct * 100, LOG_RESET,
            )
            return max_qty, "cash_clamped"

        return qty, None
