"""Student's T distribution fitter."""

import numpy as np
import pandas as pd
from scipy.stats import t as student_t
from scipy.optimize import minimize
from typing import Dict, Optional

from .base import DistributionFitter


class StudentT(DistributionFitter):
    """Student's T distribution fitter for heavy-tailed returns."""
    
    def __init__(self, returns: np.ndarray):
        """
        Initialize Student's T distribution fitter.
        
        Parameters
        ----------
        returns : np.ndarray
            Array of returns
        """
        super().__init__(returns)
        self.name = "StudentT"
    
    def fit(self) -> Dict[str, float]:
        """
        Fit Student's T distribution using MLE.
        
        Returns
        -------
        dict
            Fitted parameters: df, loc, scale
        """
        # Initial parameter estimates
        initial_loc = np.mean(self.returns)
        initial_scale = np.std(self.returns) * np.sqrt(0.5)  # Approx adjustment
        
        # Estimate degrees of freedom from kurtosis
        empirical_kurt = pd.Series(self.returns).kurtosis()
        if empirical_kurt > 3:
            initial_df = max(2.1, 6 / (empirical_kurt - 3))  # Heuristic
        else:
            initial_df = 10
        
        # Use scipy's fit method
        df, loc, scale = student_t.fit(self.returns, 
                                     df=initial_df,
                                     loc=initial_loc,
                                     scale=initial_scale)
        
        self.params = {
            'df': df,
            'loc': loc,
            'scale': scale
        }
        
        # Calculate log-likelihood, AIC, BIC
        self.loglikelihood = np.sum(student_t.logpdf(self.returns, **self.params))
        self.aic = -2 * self.loglikelihood + 2 * len(self.params)
        self.bic = -2 * self.loglikelihood + len(self.params) * np.log(len(self.returns))
        
        self._fitted = True
        return self.params
    
    def pdf(self, x: np.ndarray) -> np.ndarray:
        """
        Probability density function.
        
        Parameters
        ----------
        x : np.ndarray
            Points at which to evaluate PDF
            
        Returns
        -------
        np.ndarray
            PDF values
        """
        if not self._fitted:
            self.fit()
        
        return student_t.pdf(x, **self.params)
    
    def cdf(self, x: np.ndarray) -> np.ndarray:
        """
        Cumulative distribution function.
        
        Parameters
        ----------
        x : np.ndarray
            Points at which to evaluate CDF
            
        Returns
        -------
        np.ndarray
            CDF values
        """
        if not self._fitted:
            self.fit()
        
        return student_t.cdf(x, **self.params)
    
    def ppf(self, q: np.ndarray) -> np.ndarray:
        """
        Percent point function (inverse of CDF).
        
        Parameters
        ----------
        q : np.ndarray
            Quantiles to evaluate
            
        Returns
        -------
        np.ndarray
            PPF values
        """
        if not self._fitted:
            self.fit()
        
        return student_t.ppf(q, **self.params)
    
    def es(self, alpha: float = 0.05) -> float:
        """
        Calculate Expected Shortfall (Conditional VaR).
        
        Parameters
        ----------
        alpha : float
            Significance level (default: 0.05)
            
        Returns
        -------
        float
            Expected Shortfall value
        """
        if not self._fitted:
            self.fit()
        
        # Use numerical integration for ES
        var = self.var(alpha)
        
        # For Student's T, we can use analytical formula or numerical integration
        # Here we use numerical integration for better accuracy
        from scipy.integrate import quad
        
        def integrand(x):
            return x * self.pdf(x)
        
        # Integrate from -∞ to VaR
        es_value, _ = quad(integrand, -np.inf, var)
        es_value = es_value / alpha
        
        return es_value
    
    def moment(self, order: int) -> float:
        """
        Calculate the theoretical moment of the distribution.
        
        Parameters
        ----------
        order : int
            Order of the moment
            
        Returns
        -------
        float
            Theoretical moment
        """
        if not self._fitted:
            self.fit()
        
        df = self.params['df']
        loc = self.params['loc']
        scale = self.params['scale']
        
        if order == 1:
            return loc
        elif order == 2 and df > 2:
            return scale**2 * df / (df - 2) + loc**2
        elif order == 3 and df > 3:
            return 0.0  # Symmetric distribution
        elif order == 4 and df > 4:
            return 3 * scale**4 * df**2 / ((df - 2) * (df - 4))
        else:
            return np.nan