"""
키움 REST API → market_data dict.

guards.validate_request 가 요구하는 모든 필드를 채워 반환한다.
기존 kiwoom_client 의 조회 함수를 재사용.

호가창(ka10004)·NXT 가능여부 등 일부 필드는 키움 응답 필드명이 환경마다 다를 수 있어
best-effort 파싱이며, 실패 시 None/0 으로 떨어진다. 첫 실거래 검증 시 실데이터로 보강 필요.
"""

from __future__ import annotations

import sys
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Optional

_PARENT = Path(__file__).resolve().parent.parent
if str(_PARENT) not in sys.path:
    sys.path.insert(0, str(_PARENT))

import kiwoom_client as kc
from . import guards

KST = timezone(timedelta(hours=9))


def _to_int(s) -> int:
    if s is None or s == '':
        return 0
    try:
        return int(str(s).replace(',', '').replace('+', '').strip() or 0)
    except (ValueError, AttributeError):
        return 0


def _safe_quote(symbol: str, account_label: str) -> dict:
    """ka10001 (주식기본정보) 응답에서 가드용 필드 추출.

    공식 명세 필드명:
    - cur_prc: 현재가, base_pric: 기준가(전일종가)
    - upl_pric: 상한가, lst_pric: 하한가
    - 거래정지(halt) 필드는 ka10001 명세에 없음 — 별도 TR 보강 필요. 보수적으로 False.
    """
    try:
        q_wrap = kc.get_stock_quote(symbol, account_label=account_label, exchange='AL')
    except Exception:
        return {}
    if not isinstance(q_wrap, dict):
        return {}
    raw = q_wrap.get('raw') if isinstance(q_wrap.get('raw'), dict) else q_wrap
    out = {
        'cur_price': abs(_to_int(raw.get('cur_prc'))),
        'prev_close': abs(_to_int(raw.get('base_pric'))),
        'upper_limit': abs(_to_int(raw.get('upl_pric'))),
        'lower_limit': abs(_to_int(raw.get('lst_pric'))),
        'halt': False,  # ka10001 명세에 거래정지 플래그 없음 — 별도 TR 보강 예정
    }
    out['_raw'] = raw
    return out


def _safe_orderbook(symbol: str, account_label: str) -> Optional[dict]:
    """ka10004 (주식호가요청) — /api/dostk/mrkcond.

    필드 매핑 (키움 공식 명세):
    - 1호가: sel_fpr_bid / sel_fpr_req (매도), buy_fpr_bid / buy_fpr_req (매수)
    - 2~10호가: sel_{N}th_pre_bid / sel_{N}th_pre_req (매도), buy_{N}th_pre_bid / buy_{N}th_pre_req (매수)
    가격에 부호(+/-) 붙어올 수 있어 abs 처리.
    """
    try:
        resp = kc._call(account_label, 'ka10004', {'stk_cd': symbol},
                         endpoint=kc.ENDPOINT_MRKCOND)
    except Exception:
        return None
    if not isinstance(resp, dict):
        return None
    asks, bids = [], []
    # 1호가
    ap1 = abs(_to_int(resp.get('sel_fpr_bid')))
    aq1 = abs(_to_int(resp.get('sel_fpr_req')))
    if ap1 and aq1:
        asks.append({'price': ap1, 'qty': aq1})
    bp1 = abs(_to_int(resp.get('buy_fpr_bid')))
    bq1 = abs(_to_int(resp.get('buy_fpr_req')))
    if bp1 and bq1:
        bids.append({'price': bp1, 'qty': bq1})
    # 2~10호가
    for i in range(2, 11):
        ap = abs(_to_int(resp.get(f'sel_{i}th_pre_bid')))
        aq = abs(_to_int(resp.get(f'sel_{i}th_pre_req')))
        if ap and aq:
            asks.append({'price': ap, 'qty': aq})
        bp = abs(_to_int(resp.get(f'buy_{i}th_pre_bid')))
        bq = abs(_to_int(resp.get(f'buy_{i}th_pre_req')))
        if bp and bq:
            bids.append({'price': bp, 'qty': bq})
    if not asks and not bids:
        return None
    return {'asks': asks, 'bids': bids}


