#!/user/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Galaxy Technologies
Licensed under the Apache License, Version 2.0
"""
import datetime
from contextlib import suppress
import numpy as np
from pricelib.common.utilities.enums import (CallPut, InOut, UpDown, BarrierType, PaymentType, EngineType, StatusType,
                                             ExerciseType)
from pricelib.common.date_handler import CN_CALENDAR, AnnualDays, global_evaluation_date
from pricelib.common.utilities.patterns import Observer, global_auto_update_status
from pricelib.common.utilities.utility import time_this, logging
from pricelib.common.product_base.option_base import OptionBase
from pricelib.pricing_engines.analytic_engines import AnalyticBarrierEngine
from pricelib.pricing_engines.fdm_engines import FdmVanillaEngine
from pricelib.products.vanilla.vanilla_option import VanillaOption
from pricelib.products.barrier.barrier_option import BarrierOption


class SingleShark(BarrierOption):
    """单向鲨鱼鳍，含封底价格"""

    def __init__(self, strike, barrier, rebate, callput=CallPut.Call, updown=UpDown.Up, *, parti=1,
                 obs_type=ExerciseType.American, payment_type=None, rebate_annual=False, floor_yield=0.0,
                 maturity=None, start_date=None, end_date=None, discrete_obs_interval=None, status=StatusType.NoTouch,
                 trade_calendar=CN_CALENDAR, annual_days=AnnualDays.N365, t_step_per_year=243,
                 engine=None, stoch_process=None, s0=None, s=None, r=None, q=None, vol=None, engine_method=None):
        """构造函数
        产品参数:
            strike: float, 执行价
            barrier: float, 障碍价
            rebate: float, 敲出现金返还金额，或未敲入现金返还金额，绝对数值，非百分比，非年化
            rebate_annual: bool, 现金返还是否为年化，默认为False，即绝对数值，非年化，非百分比；若为True，则为百分比年化收益率
            callput: 看涨看跌类型，CallPut枚举类，Call看涨/Put看跌
            inout: 敲入敲出类型，InOut枚举类，In敲入/Out敲出
            updown: 向上向下类型，UpDown枚举类，Up向上/Down向下
            payment_type: 现金返还支付类型，PaymentType枚举类，敲入默认为到期支付Expire；敲出默认为立即支付Hit
            parti: float, 香草期权的参与率，默认为1
            floor_yield: float, 封底收益(保底收益)，当未敲出情况下，约定的最低收益率，默认为0，绝对数值，非百分比，非年化。
                          即未敲出收益支付 = 封底收益 + 参与率 * max[(期末标的价-行权价格), 0]
            status: 估值日前的敲入敲出状态，StatusType枚举类，默认为NoTouch未敲入/未敲出，UpTouch为向上敲入/敲出，DownTouch为向下敲入/敲出
        时间参数: 要么输入年化期限，要么输入起始日和到期日
            maturity: float，年化期限
            start_date: datetime.date，起始日
            end_date: datetime.date，到期日
            trade_calendar: 交易日历，Calendar类，默认为中国内地交易日历
            annual_days: int，每年的自然日数量
            t_step_per_year: int，每年的交易日数量
            discrete_obs_interval: 观察时间间隔. 若为连续观察或到期观察，None；若为均匀离散观察，为年化的观察时间间隔(float)。
        可选参数:
            s0: float，标的期初价格，只有当 rebate_annual = True，即现金返还为年化收益率时；
                                  或者当 floor_yield != 0，即存在非零的保底收益率时，
                       需要输入标的期初价格，将百分比的收益率转化成绝对数值
            engine: 定价引擎，PricingEngine类，以下几种引擎均支持向上/向下、敲入/敲出、看涨/看跌8种单边障碍期权，支持敲出/未敲入现金返还
                            对于美式障碍，解析解和PDE引擎支持连续观察，其余引擎只支持离散观察(默认为每日观察)
                    解析解: AnalyticBarrierEngine 支持欧式障碍（到期观察）和美式障碍（支持连续观察/离散观察）
                                        欧式障碍：到期观察，支持保底收益率，支持支持现金返还为绝对数值/年化百分比
                                        美式障碍：敲入现金返还为到期支付；敲出现金返还为立即支付。现金返还为固定绝对数值，不支持年化的现金返还。
                                                不支持同时输入非零的现金返还和不等于1的参与率，二者只有其一时支持。不支持保底收益率
                    蒙特卡洛: MCBarrierEngine 支持欧式障碍（到期观察）和美式障碍（仅支持离散观察）
                                        欧式障碍：到期观察，支持保底收益率，支持支持现金返还为绝对数值/年化百分比
                                        美式障碍 ：敲入现金返还为到期支付；敲出现金返还 支持 立即支付/到期支付
                                                支持现金返还为绝对数值/年化百分比，支持保底收益率
                    PDE: FdmBarrierEngine  支持欧式障碍（到期观察）和美式障碍（支持连续观察/离散观察）
                                        欧式障碍：到期观察，支持保底收益率，支持支持现金返还为绝对数值/年化百分比
                                        美式障碍 ：敲入现金返还为到期支付；敲出现金返还 支持 立即支付/到期支付
                                                支持现金返回为绝对数值/年化百分比，支持保底收益率
                    积分法: QuadBarrierEngine  仅支持离散观察的美式障碍，todo: 支持欧式障碍、年化的现金返还、保底收益率
                                            敲入现金返还为到期支付；敲出现金返还 支持 立即支付/到期支付
                    二叉树: BiTreeBarrierEngine  仅支持离散观察的美式障碍，todo: 支持欧式障碍、年化的现金返还、保底收益率
                                            敲入现金返还为到期支付；敲出现金返还 支持 立即支付/到期支付
            engine_method: str, 定价引擎种类。若未输入engine，可以用engine_method快捷生成指定定价引擎。可选Analytic、MC/MonteCarlo、PDE/FDM、QUAD、TREE。
            若未提供引擎engine、也未指定引擎种类engine_method的情况下，如果
            若未提供引擎的情况下，提供了 随机过程 或 标的价格、无风险利率、分红/融券率、波动率，
            则默认使用解析解定价引擎 AnalyticBarrierEngine - Merton(1973), Reiner & Rubinstein(1991a)
                                支持 Broadie, Glasserman和Kou(1995)均匀离散观察调整
            stoch_process: 随机过程StochProcessBase对象
            s: float，标的价格
            r: float，无风险利率
            q: float，分红/融券率
            vol: float，波动率
        """
        self._inout = InOut.Out
        # 连续观察/到期观察，discrete_obs_interval=None; 均匀离散观察=观察时间间隔; 每日观察自动转为1/243
        discrete_obs_interval = 1 / 243 if discrete_obs_interval == "每日观察" else discrete_obs_interval
        super().__init__(inout=InOut.Out, discrete_obs_interval=discrete_obs_interval, obs_type=obs_type,
                         strike=strike, barrier=barrier, callput=callput, updown=updown, parti=parti,
                         rebate=rebate, rebate_annual=rebate_annual, floor_yield=floor_yield,
                         payment_type=payment_type, status=status, maturity=maturity, start_date=start_date,
                         end_date=end_date, trade_calendar=trade_calendar, annual_days=annual_days,
                         t_step_per_year=t_step_per_year, engine=engine, stoch_process=stoch_process,
                         s0=s0, s=s, r=r, q=q, vol=vol, engine_method=engine_method)

    def __repr__(self):
        """返回期权的描述"""
        if self.obs_type == ExerciseType.European:
            obs_type = "到期观察"
        elif self.obs_type == ExerciseType.American:
            if self.discrete_obs_interval is None:
                obs_type = "连续观察"
            elif self.discrete_obs_interval == 1 / 243:
                obs_type = "每日观察"
            else:
                obs_type = "离散观察"
        else:
            raise ValueError("obs_type should be European or American")
        return f"{obs_type}{self._callput.name}单向鲨鱼鳍期权"

    @time_this
    def price(self, t: datetime.date = None, spot=None):
        """计算期权价格
        Args:
            t: datetime.date，计算期权价格的日期
            spot: float，标的价格
        Returns: 期权现值
        """
        self.validate_parameters(t=t)
        if self.status != StatusType.NoTouch:  # 如果已经触碰障碍，则敲出价值=现金返还的现值
            calculate_date = global_evaluation_date() if t is None else t
            if self.payment_type == PaymentType.Hit:  # 立即支付
                if self.rebate_annual:  # 年化百分比
                    elapsed_time = (calculate_date - self.start_date).days / self.annual_days.value
                    _rebate = [r * self.s0 * elapsed_time for r in self.rebate]
                else:  # 绝对数值
                    _rebate = self.rebate
                return _rebate
            elif self.payment_type == PaymentType.Expire:  # 到期支付
                if self.rebate_annual:  # 年化百分比
                    tenor = (self.end_date - self.start_date).days / self.annual_days.value  # 从起始日到终止日的自然日时间（年）
                    _rebate = [r * self.s0 * tenor for r in self.rebate]
                else:  # 绝对数值
                    _rebate = self.rebate
                _maturity = (self.end_date - calculate_date).days / self.annual_days.value
                return self.rebate * self.engine.process.interest.disc_factor(_maturity)
            else:
                raise ValueError(f"未知的支付方式{self.payment_type}")
        else:  # 尚未触碰障碍，正常定价
            if self.engine.engine_type == EngineType.PdeEngine:  # 接口特殊是因为PDE引擎兼容了单双边障碍，所以需要把barrier填入bound
                if self.updown == UpDown.Up:
                    bound = (None, self.barrier)
                else:
                    bound = (self.barrier, None)
                price = self.engine.calc_present_value(prod=self, t=t, spot=spot, bound=bound,
                                                       rebate=(self.rebate, self.rebate))
            else:
                price = self.engine.calc_present_value(prod=self, t=t, spot=spot)
        return price
