"""
Institutional Confidence Metrics Service
Calculates confidence scores for institutional flow signals
"""
import pandas as pd
import numpy as np
from typing import Optional
from utils.logger import logger


class InstitutionalConfidence:
    """
    Calculates institutional confidence metrics:
    - confidence_score (0-100)
    - institutional_likelihood (0-1)
    - dealer_pain_level (0-100)
    - expected_move_vs_implied (ratio)
    """
    
    def __init__(self):
        # Configuration
        self.min_premium_for_institutional = 200000  # Minimum premium for institutional classification
    
    def calculate_confidence_score(self, row: pd.Series) -> float:
        """
        Calculate overall confidence score (0-100) combining multiple factors.
        Higher = more confidence in signal quality.
        """
        score = 0.0
        
        # Relative premium component (0-25 points)
        relative_premium = row.get('relative_premium_score', 0) or 0
        score += (relative_premium / 100.0) * 25.0
        
        # Signal strength component (0-25 points)
        signal_strength = row.get('signal_strength', 0) or 0
        score += (signal_strength / 100.0) * 25.0
        
        # Dealer pressure component (0-20 points)
        dealer_pressure = row.get('dealer_hedge_pressure_score', 0) or 0
        score += (dealer_pressure / 100.0) * 20.0
        
        # Flow continuation component (0-15 points)
        follow_on_ratio = row.get('follow_on_ratio')
        if follow_on_ratio is not None and not pd.isna(follow_on_ratio):
            score += follow_on_ratio * 15.0
        
        # Strike laddering component (0-15 points)
        strike_laddering = row.get('strike_laddering_detected', False)
        if strike_laddering:
            score += 15.0
        
        return min(100.0, max(0.0, score))
    
    def calculate_institutional_likelihood(self, row: pd.Series) -> float:
        """
        Calculate likelihood that flow is institutional (0-1).
        Based on premium size, trade characteristics, and patterns.
        """
        premium_num = row.get('premium_num', 0) or 0
        relative_premium = row.get('relative_premium_score', 0) or 0
        size_concentration = row.get('size_concentration_score', 0) or 0
        
        likelihood = 0.0
        
        # Premium size component (0-40%)
        if premium_num >= 1000000:  # $1M+
            likelihood += 0.40
        elif premium_num >= 500000:  # $500K+
            likelihood += 0.30
        elif premium_num >= self.min_premium_for_institutional:
            likelihood += 0.20
        
        # Relative premium component (0-30%)
        if relative_premium >= 80:
            likelihood += 0.30
        elif relative_premium >= 60:
            likelihood += 0.20
        elif relative_premium >= 40:
            likelihood += 0.10
        
        # Size concentration component (0-30%)
        # Institutional trades are often concentrated
        if size_concentration >= 70:
            likelihood += 0.30
        elif size_concentration >= 50:
            likelihood += 0.20
        elif size_concentration >= 30:
            likelihood += 0.10
        
        return min(1.0, max(0.0, likelihood))
    
    def calculate_dealer_pain_level(self, row: pd.Series) -> float:
        """
        Calculate dealer pain level (0-100).
        Higher = dealers in pain (large gamma exposure, forced to hedge).
        """
        dealer_pressure = row.get('dealer_hedge_pressure_score', 0) or 0
        net_gamma = abs(row.get('net_gamma_exposure_per_symbol', 0) or 0)
        gamma_flip_prox = row.get('gamma_flip_proximity')
        
        pain = 0.0
        
        # Dealer pressure component (0-50 points)
        pain += (dealer_pressure / 100.0) * 50.0
        
        # Gamma exposure component (0-30 points)
        # Large absolute gamma = more pain
        if net_gamma > 0:
            normalized_gamma = min(1.0, net_gamma / 1000000000)  # 1B threshold
            pain += normalized_gamma * 30.0
        
        # Gamma flip proximity component (0-20 points)
        # Near flip = high pain (dealers forced to adjust)
        if gamma_flip_prox is not None and not pd.isna(gamma_flip_prox):
            # Absolute value of proximity (closer to 0 = more pain)
            pain += (1.0 - abs(gamma_flip_prox)) * 20.0
        
        return min(100.0, max(0.0, pain))
    
    def calculate_expected_move_vs_implied(self, row: pd.Series) -> Optional[float]:
        """
        Calculate expected move vs implied move ratio.
        Estimates expected move from flow characteristics vs implied volatility.
        
        Returns: ratio (expected_move / implied_move)
        - >1.0 = flow suggests larger move than implied
        - <1.0 = flow suggests smaller move than implied
        - None if cannot calculate
        """
        # Simplified calculation: use premium and delta exposure as proxies
        premium_num = row.get('premium_num', 0) or 0
        spot_num = row.get('spot_num', 0) or 0
        delta_exposure = abs(row.get('delta_exposure', 0) or 0)
        
        if pd.isna(spot_num) or spot_num == 0:
            return None
        
        # Estimate expected move from premium paid
        # High premium relative to spot = expectation of larger move
        if premium_num > 0:
            prem_to_spot_ratio = premium_num / spot_num
            
            # Estimate implied move (simplified: assume 1% IV = 1% move expectation)
            # This is a placeholder - in production, use actual IV from options chain
            implied_move_pct = 2.0  # Default 2% implied move
            
            # Estimate expected move from premium
            # Premium of 2% of spot = expectation of ~2% move (rough approximation)
            expected_move_pct = prem_to_spot_ratio * 100.0
            
            # Calculate ratio
            if implied_move_pct > 0:
                ratio = expected_move_pct / implied_move_pct
                return float(ratio)
        
        return None
    
    def enrich_with_confidence_metrics(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Add institutional confidence metrics to DataFrame.
        Adds: confidence_score, institutional_likelihood, dealer_pain_level, expected_move_vs_implied
        """
        if df.empty:
            return df
        
        df = df.copy()
        
        # Initialize columns
        df['confidence_score'] = 0.0
        df['institutional_likelihood'] = 0.0
        df['dealer_pain_level'] = 0.0
        df['expected_move_vs_implied'] = None
        
        # Calculate metrics
        df['confidence_score'] = df.apply(self.calculate_confidence_score, axis=1)
        df['institutional_likelihood'] = df.apply(self.calculate_institutional_likelihood, axis=1)
        df['dealer_pain_level'] = df.apply(self.calculate_dealer_pain_level, axis=1)
        
        # Expected move vs implied (some rows may not have this)
        for idx in df.index:
            expected_move = self.calculate_expected_move_vs_implied(df.iloc[idx])
            if expected_move is not None:
                df.at[idx, 'expected_move_vs_implied'] = float(expected_move)
        
        logger.info(f"Confidence metrics complete. Mean confidence: {df['confidence_score'].mean():.2f}")
        
        return df