def _nxt_eligible(symbol: str, quote: dict) -> bool:
    """NXT 거래가능 여부.

    1차 소스: ka10099 종목정보 캐시 (`state/stock_codes.json`)의 `nxt_enable` 플래그.
    캐시 미스 또는 구 스키마(필드 없음) → 보수적 True (= 가드 통과 → 키움이 사후 거부).
    캐시 갱신 명령: `python3 kiwoom_client.py refresh-codes`.
    """
    try:
        meta = kc.lookup_stock_meta(symbol)
    except Exception:
        return True
    if not meta:
        return True
    if 'nxt_enable' not in meta:  # 구 스키마 캐시 — 갱신 전엔 보수적 True
        return True
    return bool(meta['nxt_enable'])


def _safe_broker_executions(account_label: str, now: datetime) -> tuple[Optional[list], Optional[str]]:
    """kt00007 기준 오늘자 매매 행 조회. 실패 시 (None, error_repr).

    가드(validate_delay_same_symbol_via_broker)에서 사용. 정확도 우선이라 실패 시 보수적 차단.
    """
    try:
        executions = kc.get_order_executions(account_label, base_dt=now.strftime('%Y%m%d'))
    except Exception as e:
        return None, repr(e)
    return executions, None


def collect_market_data(account_label: str, symbol: str, side: str, qty: int) -> dict:
    now = datetime.now(KST)
    quote = _safe_quote(symbol, account_label)
    orderbook = _safe_orderbook(symbol, account_label)
    broker_executions, broker_query_error = _safe_broker_executions(account_label, now)
    try:
        stock_meta = kc.lookup_stock_meta(symbol)
    except Exception:
        stock_meta = None

    md = {
        'now': now,
        'is_holiday': guards.is_today_holiday(now),
        'nxt_eligible': _nxt_eligible(symbol, quote),
        'current_price': quote.get('cur_price', 0),
        'prev_close': quote.get('prev_close', 0),
        'upper_limit': quote.get('upper_limit', 0),
        'lower_limit': quote.get('lower_limit', 0),
        'halt': quote.get('halt', False),
        'vi': False,  # VI 실시간 감지는 별도 채널 필요. 보강 예정.
        'orderbook': orderbook,
        'broker_executions': broker_executions,
        'broker_query_error': broker_query_error,
        'stock_meta': stock_meta,
    }

    try:
        open_orders = kc.get_open_orders(account_label)
    except Exception:
        open_orders = []

    if side == 'BUY':
        try:
            bal = kc.get_balance(account_label)
            d2 = _to_int(bal.get('d2_entra'))
            # d2_entra는 미체결 매수 지정가로 묶인 예수금을 아직 포함 → 실제 가용액에서 차감.
            locked = sum(_to_int(o.get('order_price')) * _to_int(o.get('unfilled_qty'))
                         for o in open_orders
                         if o.get('side') == 'BUY' and _to_int(o.get('order_price')) > 0)
            md['balance_d2'] = max(0, d2 - locked)
        except Exception:
            md['balance_d2'] = 0
    else:
        try:
            positions = kc.get_positions(account_label)
            pos = next((p for p in positions
                        if (p.get('code') == symbol) or (p.get('symbol') == symbol) or (p.get('stk_cd') == symbol)),
                       None)
            if pos:
                hold = _to_int(pos.get('qty') or pos.get('hold_qty') or pos.get('rmnd_qty'))
                trde_able = _to_int(pos.get('trde_able_qty') or hold)
                pend_sell = sum(_to_int(o.get('unfilled_qty')) for o in open_orders
                                if o.get('side') == 'SELL' and o.get('code') == symbol)
                # trde_able이 미체결 매도 제외 여부 불확실 → min으로 이중차감 방지 (check 핸들러와 동일).
                md['position_qty'] = max(0, min(trde_able, hold - pend_sell))
            else:
                md['position_qty'] = 0
        except Exception:
            md['position_qty'] = 0

    return md
