from typing import Tuple, Dict

import pandas as pd
from pandas import DataFrame, Series


# def swap_in_out(data: DataFrame, is_mature: str, target: str, new_model_score: str, 
#                 old_risk_level: str = "risk_level", new_risk_level: str = "new_risk_level", 
#                 primary_key: str = "sn", 
#                 accept: str = "ACCEPT", review: str = "REVIEW", reject: str = "REJECT", 
#                 new_accept_rate: Optional[float] = None, new_reject_rate: Optional[float] = None
#                 ) -> Tuple[DataFrame, DataFrame]:
#     """计算换入换出

#     Args:
#         data (DataFrame): 进件订单数据
#         is_mature (str): 0-1 变量，放款订单是否成熟
#         target (str): 0-1 变量，成熟订单是否坏账
#         new_model_score (str): 新模型分字段
#         old_risk_level (str, optional): 当前风险水平字段. Defaults to "risk_level".
#         new_risk_level (str, optional): 新模型分的风险水平字段（会被新增的字段）. Defaults to "new_risk_level".
#         primary_key (str, optional): 用以唯一确定一笔订单. Defaults to "sn".
#         accept (str, optional): 模型通过. Defaults to "ACCEPT".
#         review (str, optional): 走人审. Defaults to "REVIEW".
#         reject (str, optional): 模型拒绝. Defaults to "REJECT".
#         new_accept_rate (Optional[float], optional): 规定的新模型通过占比，为空则表示保持通过占比不变. Defaults to None.
#         new_reject_rate (Optional[float], optional): 规定的新模型拒绝占比，为空则表示保持拒绝占比不变. Defaults to None.

#     Returns:
#         Tuple[DataFrame, DataFrame]: (进件订单的换入换出, 换入换出的风险情况)
#     """
#     num: int = len(data)
#     old_accept_rate: float = len(data[data[old_risk_level]==accept]) / len(data)
#     old_reject_rate: float = len(data[data[old_risk_level]==reject]) / len(data)
#     print(f"当前模型通过的占比为：{old_accept_rate:.2%}，拒绝的占比为：{old_reject_rate:.2%}")
#     if new_accept_rate is None:
#         new_accept_rate = old_accept_rate
#         print("保持模型通过的比例不变")
#     if new_reject_rate is None:
#         new_reject_rate = old_reject_rate
#         print("保持模型拒绝的比例不变")
#     new_model_accept_score: float = data[new_model_score].quantile(q=1-new_accept_rate)
#     new_model_reject_score: float = data[new_model_score].quantile(q=new_reject_rate)
#     print(f"新模型通过比例定为：{new_accept_rate:.2%}，新模型通过的分数为：{new_model_accept_score:.2f}；新模型拒绝比例定为：{new_reject_rate:.2%}，新模型拒绝的分数为：{new_model_reject_score:.2f}")

#     def tag_risk_level(model_score: float, model_accept_score: float, model_reject_score: float) -> str:
#         if model_score <= model_reject_score:
#             return reject
#         elif model_score >= model_accept_score:
#             return accept
#         else:
#             return review
    
#     data[new_risk_level] = data[new_model_score].apply(func=lambda x: tag_risk_level(model_score=x, model_accept_score=new_model_accept_score, model_reject_score=new_model_reject_score))

#     def verify_pct(series: Series) -> float:
#         """用于计算交叉部分占总体的比例"""
#         return len(series) / num
    
#     def bad_rate(series: Series) -> float:
#         """用于计算交叉部分的风险情况"""
#         return series.mean()
    
#     verify_swap: DataFrame = pd.pivot_table(
#         data=data,
#         columns=[old_risk_level],
#         index=[new_risk_level],
#         values=[primary_key],
#         aggfunc={primary_key: ["count", verify_pct]} # type: ignore
#     )
    
