from abc import ABC, abstractmethod 
from pydantic import BaseModel, Field, validator, Extra
import pandas as pd
from typing import List, Optional, Union, Dict
from datetime import date
from scipy import stats

from .timeconverter import FreqEnum
class DCA(ABC):
    """ 
    Declare the DCA abstract Class that can be subclassed by the all Diferent 
    declination types
    """
    @abstractmethod
    def __str__(self):
        pass 
    
    @abstractmethod
    def __repr__(self):
        pass   
    
    @abstractmethod
    def forecast(self):
        pass     

    """
    Forecast Class. It contains a template for the columns requiered in a Forecast when
    generated by DCA classes and Methods. 

    This class was built with two main objectives. 1) Validate the input data 2) Easily serialize to json

    """
    
class Forecast(BaseModel):
    """Forecast     Forecast Class. It contains a template for the columns requiered in a Forecast when
    generated by DCA classes and Methods. 

    This class was built with two main objectives. 1) Validate the input data 2) Easily serialize to json

    Attributes:
        date (List[Union[date,int]])) : Datetime column
        oil_rate  (Optional[List[float]]) : Oil rate column
        oil_cum  (Optional[List[float]]) : Oil Cumulative Column
        oil_volume  (Optional[List[float]]) : Oil Volume Column
        gas_rate (Optional[List[float]]) : Gas Rate Column
        gas_cum (Optional[List[float]]) : Gas Cumulative Column
        gas_volume (Optional[List[float]]) : Gas Volume Column
        fluid_rate (Optional[List[float]]) : Fluid Rate Column
        water_rate (Optional[List[float]]) : Water Rate Column
        bsw (Optional[List[float]]) : Bsw Column
        wor (Optional[List[float]]) : Wor Column
        water_cum (Optional[List[float]]) : Water Cumulative Column
        fluid_cum (Optional[List[float]]) : Fluid Cumulative Column
        fluid_volume (Optional[List[float]]) : Fluid volumn Column
        iteration (Optional[List[int]]) : Number of Iterations
        period (Optional[List[str]]) : Name of the period
        scenario (Optional[List[str]]) : Name of the scenario
        well (Optional[List[str]]) : Name of the Well
        freq  (Literal['M','D','A']) : Frequency 

    Returns:
        forecast: Forecast instance
    """
    date : List[Union[int,date]]
    oil_rate : Optional[List[float]]
    oil_cum : Optional[List[float]]
    oil_volume : Optional[List[float]]
    gas_rate : Optional[List[float]]
    gas_cum : Optional[List[float]]
    gas_volume : Optional[List[float]]
    fluid_rate : Optional[List[float]]
    water_rate : Optional[List[float]]
    bsw : Optional[List[float]]
    wor : Optional[List[float]]
    water_cum : Optional[List[float]]
    fluid_cum : Optional[List[float]]
    water_cum : Optional[List[float]] 
    fluid_volume : Optional[List[float]] 
    iteration : Optional[List[int]]
    period : Optional[List[str]]
    scenario : Optional[List[str]] 
    well : Optional[List[str]] 
    freq : FreqEnum = Field(FreqEnum.M)
    
    def df(self):
        _forecast_dict = self.dict()
        freq = _forecast_dict.pop('freq')
        _fr = pd.DataFrame(_forecast_dict)
        if isinstance(self.date[0],date):
            _fr['date'] = pd.to_datetime(_fr['date'])
            _fr.set_index('date',inplace=True)
            _fr = _fr.to_period(freq)
        else:
            _fr.set_index('date',inplace=True)

        return _fr


allowed_prob_dist = [] 
dist_continu = [d for d in dir(stats) if isinstance(getattr(stats, d), stats.rv_continuous)]
dist_discrete = [d for d in dir(stats) if isinstance(getattr(stats, d), stats.rv_discrete)]

allowed_prob_dist.extend(dist_continu)
allowed_prob_dist.extend(dist_continu)


class ProbVar(BaseModel):
    dist: str = Field('norm')
    kw : dict = Field({'loc':0,'scale':1})
    factor: float = Field(1.)
    seed : int = Field(None)

    class Config:
        validate_assignment = True
        extra = Extra.forbid

    @validator('kw')
    def check_dist_build(cls,v,values):
        if isinstance(getattr(stats,values['dist'])(**v),stats._distn_infrastructure.rv_frozen):
            return v 
        else:
            raise ValueError(f"{v} are not allowed")

    def get_instance(self):
        return getattr(stats,self.dist)(**self.kw)

    def get_sample(self, size:Union[int,tuple]=None, ppf:float=None, seed=None):
        if seed is None:
            seed = self.seed

        if ppf is not None:
            return getattr(stats,self.dist)(**self.kw).ppf(ppf)*self.factor
        elif size:
            return getattr(stats,self.dist)(**self.kw).rvs(size=size,random_state=seed)*self.factor
        else:
            return getattr(stats,self.dist)(**self.kw).mean()*self.factor
