#multiplotter
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from .helpers import payoff_calculator, check_optype, check_trtype
from .blackscholes import black_scholes

abb={'c': 'Call',
    'p': 'Put',
    'b': 'Long',
    's': 'Short'}

def multi_plotter(spot_range=20, spot=100,
                op_list=[{'op_type':'c','strike':110,'tr_type':'s','op_pr':2,'contract':1},
                {'op_type':'p','strike':95,'tr_type':'s','op_pr':6,'contract':1}],
                  save=False, file='fig.png', risk_free_rate=4.00, default_volatility=32.00, y_limiter=None):
    """
    Plots a basic option payoff diagram for a multiple options and resultant payoff diagram

    Parameters
    ----------
    spot: int, float, default: 100
       Spot Price

    spot_range: int, float, optional, default: 20
       Range of spot variation in percentage

    op_list: list of dictionary

       Each dictionary must contiain following keys
       'strike': int, float, default: 720
           Strike Price
       'tr_type': kind {'b', 's'} default:'b'
          Transaction Type>> 'b': long, 's': short
       'op_pr': int, float, default: 10
          Option Price (required if 'dte' is not provided)
       'op_type': kind {'c','p'}, default:'c'
          Opion type>> 'c': call option, 'p':put option
       'contracts': int default:1, optional
           Number of contracts
       'dte': int, optional
           Days to expiration. If provided for one option, must be provided for all.
           When provided, uses Black-Scholes model to plot daily option values instead of payoff diagrams.
       'volatility': float, optional
           Implied volatility in percentage for this specific option leg.
           If not provided, uses the default_volatility parameter.

    save: Boolean, default False
        Save figure

    file: String, default: 'fig.png'
        Filename with extension

    risk_free_rate: float, default: 4.00
        Risk free rate in percentage

    default_volatility: float, default: 32.00
        Default volatility in percentage used for all option legs that don't specify their own volatility.
        Only used when 'dte' is provided for options.

    y_limiter: callable, optional, default: None
        A function that takes the combined payoff array (y) and returns a tuple (y_min, y_max) for setting ylim.
        If provided, will be called to set the y-axis limits. If None, matplotlib auto-scales the y-axis.
        Example: lambda y: (-max(abs(y)), max(abs(y))) for symmetric limits

    Example
    -------
    op1={'op_type':'c','strike':110,'tr_type':'s','op_pr':2,'contract':1}
    op2={'op_type':'p','strike':95,'tr_type':'s','op_pr':6,'contract':1}

    import superOpStrat  as op
    op.multi_plotter(spot_range=20, spot=100, op_list=[op1,op2])

    #Plots option payoff diagrams for each op1 and op2 and combined payoff

    Example with DTE and per-leg volatility
    ----------------------------------------
    op1={'op_type':'c','strike':110,'tr_type':'b','dte':30,'contract':1,'volatility':25.0}
    op2={'op_type':'p','strike':95,'tr_type':'b','dte':30,'contract':1,'volatility':28.5}

    op.multi_plotter(spot_range=20, spot=100, op_list=[op1,op2], default_volatility=32.00)

    #Plots option values using Black-Scholes with different volatilities for each leg

    """
    x=spot*np.arange(100-spot_range,101+spot_range,0.01)/100
    y0=np.zeros_like(x)

    # Check if DTE is provided and validate consistency
    has_dte = any('dte' in op for op in op_list)
    if has_dte:
        # If any option has DTE, all must have DTE
        if not all('dte' in op for op in op_list):
            raise ValueError("If 'dte' is provided for one option, it must be provided for all options")

    y_list=[]
    for op in op_list:
        op_type=str.lower(op['op_type'])
        tr_type=str.lower(op['tr_type'])
        check_optype(op_type)
        check_trtype(tr_type)

        strike=op['strike']
        try:
            contract=op['contract']
        except:
            contract=1

        if has_dte:
            # Use Black-Scholes to calculate daily option values
            dte=op['dte']
            # Use per-leg volatility if provided, otherwise use default
            volatility=op.get('volatility', default_volatility)
            y_values=[]
            for spot_price in x:
                # Call black_scholes with per-leg volatility
                bs_result=black_scholes(t=dte, r=risk_free_rate, v=volatility, K=strike, St=spot_price, type=op_type)
                option_value=bs_result['value']['option value']

                # Apply transaction type (short = negative)
                if tr_type=='s':
                    option_value=-option_value

                # Apply contract multiplier
                option_value=option_value*contract

                y_values.append(option_value)
            y_list.append(np.array(y_values))
        else:
            # Use original payoff calculator (requires op_pr)
            op_pr=op['op_pr']
            y_list.append(payoff_calculator(x, op_type, strike, op_pr, tr_type, contract))
    

    def plotter():
        y=0
        plt.figure(figsize=(10,6))
        for i in range (len(op_list)):
            try:
                contract=str(op_list[i]['contract'])  
            except:
                contract='1'
                
            label=contract+' '+str(abb[op_list[i]['tr_type']])+' '+str(abb[op_list[i]['op_type']])+' ST: '+str(op_list[i]['strike'])
            sns.lineplot(x=x, y=y_list[i], label=label, alpha=0.5)
            y+=np.array(y_list[i])
        
        sns.lineplot(x=x, y=y, label='combined', alpha=1, color='k')
        plt.axhline(color='k', linestyle='--')
        plt.axvline(x=spot, color='r', linestyle='--', label='spot price')
        plt.legend()
        plt.legend(loc='upper right')
        title="Multiple Options Plotter"
        plt.title(title)
        plt.fill_between(x, y, 0, alpha=0.2, where=y>y0, facecolor='green', interpolate=True)
        plt.fill_between(x, y, 0, alpha=0.2, where=y<y0, facecolor='red', interpolate=True)

        if y_limiter is not None:
            y_limits = y_limiter(y)
            plt.ylim(y_limits)

        plt.tight_layout()
        if save==True:
            plt.savefig(file)
        plt.show()

    plotter()      