"""Risk Management — Circuit Breakers, Position Sizing, Kelly Criterion.

Prevents catastrophic losses by enforcing risk limits before executing trades.
"""
from __future__ import annotations
import logging
import numpy as np
from datetime import datetime, timedelta
from typing import Optional

logger = logging.getLogger(__name__)


def _cfg(key: str, fallback):
    try:
        from flask import current_app
        return current_app.config.get(key, fallback)
    except RuntimeError:
        return fallback


class RiskManager:
    """Pre-trade risk checks and position sizing."""

    def __init__(self):
        self.max_daily_trades = 10
        self.max_drawdown_pct = 15.0  # 15% max portfolio drawdown
        self.max_single_position_pct = 25.0  # max 25% per position
        self.max_correlated_exposure_pct = 50.0  # max correlated assets

    def should_trade(
        self, asset_id: str, signal: str, score: float,
        confidence: str, regime: str = 'ranging',
        direction_probability: float = 0.5,
    ) -> dict:
        """Check if a trade should be executed.

        Returns:
            dict with 'allowed' (bool), 'reason' (str), 'adjustments' (dict)
        """
        reasons = []
        adjustments = {}

        # 1. Direction probability threshold
        threshold = _cfg('ML_DIRECTION_THRESHOLD', 0.65)
        if direction_probability < threshold and signal != 'HOLD':
            reasons.append(
                f'Direction probability {direction_probability:.1%} < '
                f'threshold {threshold:.1%}'
            )

        # 2. Low confidence filter
        if confidence == 'Low' and abs(score) < 50:
            reasons.append(f'Low confidence with moderate score ({score})')

        # 3. Regime-based restrictions
        if regime == 'volatile':
            adjustments['position_scale'] = 0.3
            if abs(score) < 60:
                reasons.append('Volatile regime requires high conviction (score > 60)')
        elif regime == 'trending_down' and signal == 'BUY':
            adjustments['position_scale'] = 0.5
            if abs(score) < 50:
                reasons.append('Buying in downtrend requires strong conviction')
        elif regime == 'ranging':
            adjustments['position_scale'] = 0.7

        # 4. Check recent signals (avoid overtrading)
        recent_count = self._count_recent_signals(asset_id)
        if recent_count >= self.max_daily_trades:
            reasons.append(f'Max daily trades ({self.max_daily_trades}) reached')

        allowed = len(reasons) == 0

        return {
            'allowed': allowed,
            'reasons': reasons,
            'adjustments': adjustments,
            'risk_checks': {
                'direction_probability': direction_probability,
                'confidence': confidence,
                'regime': regime,
                'score': score,
                'recent_trades': recent_count,
            },
        }

    def kelly_position_size(
        self, winrate: float, avg_win: float, avg_loss: float,
        capital: float, fraction: float | None = None,
    ) -> dict:
        """Calculate Kelly Criterion position size.

        Args:
            winrate: Historical win rate (0-1)
            avg_win: Average winning return (e.g. 0.03 = 3%)
            avg_loss: Average losing return (e.g. -0.02 = 2%, passed as positive)
            capital: Total available capital
            fraction: Kelly fraction (default: half-Kelly from config)

        Returns:
            dict with kelly_pct, position_size, explanation
        """
        if fraction is None:
            fraction = _cfg('ML_KELLY_FRACTION', 0.5)

        if avg_loss <= 0 or winrate <= 0:
            return {
                'kelly_pct': 0.0,
                'position_size': 0.0,
                'explanation': 'Insufficient data for Kelly calculation',
            }

        # Kelly formula: f* = (p * b - q) / b
        # where p = win probability, q = 1-p, b = avg_win/avg_loss
        b = abs(avg_win) / abs(avg_loss)
        q = 1 - winrate
        full_kelly = (winrate * b - q) / b

        # Cap at maximum and apply fraction
        full_kelly = max(0, min(full_kelly, 0.5))  # Never bet more than 50%
        fractional_kelly = full_kelly * fraction

        # Cap at max single position
        fractional_kelly = min(fractional_kelly, self.max_single_position_pct / 100)

        position_size = capital * fractional_kelly

        return {
            'kelly_pct': round(fractional_kelly * 100, 2),
            'full_kelly_pct': round(full_kelly * 100, 2),
            'position_size': round(position_size, 0),
            'fraction_used': fraction,
            'explanation': (
                f'Win rate: {winrate:.1%}, Avg W/L ratio: {b:.2f}, '
                f'Full Kelly: {full_kelly:.1%}, '
                f'{fraction:.0%}-Kelly: {fractional_kelly:.1%}'
            ),
        }

    def _count_recent_signals(self, asset_id: str, hours: int = 24) -> int:
        """Count recently generated signals for a asset."""
        try:
            from app.models.signal import TradingSignal
            from app.extensions import db

            cutoff = datetime.utcnow() - timedelta(hours=hours)
            count = TradingSignal.query.filter(
                TradingSignal.asset_id == asset_id,
                TradingSignal.created_at >= cutoff,
            ).count()
            return count
        except Exception:
            return 0

    def compute_drawdown(self, equity_curve: list[float]) -> dict:
        """Compute current and max drawdown from equity curve."""
        if not equity_curve or len(equity_curve) < 2:
            return {'current_dd': 0.0, 'max_dd': 0.0, 'peak': 0.0}

        arr = np.array(equity_curve)
        peaks = np.maximum.accumulate(arr)
        drawdowns = (peaks - arr) / peaks

        return {
            'current_dd': float(drawdowns[-1]),
            'max_dd': float(np.max(drawdowns)),
            'peak': float(peaks[-1]),
            'is_in_drawdown': float(drawdowns[-1]) > 0.01,
        }
