import unittest
from importlib import import_module
from pathlib import Path

from promptinject import (
    build_dataframe,
    build_prompts,
    prompt_data,
    run_prompts_api,
    score_attacks,
)


def _remove_keys(dicts, keys):
    def _remove_keys_from_dict(d):
        return {k: v for k, v in d.items() if k not in keys}

    return tuple(_remove_keys_from_dict(d) for d in dicts)


class TestPromptInject(unittest.TestCase):
    def test_prompt_build(self):
        SUBDIR_NAME = "prompting"
        tests_dir = Path(__file__).resolve().parent / "configs" / SUBDIR_NAME
        self.maxDiff = None
        for test_file in tests_dir.glob("*.py"):
            with self.subTest(file=test_file.stem):
                test_module = import_module(
                    f".configs.{SUBDIR_NAME}.{test_file.stem}",
                    "tests",
                )
                prompt = build_prompts(test_module.prompt_config)

                # Remove keys that we don't want to test
                prompt = _remove_keys(prompt, ["hash"])
                expected_result = _remove_keys(test_module.expected_result, ["hash"])
                self.assertEqual(prompt, expected_result)

    def test_score_without_run(self):
        prompt_config = {
            "base": {
                "text": prompt_data.openai_playground_prompts["default-grammar"],
            },
            "attack": {
                "text": prompt_data.goal_hikacking_attacks["ignore-print"],
                "rogue_string": prompt_data.rogue_strings["hate-humans"],
                "scoring": "match_rogue_to_output",
            },
            "visualization": {"columns": ("config_model",)},
        }

        with self.assertRaises(ValueError):
            prompts = build_prompts(prompt_config)
            score_attacks(prompts)

    def test_invalid_score_method(self):
        prompt_config = {
            "base": {
                "text": prompt_data.openai_playground_prompts["default-grammar"],
            },
            "attack": {
                "text": prompt_data.goal_hikacking_attacks["ignore-print"],
                "rogue_string": prompt_data.rogue_strings["hate-humans"],
                "scoring": "invalid_method",
            },
            "visualization": {"columns": ("config_model",)},
        }

        with self.assertRaises(ValueError):
            prompts = build_prompts(prompt_config)
            run_prompts_api(prompts, quiet=True, dry_run=True)
            score_attacks(prompts)

    def test_score(self):
        from .configs.scoring import default_settings

        prompts = build_prompts(default_settings.prompt_config)
        run_prompts_api(prompts, quiet=True, dry_run=True)
        score_attacks(prompts)

        # Remove keys that we don't want to test
        prompts = _remove_keys(prompts, ["hash"])
        expected_result = _remove_keys(default_settings.expected_result, ["hash"])
        self.assertEqual(prompts, expected_result)

    def test_visualization(self):
        SUBDIR_NAME = "visualization"
        tests_dir = Path(__file__).resolve().parent / "configs" / SUBDIR_NAME
        self.maxDiff = None
        for test_file in tests_dir.glob("*.py"):
            with self.subTest(file=test_file.stem):
                test_module = import_module(
                    f".configs.{SUBDIR_NAME}.{test_file.stem}",
                    "tests",
                )
                config = test_module.prompt_config
                prompts = build_prompts(config)
                df = build_dataframe(prompts)
                self.assertEqual(len(df), test_module.expected["df_len"])
                self.assertEqual(len(df.columns), test_module.expected["df_n_columns"])

                if "column_names" in test_module.expected:
                    self.assertListEqual(
                        list(df.columns), test_module.expected["column_names"]
                    )
                for col in test_module.expected["columns"]:
                    self.assertIn(col, prompt_data.column_aliases)
                    alias = prompt_data.column_aliases[col]
                    self.assertTupleEqual(
                        tuple(sorted(df[alias].unique())),
                        tuple(sorted(test_module.expected["columns"][col])),
                    )
                    self.assertIsNone(df["Score"].unique()[0])
                    self.assertEqual(len(df["Score"].unique()), 1)


if __name__ == "__main__":
    unittest.main()
