"""
변동성 기반 리스크 관리 모듈
- 예수금 비율 기반 비중 조절
- ATR/변동성 역가중 (Volatility Inverse Weighting)
- 종목별 안전 매수 금액 계산
"""
import logging
import pandas as pd
import numpy as np
from typing import List, Dict

logger = logging.getLogger("RiskManager")


class RiskManager:
    """
    [예수금 연동 리스크 관리 모듈]
    - 고정 금액이 아닌 '현재 예수금' 대비 퍼센트로 투자금 계산
    - 변동성 큰 종목 = 적게 매수, 변동성 작은 종목 = 많이 매수
    """
    
    def __init__(
        self,
        risk_pct_per_trade: float = 0.02,   # 1회 거래 시 허용 손실 비율 (2%)
        max_position_pct: float = 0.20,     # 종목당 최대 비중 (20%)
        min_position_amount: int = 50000,   # 최소 매수 금액 (5만원)
        use_kelly: bool = False,            # 켈리 공식 사용 여부
        kelly_multiplier: float = 0.5,      # 켈리 배율 (0.5 = 하프 켈리)
        slot_base_amount_cap: int = 0,      # 종목당 원화 상한 (0=미적용)
        # ── 반드시 적용되는 MAX_LOSS 기반 절대 상한 ─────────────────────
        # max_loss_per_trade_krw / |stop_loss_pct| = 투자 상한 금액
        # 0이면 미적용 (RiskManager 계산 결과를 그대로 사용)
        max_loss_per_trade_krw: int = 0,    # 1회 최대 손실 허용액(원), MAX_LOSS_PER_TRADE_KRW
        stop_loss_pct: float = 0.0,         # 손절 비율(음수 가능), STOP_LOSS_PCT
        # ── 사이즈 클래스별 매수 비율 (env/DB에서 주입, 하드코딩 금지) ──
        size_small_ratio: float = 0.70,     # 소형주 투자 비율 (SIZE_CLASS_SMALL_RATIO)
        size_mid_ratio: float = 0.85,       # 중형주 투자 비율 (SIZE_CLASS_MID_RATIO)
    ):
        """
        Args:
            risk_pct_per_trade: 1회 매매 시 허용할 손실 비율 (기본 0.02 = 2%)
            max_position_pct: 종목당 최대 투자 비율 (기본 0.20 = 20%)
            min_position_amount: 최소 매수 금액 (너무 작은 주문 방지)
            use_kelly: 켈리 공식 활성화 여부
            kelly_multiplier: 켈리 배율 (0.5 = 하프켈리, 0.25 = 쿼터켈리)
            slot_base_amount_cap: 종목당 매수 금액 원화 상한 (0이면 미적용, SLOT_BASE_AMOUNT_CAP)
        """
        self.risk_pct = risk_pct_per_trade
        self.max_pos_pct = max_position_pct
        self.min_amount = min_position_amount
        self.use_kelly = use_kelly
        self.kelly_mult = kelly_multiplier
        self.slot_base_amount_cap = slot_base_amount_cap if slot_base_amount_cap and slot_base_amount_cap > 0 else 0

        # ── MAX_LOSS 기반 절대 투자 상한 ─────────────────────────────────
        # = MAX_LOSS_PER_TRADE_KRW / |STOP_LOSS_PCT|
        # 0이면 미적용 (stop_loss_pct 가 0이면 계산 불가이므로 미적용)
        self._max_loss_krw = max_loss_per_trade_krw
        _stop_abs = abs(stop_loss_pct)
        self._max_loss_cap = int(max_loss_per_trade_krw / _stop_abs) if (max_loss_per_trade_krw > 0 and _stop_abs > 0) else 0

        # ── 사이즈 클래스 비율 (하드코딩 금지) ────────────────────────────
        self.size_small_ratio = size_small_ratio   # 소형주 (SIZE_CLASS_SMALL_RATIO)
        self.size_mid_ratio = size_mid_ratio       # 중형주 (SIZE_CLASS_MID_RATIO)

        logger.info(
            f"💰 RiskManager 초기화: 리스크{self.risk_pct*100}% | "
            f"최대비중{self.max_pos_pct*100}% | 켈리{'ON' if use_kelly else 'OFF'}"
            + (f" | 슬롯상한{self.slot_base_amount_cap:,}원" if self.slot_base_amount_cap else "")
            + (f" | MAX_LOSS상한{self._max_loss_cap:,}원" if self._max_loss_cap else "")
        )

    def calculate_volatility_atr(self, df: pd.DataFrame, period: int = 14) -> float:
        """
        ATR(Average True Range) 기반 변동성 계산
        
        Args:
            df: OHLC 데이터프레임 (컬럼: open, high, low, close)
            period: ATR 계산 기간 (기본 14)
            
        Returns:
            현재가 대비 ATR 비율 (%)
        """
        try:
            if df is None or len(df) < period:
                logger.warning(f"⚠️ ATR 계산: 데이터 부족 -> 기본값 3.0% 리턴")
                return 3.0
            
            # True Range 계산
            df = df.copy()
            df['tr'] = np.maximum(
                df['high'] - df['low'],
                np.maximum(
                    np.abs(df['high'] - df['close'].shift()),
                    np.abs(df['low'] - df['close'].shift())
                )
            )
            
            # ATR = TR의 이동평균
            atr = df['tr'].rolling(window=period).mean().iloc[-1]
            current_price = df['close'].iloc[-1]
            
            if current_price == 0:
                return 3.0
            
            # 현재가 대비 ATR 비율 (%)
            volatility_pct = (atr / current_price) * 100
            
            # 최소값 보정 (0.5% 미만은 0.5%로)
            return max(round(volatility_pct, 2), 0.5)
            
        except Exception as e:
            logger.error(f"❌ ATR 계산 에러: {e}")
            return 3.0

    def calculate_volatility_simple(self, candles: List[Dict], period: int = 10) -> float:
        """
        간단한 변동성 계산 (고저차 비율 평균)
        
        Args:
            candles: 캔들 데이터 리스트 [{'high': , 'low': , 'close': }, ...]
            period: 계산 기간 (기본 10일)
            
        Returns:
            변동성 (%)
        """
        try:
            if not candles or len(candles) < period:
                return 3.0
            
            recent = candles[-period:]
            volatility_sum = 0.0
            
            for candle in recent:
                high = float(candle.get('high', 0))
                low = float(candle.get('low', 0))
                close = float(candle.get('close', 1))
                
                if close == 0:
                    continue
                
                # (고가 - 저가) / 종가
                volatility_sum += (high - low) / close
            
            avg_vol = (volatility_sum / len(recent)) * 100
            return max(round(avg_vol, 2), 0.5)
            
        except Exception as e:
            logger.error(f"❌ 변동성 계산 에러: {e}")
            return 3.0

    def get_position_size(
        self,
        stock_name: str,
        current_balance: float,
        volatility_pct: float = None,
        df: pd.DataFrame = None,
        kelly_fraction: float = None,
        size_class: str = None,
    ) -> int:
        """
        [메인 함수] 종목별 안전 매수 금액 계산
        
        Args:
            stock_name: 종목명 (로깅용)
            current_balance: 현재 예수금
            volatility_pct: 변동성 (직접 제공 시)
            df: OHLC 데이터프레임 (ATR 계산용)
            kelly_fraction: 켈리 비율 (DB에서 계산한 값)
            size_class: 대/중/소형 구분 ("대", "중", "소")
            
        Returns:
            추천 매수 금액 (원)
        """
        try:
            if current_balance <= 0:
                logger.warning(f"⚠️ [{stock_name}] 예수금 0원 이하")
                return 0
            
            # 1. 변동성 계산
            if volatility_pct is None:
                if df is not None and not df.empty:
                    volatility_pct = self.calculate_volatility_atr(df)
                else:
                    logger.warning(f"⚠️ [{stock_name}] 변동성 데이터 없음 -> 기본값 3.0%")
                    volatility_pct = 3.0
            
            # 2. 기본 리스크 금액 계산 (예수금 * 리스크 비율)
            # 예: 1,000만 원 * 2% = 20만 원 (이 종목에서 최대 20만원까지 잃을 수 있음)
            allowable_risk = current_balance * self.risk_pct
            
            # 3. 켈리 공식 적용 (쿼터 켈리/하프 켈리)
            if self.use_kelly:
                if kelly_fraction is not None and kelly_fraction > 0:
                    # DB에서 계산한 켈리 비율 사용 (하프 켈리)
                    base_position_pct = kelly_fraction * self.kelly_mult
                    allowable_risk = current_balance * base_position_pct
                    logger.info(f"🎲 [{stock_name}] 켈리 적용(DB): {base_position_pct*100:.1f}% (켈리={kelly_fraction*100:.1f}% × 배율={self.kelly_mult})")
                else:
                    # 켈리 비율이 없으면 쿼터 켈리 배율을 기본값으로 사용
                    # 예: 리스크 1% × 쿼터 켈리 0.25 = 0.25% (더 보수적)
                    base_position_pct = self.risk_pct * self.kelly_mult
                    allowable_risk = current_balance * base_position_pct
                    logger.info(f"🎲 [{stock_name}] 켈리 적용(기본): {base_position_pct*100:.1f}% (리스크={self.risk_pct*100}% × 쿼터켈리={self.kelly_mult})")
            
            # 4. 변동성 역가중으로 매수 금액 계산
            # 공식: (허용 손실액) / (변동성 비율)
            # 변동성 5% -> 20만 / 0.05 = 400만원 매수
            # 변동성 10% -> 20만 / 0.10 = 200만원 매수
            risk_ratio = volatility_pct / 100.0
            calculated_amount = allowable_risk / risk_ratio
            
            # 5. 상한선 체크 (종목당 최대 비중)
            max_limit = current_balance * self.max_pos_pct

            if calculated_amount > max_limit:
                final_amount = max_limit
                note = f"(최대{self.max_pos_pct*100:.0f}% 제한)"
            else:
                final_amount = calculated_amount
                note = "(변동성기반)"

            # 5-2. 대형/소형 구간별 조정 (소형주는 포지션 축소)
            # → 비율은 하드코딩 금지: __init__ 시 SIZE_CLASS_SMALL/MID_RATIO 주입
            if size_class == "소":
                final_amount = int(final_amount * self.size_small_ratio)
                note = f"(소형주 {self.size_small_ratio*100:.0f}%)"
            elif size_class == "중":
                final_amount = int(final_amount * self.size_mid_ratio)
                note = f"(중형주 {self.size_mid_ratio*100:.0f}%)"
            elif size_class == "대":
                pass  # 대형주는 기존과 동일

            # 5-3. 슬롯 원화 상한 (SLOT_BASE_AMOUNT_CAP, 0이면 미적용)
            if self.slot_base_amount_cap > 0 and final_amount > self.slot_base_amount_cap:
                final_amount = self.slot_base_amount_cap
                note = f"(슬롯상한{self.slot_base_amount_cap:,}원)"

            # ── 5-4. MAX_LOSS 기반 절대 투자 상한 ────────────────────────
            # 어떤 전략이든 MAX_LOSS_PER_TRADE_KRW / |STOP_LOSS_PCT| 를 초과 불가
            # 이 값을 넘으면 손절 시 의도한 금액 손실(원)을 초과하게 됨
            if self._max_loss_cap > 0 and final_amount > self._max_loss_cap:
                final_amount = self._max_loss_cap
                note = f"(MAX_LOSS상한 {self._max_loss_cap:,}원)"

            # 6. 하한선 체크 (너무 작은 금액은 거래 안함)
            if final_amount < self.min_amount:
                logger.info(
                    f"🚫 [{stock_name}] 계산 금액({final_amount:,.0f}원) < "
                    f"최소금액({self.min_amount:,.0f}원) -> 매수 보류"
                )
                return 0
            
            logger.info(
                f"💰 [{stock_name}] 예수금:{current_balance:,.0f}원 | "
                f"변동성:{volatility_pct:.2f}% | 추천:{int(final_amount):,.0f}원 {note}"
            )
            
            return int(final_amount)
            
        except Exception as e:
            logger.error(f"❌ [{stock_name}] 포지션 사이즈 계산 실패: {e}")
            return 0

    def calculate_quantity(
        self, 
        current_price: float, 
        target_amount: int,
        fee_rate: float = 0.00015
    ) -> int:
        """
        목표 금액을 현재가와 수수료 고려하여 수량으로 변환
        
        Args:
            current_price: 현재 주가
            target_amount: 목표 투자 금액
            fee_rate: 수수료율 (기본 0.015%)
            
        Returns:
            매수 가능 수량 (주)
        """
        try:
            if current_price <= 0 or target_amount <= 0:
                return 0
            
            # 수수료 고려 실제 매수 가능 금액
            # 총비용 = 가격 * 수량 * (1 + 수수료율)
            # 수량 = 목표금액 / (가격 * (1 + 수수료율))
            max_buy_amount = target_amount / (1 + fee_rate)
            quantity = int(max_buy_amount / current_price)
            
            if quantity < 1:
                return 0
            
            # 예상 총 비용 계산 (확인용)
            expected_cost = quantity * current_price * (1 + fee_rate)
            
            logger.debug(
                f"💵 수량 계산: {current_price:,.0f}원 × {quantity}주 = "
                f"예상비용 {expected_cost:,.0f}원 (목표: {target_amount:,.0f}원)"
            )
            
            return quantity
            
        except Exception as e:
            logger.error(f"❌ 수량 계산 에러: {e}")
            return 0
