import pandas as pd
import datetime
import numpy as np

import market_trade.core.CoreTradeMath


class CoreIndicator():
    """Base class for technical indicators.

    This class provides the foundation for implementing various technical
    indicators used in trading signal generation.
    """

    def __init__(self, options: dict, data_type: str = None, predict_type: str = None, name: str = None):
        """Initialize CoreIndicator with configuration options.

        Args:
            options: Dictionary containing indicator-specific parameters.
            data_type: Type of data to process (e.g., 'ohlc'). Defaults to None.
            predict_type: Type of prediction to make (e.g., 'trend'). Defaults to None.
            name: Optional identifier. Defaults to None.
        """
        self.options = options
        self.data_type = data_type  # ohlc
        self.predict_type = predict_type  # trend

    def get_answer(self, data: pd.DataFrame):
        """Get indicator answer from data.

        Args:
            data: DataFrame containing market data.

        Returns:
            Calculated indicator values or "ERROR" if not implemented.
        """
        return "ERROR"


class IndicatorsAggregator:
    """Aggregates and manages multiple indicator instances.

    Example usage:
        indicators = {
            'ind_BB': {
                'className': ind_BB,
                'params': {'MeanType': 'SMA', 'window': 15, 'valueType': 'close', 'kDev': 2.5}
            }
        }
        data_dict = {
            'ind_BB': df_candle[:1000]
        }
        aggregator = IndicatorsAggregator(indicators)
        results = aggregator.get_answer(data_dict)
    """

    def __init__(self, ind_dict=None):
        """Initialize aggregator with indicator dictionary.

        Args:
            ind_dict: Dictionary mapping indicator names to configurations.
                Defaults to empty dict if not provided.
        """
        self.ind_dict = ind_dict if ind_dict is not None else {}
        self.ind_instances = {}
        self.ans = {}
        self.create_indicators_instance()

    def create_indicators_instance(self):
        """Create instances of all configured indicators."""
        for i in self.ind_dict.keys():
            self.ind_instances[i] = self.ind_dict[i]['className'](self.ind_dict[i]['params'])

    def get_answer(self, data_dict=None):
        """Calculate answers from all indicators.

        Args:
            data_dict: Dictionary mapping indicator names to their data.
                Defaults to empty dict.

        Returns:
            Dictionary of indicator results.
        """
        if data_dict is None:
            data_dict = {}
        ans = {}
        for i in data_dict.keys():
            ans[i] = self.ind_instances[i].get_answer(data_dict[i])
        return ans


class ind_BB(CoreIndicator):
    """Bollinger Bands indicator implementation.

    Calculates Bollinger Bands using moving average and standard deviation.

    Required options:
        MeanType: Type of moving average (e.g., 'SMA')
        window: Period for calculations (int)
        valueType: Price type to use ('low', 'high', 'open', 'close')
        kDev: Standard deviation multiplier (float)
    """

    def __init__(self, options: dict, name=None):
        """Initialize Bollinger Bands indicator.

        Args:
            options: Configuration parameters dictionary.
            name: Optional identifier.
        """
        super().__init__(
            options=options,
            data_type='ohlc',
            predict_type='trend',
            name=name
        )

    def get_answer(self, data: pd.DataFrame):
        """Calculate Bollinger Bands values.

        Args:
            data: DataFrame with OHLC price data.

        Returns:
            Dictionary containing:
                - BB: Middle band (moving average)
                - STD: Standard deviation
                - pSTD: Upper band (BB + kDev * STD)
                - mSTD: Lower band (BB - kDev * STD)
                - x: Date array
        """
        data = data.reset_index(drop=True)
        ans = {}

        op_ma = {
            'dataType': 'ohcl',
            'action': 'findMean',
            'actionOptions': {
                'MeanType': self.options['MeanType'],
                'valueType': self.options['valueType'],
                'window': self.options['window']
            }
        }
        ans['BB'] = market_trade.core.CoreTradeMath.CoreMath(data, op_ma).ans

        op_std = {
            'dataType': 'ohcl',
            'action': 'findSTD',
            'actionOptions': {
                'valueType': self.options['valueType'],
                'window': self.options['window']
            }
        }
        ans['STD'] = market_trade.core.CoreTradeMath.CoreMath(data, op_std).ans
        ans['pSTD'] = ans['BB'] + ans['STD'] * self.options['kDev']
        ans['mSTD'] = ans['BB'] - ans['STD'] * self.options['kDev']
        ans['x'] = np.array(data['date'][self.options['window']-1:].to_list())
        self.ans = ans
        return ans
