#!/user/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Galaxy Technologies
Licensed under the Apache License, Version 2.0
"""
from pricelib.common.utilities.enums import CallPut, InOut, UpDown, ExerciseType
from pricelib.common.pricing_engine_base import AnalyticEngine
from pricelib.common.date_handler import global_evaluation_date
from ...products.barrier.barrier_option import BarrierOption
from .analytic_barrier_engine import AnalyticBarrierEngine


class AnalyticAirbagEngine(AnalyticEngine):
    """安全气囊闭式解定价引擎
    安全气囊是美式观察向下敲入期权，敲入前是call或价差，敲入后payoff与持有标的资产涨跌幅相同(在参与率为1的情况下)
    相当于向下敲出看涨期权 + 向下触碰资产或无期权 的组合：
    1. 如果使用向下触碰资产或无期权，敲入的下跌参与率与重置后看涨参与率必须相等，因此受到限制，弃之不用
    2. 如果敲入的下跌参与率与重置后看涨参与率不相同，就需要使用执行价=期初价格的，一个向下敲入call和一个向下敲入put再组合起来
    3. 如果还存在敲入前封顶、敲入后封顶、敲入后封底，则需要再分别减去一个障碍期权，组合为带障碍的价差。
    """

    # pylint: disable=invalid-name
    def calc_present_value(self, prod, t=None, spot=None):
        """计算现值
        Args:
            prod: Product产品对象
            t: datetime.date，估值日; 如果是None，则使用全局估值日globalEvaluationDate
            spot: float，估值日标的价格，如果是None，则使用随机过程的当前价格
        Returns: float，现值
        """
        if spot is None:
            spot = self.process.spot()
        # 1. 安全气囊敲入前，payoff为向下敲出看涨期权的多头
        DOC = BarrierOption(strike=prod.strike, barrier=prod.barrier, inout=InOut.Out,
                            updown=UpDown.Down, callput=CallPut.Call, obs_type=ExerciseType.American,
                            start_date=prod.start_date, end_date=prod.end_date,
                            discrete_obs_interval=prod.discrete_obs_interval, rebate=0, floor_yield=0.0,
                            engine=AnalyticBarrierEngine(self.process))
        not_in_value = DOC.price(t=t, spot=spot)
        # 1*. 如果敲入前含有敲入前封顶价格，则敲入前的结构变为牛市价差，此时需要减去一个以敲入前封顶价格为执行价的向下敲出看涨期权
        if prod.init_strike_cap is not None:
            DOC2 = BarrierOption(strike=prod.init_strike_cap, barrier=prod.barrier, inout=InOut.Out,
                                 updown=UpDown.Down, callput=CallPut.Call, obs_type=ExerciseType.American,
                                 start_date=prod.start_date, end_date=prod.end_date,
                                 discrete_obs_interval=prod.discrete_obs_interval, rebate=0, floor_yield=0.0,
                                 engine=AnalyticBarrierEngine(self.process))
            not_in_value -= DOC2.price(t=t, spot=spot)
        # 2. 若已敲入，且标的价格上涨，这一部分payoff为向下敲入看涨期权的多头
        DIC = BarrierOption(strike=prod.s0, barrier=prod.barrier, inout=InOut.In, updown=UpDown.Down,
                            callput=CallPut.Call, obs_type=ExerciseType.American,
                            start_date=prod.start_date, end_date=prod.end_date,
                            discrete_obs_interval=prod.discrete_obs_interval, rebate=0, floor_yield=0.0,
                            engine=AnalyticBarrierEngine(self.process))
        in_upper_value = DIC.price(t=t, spot=spot)
        # 2*. 如果敲入后含有敲入后封顶价格，则敲入后的结构变为牛市价差，此时需要减去一个以敲入后封顶价格为执行价的向下敲入看涨期权
        if prod.in_strike_cap is not None:
            DIC2 = BarrierOption(strike=prod.in_strike_cap, barrier=prod.barrier, inout=InOut.In, updown=UpDown.Down,
                                 callput=CallPut.Call, obs_type=ExerciseType.American,
                                 start_date=prod.start_date, end_date=prod.end_date,
                                 discrete_obs_interval=prod.discrete_obs_interval, rebate=0, floor_yield=0.0,
                                 engine=AnalyticBarrierEngine(self.process))
            in_upper_value -= DIC2.price(t=t, spot=spot)
        # 3. 若已敲入，且标的价格下跌，这一部分payoff为向下敲入看跌期权的空头
        DIP = BarrierOption(strike=prod.s0, barrier=prod.barrier, inout=InOut.In, updown=UpDown.Down,
                            callput=CallPut.Put, obs_type=ExerciseType.American,
                            start_date=prod.start_date, end_date=prod.end_date,
                            discrete_obs_interval=prod.discrete_obs_interval, rebate=0, floor_yield=0.0,
                            engine=AnalyticBarrierEngine(self.process))
        in_lower_value = -DIP.price(t=t, spot=spot)
        # 2*. 如果敲入后含有敲入后封底价格，则敲入后的结构变为熊市价差，此时需要加上一个以敲入后封底价格为执行价的向下敲入看跌期权
        if prod.in_strike_floor is not None:
            DIP2 = BarrierOption(strike=prod.in_strike_floor, barrier=prod.barrier, inout=InOut.In, updown=UpDown.Down,
                                 callput=CallPut.Put, obs_type=ExerciseType.American,
                                 start_date=prod.start_date, end_date=prod.end_date,
                                 discrete_obs_interval=prod.discrete_obs_interval, rebate=0, floor_yield=0.0,
                                 engine=AnalyticBarrierEngine(self.process))
            in_lower_value += DIP2.price(t=t, spot=spot)
        # 4. 组合为安全气囊的价值
        value = (not_in_value * prod.call_parti  # 未敲入的看涨参与率
                 + in_upper_value * prod.reset_call_parti  # 敲入的重置后看涨参与率
                 + in_lower_value * prod.knockin_parti)  # 敲入的下跌参与率
        if prod.floor_yield != 0:  # 保底收益率
            calculate_date = global_evaluation_date() if t is None else t
            _maturity = (prod.end_date - calculate_date).days / prod.annual_days.value
            _tenor = (prod.end_date - prod.start_date).days / prod.annual_days.value  # 从起始日到终止日的自然日时间（年）
            value += prod.floor_yield * prod.s0 * _tenor * self.process.interest.disc_factor(_maturity)
        return value
