"""
3단계: monitor_rules 발화 시 빗썸 실주문 (가드·로그).
"""

from __future__ import annotations

import json
import time
from datetime import date, datetime
from pathlib import Path
from typing import Any

from config import (
    CHART_LOOKBACK_DAYS,
    COIN_NAME,
    LIVE_COOLDOWN_MIN,
    LIVE_DAILY_KRW_MAX,
    LIVE_DAILY_LOSS_LIMIT_KRW,
    LIVE_MAX_TRADES_PER_DAY,
    LIVE_ORDER_KRW,
    LIVE_TRADING_ENABLED,
    MATCH_PRIMARY_INTERVAL,
    SYMBOL,
    TRADING_FEE_RATE,
)
from deepcoin.data.mtf_bb import load_frames_from_db
from deepcoin.ground_truth.ground_truth import load_ground_truth
from deepcoin.matching.live_eval import evaluate_live_rules
from deepcoin.matching.live_sizing import LivePositionState, live_sizing_enabled
from deepcoin.matching.load_rules import load_monitor_rules
from deepcoin.matching.position_sizing import (
    compute_buy_amount_krw,
    live_buy_asset_pct_scale,
    load_ev_wf_approved_rule_ids,
    top_leg_ids_by_forward_return,
)
from deepcoin.paths import resolve_ground_truth_file
from deepcoin.ops.alert_message import build_rule_alert_message
from deepcoin.ops.monitor import Monitor
from deepcoin.paths import LIVE_TRADES_LOG