#     risk_swap: DataFrame = pd.pivot_table(
#         data=data[data[is_mature] == 1], # 成熟订单
#         columns=[old_risk_level],
#         index=[new_risk_level],
#         values=[primary_key, target],
#         aggfunc={primary_key: ["count"], target: [bad_rate]} # type: ignore
#     )

#     return verify_swap, risk_swap

# def model_cross(data: DataFrame, is_mature: str, target: str, model_score1: str, model_score2: str, 
#                 primary_key: str = "sn", model_score1_bins: int = 10, model_score2_bins: int = 10
#                 ) -> Tuple[DataFrame, DataFrame]:
#     """计算两个模型分的交叉情况

#     Args:
#         data (DataFrame): 进件订单数据
#         is_mature (str): 0-1 变量，放款订单是否成熟
#         target (str): 0-1 变量，成熟订单是否坏账
#         model_score1 (str): 模型分 1
#         model_score2 (str): 模型分 2
#         primary_key (str, optional): 用以唯一确定一笔订单. Defaults to "sn".
#         model_score1_bins (int, optional): 模型分 1 的等频分箱数. Defaults to 10.
#         model_score2_bins (int, optional): 模型分 2 的等频分箱数. Defaults to 10.

#     Returns:
#         Tuple[DataFrame, DataFrame]: (两个模型分交叉部分的进件情况, 两个模型分交叉部分的风险情况)
#     """
#     num: int = len(data)

#     # Combiner 对象为不同的列指定不同的分箱规则
#     # combiner: Combiner = Combiner()
#     # combiner.fit(X=data[model_score1], y=data[target], method="quantile", n_bins=model_score1_bins, empty_separate=False)
#     # combiner.fit(X=data[model_score2], y=data[target], method="quantile", n_bins=model_score2_bins, empty_separate=False)
#     # data[f"{model_score1}_bin"] = combiner.transform(X=data[model_score1], labels=True)
#     # data[f"{model_score2}_bin"] = combiner.transform(X=data[model_score2], labels=True)
#     data[f"{model_score1}_bin"] = pd.qcut(x=data[model_score1], q=model_score1_bins, duplicates="drop")
#     data[f"{model_score2}_bin"] = pd.qcut(x=data[model_score2], q=model_score2_bins, duplicates="drop")

#     def verify_pct(series: Series) -> float:
#         """用于计算交叉部分占总体的比例"""
#         return len(series) / num
    
#     def bad_rate(series: Series) -> float:
#         """用于计算交叉部分的风险情况"""
#         return series.mean()
    
#     verify_cross: DataFrame = pd.pivot_table(
#         data=data,
#         columns=[f"{model_score1}_bin"],
#         index=[f"{model_score2}_bin"],
#         values=[primary_key],
#         aggfunc={primary_key: ["count", verify_pct]} # type: ignore
#     )

#     risk_cross: DataFrame = pd.pivot_table(
#         data=data[data[is_mature] == 1], # 成熟订单
#         columns=[f"{model_score1}_bin"],
#         index=[f"{model_score2}_bin"],
#         values=[primary_key, target],
#         aggfunc={primary_key: ["count"], target: [bad_rate]} # type: ignore
#     )

#     return verify_cross, risk_cross

def save_div(dividend: float, divisor: float, /) -> float:
    """安全除，避免除零错误

    Args:
        dividend (float): 被除数
        divisor (float): 除数

    Returns:
        float: 安全除
    """
    if divisor == 0:
        return 0
    return dividend / divisor

