"""
ICA Class
============================================
This Class executes the In-Context Attack algorithm described in the paper below.
This part of code is based on the paper.

Paper title: Jailbreak and Guard Aligned Language Models with Only Few In-Context Demonstrations
arXiv link: https://arxiv.org/pdf/2310.06387.pdf
"""
import logging
import tqdm
from easyjailbreak.attacker import AttackerBase
from easyjailbreak.datasets import JailbreakDataset
from easyjailbreak.datasets.instance import Instance
from easyjailbreak.seed import SeedTemplate
from easyjailbreak.metrics.Evaluator import EvaluatorPatternJudge


class ICA(AttackerBase):
    r"""
    In-Context Attack(ICA) crafts malicious contexts to guide models in generating harmful outputs.
    """

    def __init__(
            self, 
            target_model, 
            jailbreakDatasets: JailbreakDataset,
            attack_model = None,
            eval_model = None,
            max_query: int = 100,
            max_jailbreak: int = 100,
            max_reject: int = 100,
            max_iteration: int = 100,
            prompt_num: int = 5,
            user_input: bool = False,
            pattern_dict = None,
            ):
        r"""
        Initialize the ICA attack instance.
        :param ~model_wrapper target_model: The target model to be attacked.
        :param ~JailbreakDataset jailbreakDatasets: The dataset containing harmful queries.
        :param ~int prompt_num: The number of in-context demonstration.
        :param ~bool user_input: whether to use in-context demonstration input by user.
        :param ~dict pattern_dict: the pattern dictionary used in EvaluatorPatternJudge.
        """

        super().__init__(attack_model, target_model, eval_model, jailbreakDatasets)

        self.attack_results = JailbreakDataset([])
        self.evaluator = EvaluatorPatternJudge(pattern_dict=pattern_dict)

        self.current_query: int = 0
        self.current_jailbreak: int = 0
        self.current_reject: int = 0
        self.current_iteration: int = 0

        self.max_query: int = max_query
        self.max_jailbreak: int = max_jailbreak
        self.max_reject: int = max_reject
        self.max_iteration: int = max_iteration

        # ICA特有参数
        self.prompt_num: int = prompt_num
        self.user_input: bool = user_input

        # 初始化jailbreak prompt
        if not user_input:
            init_prompt = SeedTemplate().new_seeds(seeds_num=1, prompt_usage='attack', method_list=['ICA'])
            prompt = init_prompt[0]
        else:
            harmful_prompts = []
            harmful_responses = []
            print("Please input " + str(prompt_num) + " pairs of harmful prompts and corresponding responses\n")
            for i in range(prompt_num):
                harmful_prompts.append(input("harmful prompt:"))  
                harmful_responses.append(input("harmful response:")) 
            prompt = ""
            for i in range(prompt_num):
                prompt += "User:" + harmful_prompts[i] + '\nAssistant:' + harmful_responses[i] + '\n'
            prompt += "User:{query}"

        for instance in self.jailbreakDatasets:
            instance.jailbreak_prompt = prompt


    def single_attack(self, sample: Instance): 
        r"""
        Conduct a single attack on sample with n-shot attack demonstrations.
        Split the original jailbreak_prompt by roles and merge them into the current conversation_template as in-context demonstration.
        """
        prompt = sample.jailbreak_prompt.format(query=sample.query)
        prompt_splits = prompt.split("\n")
        messages = []
        for i in range(0, 2*self.prompt_num, 2):
            messages.append(prompt_splits[i].replace("User:", ""))
            messages.append(prompt_splits[i+1].replace("Assistant:", ""))
        messages.append(prompt_splits[-1].replace("User:", ""))
        response = self.target_model.generate(messages=messages)
        sample.target_responses.append(response)
        return JailbreakDataset([sample])


    def update(self, Dataset):
        """
        Update the state of the attack.
        """
        self.current_iteration += 1
        for Instance in Dataset:
            self.current_jailbreak += Instance.num_jailbreak
            self.current_query += Instance.num_query
            self.current_reject += Instance.num_reject


    def attack(self):
        """
        Main loop for the attack process, iterate through jailbreakDatasets.
        """
        logging.info("Jailbreak started!")
        try:
            for Instance in tqdm.tqdm(self.jailbreakDatasets, desc="processing instance"):
                mutated_instance = self.single_attack(Instance)[0]
                self.attack_results.add(mutated_instance)
            self.evaluator(self.attack_results)
            self.update(self.attack_results)
        except KeyboardInterrupt:
            logging.info("Jailbreak interrupted by user!")
        self.log()
        logging.info("Jailbreak finished!")
        return self.attack_results


    def log(self):
        r"""
        Report the attack results.
        """
        logging.info("Jailbreak report:")
        logging.info(f"Total queries: {self.current_query}")
        logging.info(f"Total jailbreak: {self.current_jailbreak}")
        logging.info(f"Total reject: {self.current_reject}")
