"""A gradio app for credit card approval prediction using FHE."""

import subprocess
import time
import gradio as gr

from settings import (
    REPO_DIR,
    ACCOUNT_MIN_MAX,
    CHILDREN_MIN_MAX,
    INCOME_MIN_MAX,
    AGE_MIN_MAX,
    FAMILY_MIN_MAX,
    INCOME_TYPES,
    OCCUPATION_TYPES,
    HOUSING_TYPES,
    EDUCATION_TYPES,
    FAMILY_STATUS,
    YEARS_EMPLOYED_BINS,
    INCOME_VALUE,
    AGE_VALUE,
)
from backend import (
    keygen_send,
    pre_process_encrypt_send_applicant,
    pre_process_encrypt_send_bank,
    pre_process_encrypt_send_credit_bureau,
    run_fhe,
    get_output_and_decrypt,
    explain_encrypt_run_decrypt,
)


subprocess.Popen(["uvicorn", "server:app"], cwd=REPO_DIR)
time.sleep(3)


demo = gr.Blocks()


print("Starting the demo...")
with demo:

    gr.Markdown(
        """
            <p align="center">
                <img width=200 src="https://www.yamu.com/uploads/image/20220602/1d3eb99b96d3a84ef37eda59989e5e2f.png">
            </p>
        """
    )
    gr.Markdown(
        """
        <h1 align="center">使用全同态加密的信用卡审批预测</h1>        
        """
    )

    with gr.Accordion("什么是信用卡的审批评估分?", open=False):
        gr.Markdown(
            """
            这是一个复杂的过程,涉及多个实体:申请人、银行、信用中心和信用评分机构。
            当您申请信用卡时,您会向银行提供个人和财务信息。这可能包括您的收入、就业状况和现有债务。银行使用这些信息来评估您的信用度。
            为此,他们经常求助于信用中心和信用评分机构。
            - 信用中心收集并保存有关消费者信用历史的数据。这些数据包括您过去和当前的债务以及您的信用历史长度。
            - 信用评分机构使用算法来分析来自信用中心的数据并生成信用评分。 该分数是您信用度的数字表示。 
            - 银行使用您的信用评分以及您提供的信息来对您的信用卡申请做出决定。更高的信用评分通常会增加您获得批准的机会,并可能带来更好的条款(例如更低的利率)。            
            """
        )

    with gr.Accordion("为什么在此过程中添加新的隐私计算处理层至关重要?", open=False):
        gr.Markdown(
            """
            因为所涉及的数据高度敏感。它包括个人信息,如您的身份证号码号码、收入和信用记录。
            不同实体之间有大量数据共享。您的信息不仅掌握在银行手中,还掌握在信用中心和评分机构手中。有权访问您数据的实体越多,数据泄露的风险就越大。这可能导致身份盗窃和金融欺诈。还有数据准确性的问题。
            信用报告中的错误可能导致不公平的低信用评分,影响您获得信贷的能力。            
            """
        )

    with gr.Accordion(
        "为什么全同态加密(FHE)是提升信用评分能力的解决方案?", 
        open=False,
    ):
        gr.Markdown(
            """
            全同态加密 (FHE) 被视为增强涉及申请人、银行、信用中心和信用评分机构等多方的信用评分流程的隐私和准确性的理想解决方案。
            它允许对数据进行加密和处理,而无需解密。这意味着可以共享和分析敏感数据,而无需将实际信息暴露给任何一方或处理它的服务器。
            在信用评分的背景下,这将使对个人信誉的评估更加彻底和准确。可以组合和分析来自各种来源的数据以做出更明智的决策,但每一方的数据仍保持机密。
            因此,数据泄露或泄露的风险被大大降低,解决了主要的隐私问题。            

            总而言之,FHE 提供了一种做出更准确的信贷资格决策的方法,同时保持严格的数据隐私,为数据实用性和机密性之间的微妙平衡提供了一个复杂的解决方案。            
            """
        )

    gr.Markdown(
        """
        <p align="center">
            <img src="https://fheprc.oss-cn-hangzhou.aliyuncs.com/credit.png"
        </p>
        """
    )

    gr.Markdown("## 步骤1:创建密钥。")
    gr.Markdown("<hr />")
    gr.Markdown("<span style='color:grey'>申请人、银行和信贷机构设置</span>")
    gr.Markdown(
        """
        - 私钥由合作计算信用评分的实体共同生成。它用于加密和解密数据,绝不会与任何其他方共享。
        - 计算密钥(evaluation key)是服务器处理加密数据所需的公钥。因此,它也被传输到服务器进行进一步处理。
        """
    )
    keygen_button = gr.Button("生成密钥并将计算密钥发送到服务器。")
    evaluation_key = gr.Textbox(
        label="计算密钥:", max_lines=2, interactive=False
    )
    client_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
    
    # Button generate the keys
    keygen_button.click(
        keygen_send,
        outputs=[client_id, evaluation_key, keygen_button],
    )

    gr.Markdown("## 步骤2:填写申请信息。")
    gr.Markdown("<hr />")
    gr.Markdown("<span style='color:grey'>申请人、银行和信贷机构设置</span>")
    gr.Markdown(
        """
        选择与您要评估的个人资料相对应的信息。此模型使用了三种信息来源:
        - 申请人的个人信息,以评估其信用卡资格;
        - 申请人的银行账户历史记录,其中提供了与该决定相关的任何类型的申请人银行信息(此处,我们考虑开户时间);
        - 以及信用中心信息,这些信息代表了任何其他信息(这里指就业历史),这些信息可以提供与决策相关的额外见解。
        
        在运行FHE推理之前,请务必在更新后加密并发送值(通过右侧的按钮)。
        """
    )

    with gr.Row():
        with gr.Column():
            gr.Markdown("### 步骤2.1 - 申请人信息 🧑‍💼")
            bool_inputs = gr.CheckboxGroup(
                ["汽车", "房产", "手机"], 
                label="您目前持有或拥有以下哪些资产?"
            )
            num_children = gr.Slider(
                **CHILDREN_MIN_MAX, 
                step=1, 
                label="孩子数量", 
                info="您有几个小孩子?"
            )
            household_size = gr.Slider(
                **FAMILY_MIN_MAX, 
                step=1, 
                label="家庭规模", 
                info="您家里有几口人?"
            )
            total_income = gr.Slider(
                **INCOME_MIN_MAX,
                value=INCOME_VALUE,
                label="收入", 
                info="您的年总收入是多少?"
            )
            age = gr.Slider(
                **AGE_MIN_MAX,
                value=AGE_VALUE, 
                step=1, 
                label="年龄", 
                info="今年多大了?"
            )

        with gr.Column():
            income_type = gr.Dropdown(
                choices=INCOME_TYPES, 
                value=INCOME_TYPES[0], 
                label="收入类型", 
                info="您的主要收入来源是什么?"
            )
            education_type = gr.Dropdown(
                choices=EDUCATION_TYPES, 
                value=EDUCATION_TYPES[0], 
                label="教育程度", 
                info="您的教育背景是什么?"
            )
            family_status = gr.Dropdown(
                choices=FAMILY_STATUS, 
                value=FAMILY_STATUS[0], 
                label="家庭", 
                info="你的家庭状况如何?"
            )
            occupation_type = gr.Dropdown(
                choices=OCCUPATION_TYPES, 
                value=OCCUPATION_TYPES[0], 
                label="职业", 
                info="你的主要职业是什么?"
            )
            housing_type = gr.Dropdown(
                choices=HOUSING_TYPES, 
                value=HOUSING_TYPES[0], 
                label="住房", 
                info="你住在什么类型的房子?"
            )

    with gr.Row():
        with gr.Column(scale=2):
            encrypt_button_applicant = gr.Button("加密输入并发送到服务器。")
            
            encrypted_input_applicant = gr.Textbox(
                label="加密后的输入:", max_lines=2, interactive=False
            )

    gr.Markdown("<hr />")
    with gr.Column():
        gr.Markdown("### 步骤2.2-银行信息 🏦")
        account_age = gr.Slider(
            **ACCOUNT_MIN_MAX, 
            step=1, 
            label="账户年龄(月)", 
            info="此人拥有该银行账户多长时间了(以月为单位)?"
        )

    with gr.Row():
        with gr.Column(scale=2):
            encrypt_button_bank = gr.Button("加密输入并发送到服务器。")

            encrypted_input_bank = gr.Textbox(
                label="加密后的输入:", max_lines=2, interactive=False
            )

    gr.Markdown("<hr />")
    with gr.Column():
        gr.Markdown("### 步骤2.3 - 信用中心信息 🏢")
        employed = gr.Radio(["是", "否"], label="是否在职?", value="是")
        years_employed = gr.Dropdown(
            choices=YEARS_EMPLOYED_BINS, 
            value=YEARS_EMPLOYED_BINS[0], 
            label="工作年限", 
            info="该员工已工作多长时间(年数)?"
        )

    with gr.Row():
        with gr.Column(scale=2):
            encrypt_button_credit_bureau = gr.Button("加密输入并发送到服务器。")

            encrypted_input_credit_bureau = gr.Textbox(
                label="加密后的输入:", max_lines=2, interactive=False
            )

    # Button to pre-process, generate the key, encrypt and send the applicant inputs from the client 
    # side to the server
    encrypt_button_applicant.click(
        pre_process_encrypt_send_applicant,
        inputs=[client_id, bool_inputs, num_children, household_size, total_income, age, \
                income_type, education_type, family_status, occupation_type, housing_type],
        outputs=[encrypted_input_applicant, encrypt_button_applicant],
    )

    # Button to pre-process, generate the key, encrypt and send the bank inputs from the client 
    # side to the server
    encrypt_button_bank.click(
        pre_process_encrypt_send_bank,
        inputs=[client_id, account_age],
        outputs=[encrypted_input_bank, encrypt_button_bank],
    )

    # Button to pre-process, generate the key, encrypt and send the credit bureau inputs from the 
    # client side to the server    
    encrypt_button_credit_bureau.click(
        pre_process_encrypt_send_credit_bureau,
        inputs=[client_id, years_employed, employed],
        outputs=[encrypted_input_credit_bureau, encrypt_button_credit_bureau],
    )

    gr.Markdown("## 步骤 3:运行FHE计算。")
    gr.Markdown("<hr />")
    gr.Markdown("<span style='color:grey'>服务端</span>")
    gr.Markdown(
        """
        一旦服务器收到加密的输入,它就可以计算预测,而无需解密任何值。

        此服务器采用已在合成数据集上训练过的[决策树](https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html)分类器模型。
        """
    )

    execute_fhe_button = gr.Button("运行FHE计算。")
    fhe_execution_time = gr.Textbox(
        label="FHE总执行时间(秒):", max_lines=1, interactive=False
    )

    # Button to send the encodings to the server using post method
    execute_fhe_button.click(run_fhe, inputs=[client_id], outputs=[fhe_execution_time, execute_fhe_button])

    gr.Markdown("## 步骤4:接收来自服务器的加密输出并解密。")
    gr.Markdown("<hr />")
    gr.Markdown("<span style='color:grey'>申请人、银行和信贷机构解密</span>")
    gr.Markdown(
        """
        一旦服务器完成推理,加密输出将返回给申请人。

        提供信息来计算信用评分的三个实体是唯一可以解密结果的实体。他们参与解密协议,该协议允许只有当所有三方都解密其份额的结果时才能解密完整结果。
        """
    )
    gr.Markdown(
        """
        下面显示的第一个值是实际加密输出的缩短字节表示。
        然后,申请人可以使用其私钥解密该值。        
        """
    )

    get_output_button = gr.Button("接收来自服务器的加密输出。")
    encrypted_output_representation = gr.Textbox(
        label="加密输出表示: ", max_lines=2, interactive=False
    )
    prediction_output = gr.Textbox(
        label="预测", max_lines=1, interactive=False
    )

    # Button to send the encodings to the server using post method
    get_output_button.click(
        get_output_and_decrypt, 
        inputs=[client_id], 
        outputs=[prediction_output, encrypted_output_representation, get_output_button],
    )

    gr.Markdown("## 第5步:解释预测(仅当您的信用卡可能被拒绝时)。")
    gr.Markdown("<hr />")
    gr.Markdown(
        """
        如果信用卡申请可能被拒绝,申请人可以询问最有可能需要多少年的工作经验才能增加获得信用卡批准的机会。

        为简单起见,上述所有步骤都合并为一个按钮。因此,以下按钮会加密来自所有三方的相同输入(工作年限除外,工作年限会有所不同),在 FHE 中运行新的预测并解密输出。

        如果以下状态表明要尝试新的“工作年限”输入,则只需更新步骤2中的值并直接再次运行步骤6。        
        """
    )
    explain_button = gr.Button(
        "加密输入,在FHE中计算并解密输出。"
    )
    explain_prediction = gr.Textbox(
        label="需要额外的工作年限。", interactive=False
    )

    # Button to explain the prediction
    explain_button.click(
        explain_encrypt_run_decrypt,
        inputs=[client_id, prediction_output, years_employed, employed],
        outputs=[explain_prediction, explain_button],
    )

    gr.Markdown(
        "本应用是一个隐私保护机器学习的示例,仅供参考。"
    )

demo.launch(share=False)