"""
Relative Premium Scoring Service
Replaces static premium filter with context-aware relative premium scoring
"""
import pandas as pd
import numpy as np
from typing import Dict, Optional
from datetime import datetime, timedelta
import asyncpg
from utils.logger import logger
from utils.error_handler import safe_divide


class RelativePremiumScorer:
    """
    Computes relative premium metrics to replace static premium filtering.
    Premium of $80K might be significant for AAPL but noise for TSLA.
    """
    
    def __init__(self, pool: asyncpg.Pool = None):
        self.pool = pool
        # Configuration
        self.rolling_window_days = 20  # Rolling window for z-score calculation
        self.min_relative_premium_threshold = 0.60  # 60th percentile minimum
        
    async def fetch_historical_premium_stats(
        self, 
        symbol: str, 
        reference_date: datetime.date
    ) -> Dict[str, float]:
        """
        Fetch historical premium statistics for a symbol.
        Returns: {mean, std, min, max, median} for rolling window
        """
        if not self.pool:
            logger.warning("No database pool available for historical premium stats")
            return {}
        
        try:
            # Calculate window start date
            window_start = reference_date - timedelta(days=self.rolling_window_days + 5)  # Extra buffer
            
            async with self.pool.acquire() as conn:
                query = """
                    SELECT 
                        AVG(premium_num) as mean_premium,
                        STDDEV(premium_num) as std_premium,
                        MIN(premium_num) as min_premium,
                        MAX(premium_num) as max_premium,
                        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY premium_num) as median_premium
                    FROM (
                        SELECT 
                            CAST(REGEXP_REPLACE("Premium"::text, '[^\d.]', '', 'g') AS numeric) as premium_num
                        FROM "OptionsFlow_monthly"
                        WHERE UPPER(TRIM("Symbol")) = $1
                          AND "Premium" IS NOT NULL
                          AND TRIM("Premium"::text) <> ''
                          AND "CreatedDate" >= $2
                          AND "CreatedDate" <= $3
                          AND "StockEtf" = 'STOCK'
                    ) subq
                    WHERE premium_num > 0
                """
                
                row = await conn.fetchrow(
                    query,
                    symbol.upper(),
                    window_start.strftime('%Y-%m-%d'),
                    reference_date.strftime('%Y-%m-%d')
                )
                
                if row and row['mean_premium']:
                    return {
                        'mean': float(row['mean_premium']) if row['mean_premium'] else 0.0,
                        'std': float(row['std_premium']) if row['std_premium'] else 0.0,
                        'min': float(row['min_premium']) if row['min_premium'] else 0.0,
                        'max': float(row['max_premium']) if row['max_premium'] else 0.0,
                        'median': float(row['median_premium']) if row['median_premium'] else 0.0,
                    }
        except Exception as e:
            logger.debug(f"Error fetching historical premium stats for {symbol}: {e}")
        
        return {}
    
    async def calculate_intraday_percentile(
        self,
        df: pd.DataFrame,
        symbol: str,
        flow_date: datetime.date,
        premium: float
    ) -> Optional[float]:
        """
        Calculate percentile rank of premium within same-day flow for the symbol.
        Returns 0-100 percentile value.
        """
        try:
            # Filter to same symbol and date
            same_day_flow = df[
                (df['symbol_norm'] == symbol.upper()) &
                (df['flow_date_cst'] == flow_date) &
                (df['premium_num'].notna()) &
                (df['premium_num'] > 0)
            ]
            
            if len(same_day_flow) < 3:  # Need at least 3 trades for meaningful percentile
                return None
            
            # Calculate percentile rank
            all_premiums = same_day_flow['premium_num'].values
            percentile = (np.sum(all_premiums <= premium) / len(all_premiums)) * 100.0
            
            return float(percentile)
        except Exception as e:
            logger.debug(f"Error calculating intraday percentile for {symbol}: {e}")
            return None
    
    def calculate_zscore(
        self,
        premium: float,
        stats: Dict[str, float]
    ) -> Optional[float]:
        """
        Calculate z-score of premium relative to historical distribution.
        Returns z-score (standard deviations from mean).
        """
        if not stats or stats.get('std', 0) == 0:
            return None
        
        mean = stats.get('mean', 0)
        std = stats.get('std', 1)
        
        if std == 0:
            return None
        
        zscore = (premium - mean) / std
        return float(zscore)
    
    def calculate_relative_premium_score(
        self,
        premium: float,
        zscore: Optional[float],
        intraday_percentile: Optional[float],
        stats: Dict[str, float]
    ) -> float:
        """
        Calculate composite relative premium score (0-100).
        Combines z-score and intraday percentile with median normalization.
        
        Logic:
        - High z-score (unusual size) = higher score
        - High intraday percentile (large relative to today's flow) = higher score
        - Normalize by median to account for symbol-specific scaling
        """
        score = 0.0
        
        # Z-score component (40% weight)
        # Z-score > 1 = 1 std above mean, Z-score > 2 = 2 std above mean
        if zscore is not None:
            # Normalize z-score to 0-40 range (clamp at ±3 sigma)
            zscore_normalized = max(0, min(40, (zscore + 3) * (40 / 6)))
            score += zscore_normalized
        
        # Intraday percentile component (40% weight)
        if intraday_percentile is not None:
            # Percentile is already 0-100, scale to 0-40
            score += (intraday_percentile / 100.0) * 40.0
        
        # Median normalization component (20% weight)
        # Premium > median gets bonus, premium < median gets penalty
        if stats and stats.get('median', 0) > 0:
            median_ratio = premium / stats['median']
            # Ratio > 1.5 = strong, ratio > 2.0 = very strong
            if median_ratio >= 2.0:
                score += 20.0
            elif median_ratio >= 1.5:
                score += 15.0
            elif median_ratio >= 1.0:
                score += 10.0
            elif median_ratio >= 0.5:
                score += 5.0
        
        return min(100.0, max(0.0, score))
    
    async def enrich_with_relative_premium(
        self,
        df: pd.DataFrame
    ) -> pd.DataFrame:
        """
        Add relative premium metrics to DataFrame.
        Adds: premium_zscore, premium_percentile_intraday, relative_premium_score
        """
        if df.empty:
            return df
        
        df = df.copy()
        
        # Initialize new columns
        df['premium_zscore'] = None
        df['premium_percentile_intraday'] = None
        df['relative_premium_score'] = 0.0
        
        # Cache historical stats per symbol to avoid repeated queries
        stats_cache: Dict[str, Dict[str, float]] = {}
        
        # Group by symbol and date for batch processing
        unique_symbols = df['symbol_norm'].unique()
        unique_dates = df['flow_date_cst'].unique()
        
        logger.info(f"Calculating relative premium scores for {len(unique_symbols)} symbols")
        
        for symbol in unique_symbols:
            # Fetch historical stats once per symbol
            if self.pool:
                # Use the most recent date for this symbol as reference
                symbol_dates = df[df['symbol_norm'] == symbol]['flow_date_cst'].unique()
                if len(symbol_dates) > 0:
                    reference_date = max(symbol_dates)
                    stats = await self.fetch_historical_premium_stats(symbol, reference_date)
                    stats_cache[symbol] = stats
                else:
                    stats_cache[symbol] = {}
            else:
                stats_cache[symbol] = {}
        
        # Calculate metrics for each row
        for idx, row in df.iterrows():
            premium = row.get('premium_num')
            if pd.isna(premium) or premium <= 0:
                continue
            
            symbol = row['symbol_norm']
            flow_date = row.get('flow_date_cst')
            
            # Get historical stats
            stats = stats_cache.get(symbol, {})
            
            # Calculate z-score
            zscore = self.calculate_zscore(premium, stats)
            if zscore is not None:
                df.at[idx, 'premium_zscore'] = float(zscore)
            
            # Calculate intraday percentile (using current DataFrame subset)
            intraday_percentile = self.calculate_intraday_percentile(
                df, symbol, flow_date, premium
            )
            if intraday_percentile is not None:
                df.at[idx, 'premium_percentile_intraday'] = float(intraday_percentile)
            
            # Calculate composite relative premium score
            relative_score = self.calculate_relative_premium_score(
                premium, zscore, intraday_percentile, stats
            )
            df.at[idx, 'relative_premium_score'] = float(relative_score)
        
        logger.info(f"Relative premium scoring complete. Mean score: {df['relative_premium_score'].mean():.2f}")
        
        return df