class LiveTrader(Monitor):
    """
    규칙 발화 시 실거래 실행. LIVE_TRADING_ENABLED=0 이면 주문 없음(드라이런 로그만).
    """

    def __init__(self) -> None:
        """Monitor 초기화, 일별 카운터 비움."""
        super().__init__(cooldown_file=None)
        self._rule_last_unix: dict[str, float] = {}
        self._day: str = ""
        self._day_spent_krw: float = 0.0
        self._day_trades: int = 0
        self._day_pnl_krw: float = 0.0
        self._gt_trades: list[dict] = []
        self._large_legs: set[int] = set()
        self._approved_rules: set[str] = set()
        self._position_state = LivePositionState.load()
        self._ohlc_df = None
        self._load_sizing_context()

    def _load_sizing_context(self) -> None:
        """GT leg·EV/WF 통과 규칙 캐시."""
        gt = load_ground_truth(resolve_ground_truth_file()) or {}
        self._gt_trades = gt.get("trades") or []
        self._large_legs = top_leg_ids_by_forward_return(self._gt_trades)
        self._approved_rules = load_ev_wf_approved_rule_ids()

    def _reset_day_if_needed(self) -> None:
        """날짜 변경 시 일별 한도 카운터 초기화."""
        today = date.today().isoformat()
        if today != self._day:
            self._day = today
            self._day_spent_krw = 0.0
            self._day_trades = 0
            self._day_pnl_krw = 0.0

    def _append_log(self, record: dict[str, Any]) -> None:
        """
        live_trades.jsonl에 한 줄 append.

        Args:
            record: 로그 dict.
        """
        LIVE_TRADES_LOG.parent.mkdir(parents=True, exist_ok=True)
        with LIVE_TRADES_LOG.open("a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False) + "\n")

    def _can_trade(self, rule_id: str, planned_krw: float | None = None) -> tuple[bool, str]:
        """
        일·쿨다운·손실 한도 검사.

        Args:
            rule_id: 규칙 ID.

        Returns:
            (허용 여부, 사유).
        """
        self._reset_day_if_needed()
        if self._day_trades >= LIVE_MAX_TRADES_PER_DAY:
            return False, "일 최대 거래 수 초과"
        need = float(planned_krw if planned_krw is not None else LIVE_ORDER_KRW)
        if self._day_spent_krw + need > LIVE_DAILY_KRW_MAX:
            return False, "일 주문 한도 초과"
        if self._day_pnl_krw <= -abs(LIVE_DAILY_LOSS_LIMIT_KRW):
            return False, "일 손실 한도 초과"
        last = self._rule_last_unix.get(rule_id, 0.0)
        if time.time() - last < LIVE_COOLDOWN_MIN * 60:
            return False, f"규칙 쿨다운({LIVE_COOLDOWN_MIN}분)"
        return True, ""

    def _load_ohlc_df(self) -> None:
        """drawdown tier용 3m OHLC 캐시."""
        try:
            frames = load_frames_from_db(self, SYMBOL, lookback_days=CHART_LOOKBACK_DAYS)
            self._ohlc_df = frames.get(MATCH_PRIMARY_INTERVAL)
        except Exception:
            self._ohlc_df = None

    def _resolve_buy_amount_krw(self, hit: dict[str, Any]) -> float:
        """
        총자산·현금·EV/WF·leg 티어로 매수 원화 산출.

        GT_SIGNAL_CAUSAL=1 이면 시뮬 sim_tier_enhanced와 동일 인과 tier·weight.

        Args:
            hit: evaluate_live_rules 항목.

        Returns:
            매수 원화.
        """
        rid = hit["rule_id"]
        if rid not in self._approved_rules:
            return 0.0
        price = float(hit["close"])
        cash = 0.0
        qty = 0.0
        try:
            bal = self.load_balances_dict()
            sym = bal.get(SYMBOL, {})
            cash = float(sym.get("available_krw") or sym.get("krw") or 0)
            qty = float(sym.get("balance") or 0)
        except Exception:
            return 0.0
        if live_sizing_enabled():
            if self._ohlc_df is None:
                self._load_ohlc_df()
            return self._position_state.plan_buy_amount_krw(
                hit["dt"],
                price,
                cash,
                qty,
                self._ohlc_df,
                enhanced=False,
                fee_rate=TRADING_FEE_RATE,
            )
        scale = live_buy_asset_pct_scale(
            rid,
            hit["dt"],
            self._gt_trades,
            approved_rules=self._approved_rules,
            large_legs=self._large_legs,
        )
        return compute_buy_amount_krw(
            cash,
            qty,
            price,
            1.0,
            1.0,
            asset_pct_scale=scale,
            fee_rate=TRADING_FEE_RATE,
        )

    def _execute_order(self, hit: dict[str, Any]) -> dict[str, Any]:
        """
        매수·매도 주문 실행 또는 드라이런.

        Args:
            hit: evaluate_live_rules 항목.

        Returns:
            로그용 결과 dict.
        """
        side = hit["side"]
        price = float(hit["close"])
        if side == "buy":
            amount_krw = self._resolve_buy_amount_krw(hit)
            if amount_krw <= 0:
                return {
                    "ts": datetime.now().isoformat(timespec="seconds"),
                    "rule_id": hit["rule_id"],
                    "side": side,
                    "signal_dt": hit["dt"],
                    "price": price,
                    "amount_krw": 0,
                    "live_enabled": LIVE_TRADING_ENABLED,
                    "ok": False,
                    "message": "매수 스킵(EV/WF·leg·현금)",
                }
        else:
            amount_krw = float(LIVE_ORDER_KRW)
        record: dict[str, Any] = {
            "ts": datetime.now().isoformat(timespec="seconds"),
            "rule_id": hit["rule_id"],
            "side": side,
            "signal_dt": hit["dt"],
            "price": price,
            "amount_krw": amount_krw,
            "live_enabled": LIVE_TRADING_ENABLED,
            "ok": False,
            "message": "",
        }

        if not LIVE_TRADING_ENABLED:
            record["message"] = "dry_run (LIVE_TRADING_ENABLED=0)"
            record["ok"] = True
            return record

        try:
            if side == "buy":
                ok = self.buyCoinMarket(SYMBOL, int(amount_krw), count=None)
                record["ok"] = bool(ok)
                record["message"] = "buyCoinMarket" if ok else "buy failed"
            elif side == "sell":
                bal = self.load_balances_dict().get(SYMBOL, {})
                qty = float(bal.get("balance") or 0)
                if qty <= 0:
                    record["message"] = "보유 없음"
                else:
                    gross = qty * price
                    record["amount_krw"] = round(gross, 0)
                    ok = self.sellCoinMarket(SYMBOL, int(price), qty)
                    record["ok"] = bool(ok)
                    record["message"] = f"sell qty={qty}" if ok else "sell failed"
                    if record["ok"] and live_sizing_enabled():
                        fee = gross * TRADING_FEE_RATE
                        self._position_state.record_sell(
                            gross, fee, full_close=True
                        )
                        self._position_state.save()
            else:
                record["message"] = f"unknown side {side}"
        except Exception as exc:
            record["message"] = str(exc)

        if record["ok"]:
            spent = float(record.get("amount_krw") or amount_krw)
            self._day_spent_krw += spent
            self._day_trades += 1
            self._rule_last_unix[hit["rule_id"]] = time.time()
            if live_sizing_enabled() and side == "buy":
                fee = spent * TRADING_FEE_RATE
                self._position_state.record_buy(hit["dt"], price, spent, fee)
                self._position_state.save()
        return record

    def run_once(self) -> None:
        """1회: 규칙 평가 → (허용 시) 주문 → 텔레그램."""
        rules = load_monitor_rules()
        print(
            f"[06] {datetime.now():%Y-%m-%d %H:%M:%S} "
            f"{COIN_NAME} live={'ON' if LIVE_TRADING_ENABLED else 'OFF'} "
            f"rules={len(rules)}"
        )
        if not rules:
            print("  monitor_rules 없음")
            return

        fired = evaluate_live_rules(rules)
        balances = None
        try:
            balances = self.load_balances_dict()
        except Exception:
            pass

        if not fired:
            print("  발화 없음")
            return

        for hit in fired:
            rid = hit["rule_id"]
            if hit["side"] == "buy" and hit["rule_id"] not in self._approved_rules:
                print(f"  [{hit['side']}] {rid} @ {hit['dt']}")
                print("    skip: EV/WF 미통과 규칙")
                continue
            planned = (
                self._resolve_buy_amount_krw(hit)
                if hit["side"] == "buy"
                else float(LIVE_ORDER_KRW)
            )
            ok, reason = self._can_trade(rid, planned)
            print(f"  [{hit['side']}] {rid} @ {hit['dt']}")
            if not ok:
                print(f"    skip: {reason}")
                continue
            if hit["side"] == "buy" and planned <= 0:
                print("    skip: 매수금액 0")
                continue
            log = self._execute_order(hit)
            self._append_log(log)
            print(f"    order: {log['message']} ok={log['ok']}")
            msg = build_rule_alert_message(hit, balances)
            if log["ok"]:
                msg += f"\n[체결] {log['message']}"
            else:
                msg += f"\n[실패] {log['message']}"
            self._send_coin_msg(msg)

    def run_loop(self, sleep_sec: int) -> None:
        """
        상시 루프.

        Args:
            sleep_sec: 대기 초.
        """
        print(f"[06] 실거래 루프 시작 · sleep={sleep_sec}s")
        while True:
            self.run_once()
            time.sleep(sleep_sec)