def calculate_cutoff_line(data: DataFrame, model_score: str, risk_level: str = "risk_level", accept: str = "ACCEPT", reject: str = "REJECT") -> Dict[str, Tuple[float, float]]:
    """计算新模型准入的上下 cutoff 线

    Args:
        data (DataFrame): 仅模型决策的进件订单（含其他参数字段）
        model_score (str): 模型分字段
        risk_level (str, optional): 当前风险评级字段. Defaults to "risk_level".
        accept (str, optional): 当前风险评级表接受的值. Defaults to "ACCEPT".
        reject (str, optional): 当前风险评级表拒绝的值. Defaults to "REJECT".

    Returns:
        Dict[str, Tuple[float, float]]: 新模型准入的上下 cutoff 线
    """
    accept_ratio: float = len(data[data[risk_level]==accept]) / len(data)
    reject_ratio: float = len(data[data[risk_level]==reject]) / len(data)
    accept_cutoff_line: float = data[model_score].quantile(q=1 - accept_ratio)
    reject_cutoff_line: float = data[model_score].quantile(q=reject_ratio)
    return {
        "上 cutoff 线": (accept_ratio, float(accept_cutoff_line)),
        "下 cutoff 线": (reject_ratio, float(reject_cutoff_line))
    }

def swap_in_out_proportion(data: DataFrame, model_score: str, accept_score: float, reject_score: float, new_risk_level: str = "new_risk_level", old_risk_level: str = "risk_level", primary_key: str = "sn") -> DataFrame:
    """计算换入换出比例

    Args:
        data (DataFrame): 仅模型决策的进件订单（含其他参数字段）
        model_score (str): 模型分字段
        accept_score (float): 上 cutoff 线
        reject_score (float): 下 cutoff 线
        new_risk_level (str, optional): 新模型分风险评级字段. Defaults to "new_risk_level".
        old_risk_level (str, optional): 当前风险评级字段. Defaults to "risk_level".
        primary_key (str, optional): 订单主键. Defaults to "sn".

    Returns:
        DataFrame: 换入换出比例
    """
    def tag_risk_level(model_score: float) -> str:
        if model_score <= reject_score:
            return "REJECT"
        elif model_score >= accept_score:
            return "ACCEPT"
        else:
            return "REVIEW"
    
    def verify_pct(primary_key: Series) -> float:
        """用于计算交叉部分占总体的比例"""
        return len(primary_key) / len(data)
    
    data[new_risk_level] = data[model_score].apply(func=tag_risk_level)

    verify_swap: DataFrame = pd.pivot_table(
        data=data,
        columns=[old_risk_level],
        index=[new_risk_level],
        values=[primary_key],
        aggfunc={primary_key: ["count", verify_pct]} # type: ignore
    )
    
    return verify_swap

def swap_in_out_risk(data: DataFrame, 
                     model_score: str, 
                     accept_score: float, 
                     reject_score: float, 
                     new_risk_level: str = "new_risk_level", 
                     old_risk_level: str = "risk_level", 
                     target: str = "target", 
                     primary_key: str = "borrow_id") -> DataFrame:
    """计算换入换出的风险

    Args:
        data (DataFrame): 贷后表现订单（含其他参数字段）
        model_score (str): 模型分字段
        accept_score (float): 上 cutoff 线
        reject_score (float): 下 cutoff 线
        new_risk_level (str, optional): 新模型分风险评级字段. Defaults to "new_risk_level".
        old_risk_level (str, optional): 当前风险评级字段. Defaults to "risk_level".
        target (str, optional): 标签字段. Defaults to "target".
        primary_key (str, optional): 订单主键. Defaults to "borrow_id".

    Returns:
        DataFrame: 换入换出风险
    """
    def tag_risk_level(model_score: float) -> str:
        if model_score <= reject_score:
            return "REJECT"
        elif model_score >= accept_score:
            return "ACCEPT"
        else:
            return "REVIEW"

    def bad_rate(target: Series) -> float:
        """坏账率

        Args:
            target (Series): 0-1 变量

        Returns:
            float: 坏账率
        """
        return target.mean()

    data[new_risk_level] = data[model_score].apply(func=tag_risk_level)

    risk_swap: DataFrame = pd.pivot_table(
        data=data[data[target].isin(values=(0, 1))],
        columns=[old_risk_level],
        index=[new_risk_level],
        values=[primary_key, target],
        aggfunc={primary_key: ["count"], target: [bad_rate]} # type: ignore
    )

    return risk_swap
