import os

from django.test import TransactionTestCase
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.datastructures import MultiValueDict

from ..backend import utils
from ..forms import (
    WooeyForm,
)
from ..forms import config as forms_config
from ..models import ScriptParameterGroup, ScriptParameter, ScriptVersion, WooeyJob

from . import (
    config,
    factories,
    mixins,
    utils as test_utils,
)


class FormTestCase(
    mixins.ScriptFactoryMixin, mixins.FileCleanupMixin, TransactionTestCase
):
    @staticmethod
    def get_mvdict(data):
        return MultiValueDict(data)

    def test_master_form(self):
        script_version = self.translate_script
        form = utils.get_master_form(script_version=script_version)
        self.assertTrue(isinstance(form, WooeyForm))
        qdict = self.get_mvdict(config.SCRIPT_DATA["translate"].get("data"))
        utils.validate_form(
            form=form, data=qdict, files=config.SCRIPT_DATA["translate"].get("files")
        )
        self.assertTrue(form.is_valid())

    def test_groups_dont_duplicate(self):
        # fixes https://github.com/wooey/Wooey/issues/163
        # Update the translate script and make sure we do not duplicate form groups
        # Make a new script version
        old_translate_pk = self.translate_script.pk
        new_translate = self.translate_script
        new_translate.pk = None
        new_translate.save()
        old_translate = ScriptVersion.objects.get(pk=old_translate_pk)

        # Use a script that is slightly different but the same form groups
        script_path = test_utils.save_script_path(
            os.path.join(config.WOOEY_TEST_SCRIPTS, "translate2.py")
        )
        new_translate = utils.add_wooey_script(
            script_version=new_translate,
            script_path=script_path,
        )["script"]

        # Assert we updated correctly
        self.assertEqual(old_translate.script.pk, new_translate.script.pk)
        self.assertEqual(old_translate.script_iteration, 1)
        self.assertEqual(new_translate.script_iteration, 2)

        # Make sure we still have the same number of groups after updating
        new_form = utils.get_form_groups(script_version=new_translate)
        old_form = utils.get_form_groups(script_version=old_translate)
        for new_parsers, old_parsers in zip(new_form["parsers"], old_form["parsers"]):
            for new_subparser, old_subparser in zip(
                new_form["parsers"][new_parsers], old_form["parsers"][old_parsers]
            ):
                self.assertEqual(
                    new_subparser["group_name"], old_subparser["group_name"]
                )

            self.assertEqual(len(new_parsers), len(old_parsers))

        self.assertEqual(len(new_form["parsers"]), len(old_form["parsers"]))

    def test_group_form(self):
        script_version = self.translate_script
        form = utils.get_form_groups(script_version=script_version)
        subparser = script_version.scriptparser_set.first()
        subparser_key = (subparser.pk, subparser.name)
        self.assertEqual(len(form["parsers"][subparser_key]), 2)

        # test we can hide parameters and groups
        groups = ScriptParameterGroup.objects.filter(script_version=script_version)
        group = groups[1]
        group.hidden = True
        group.save()
        form = utils.get_form_groups(script_version=script_version)
        self.assertEqual(
            len(form["parsers"][subparser_key]),
            1,
            "Script Parameter group is hidden but shown",
        )
        group.hidden = False
        group.save()
        form = utils.get_form_groups(script_version=script_version)
        self.assertEqual(
            len(form["parsers"][subparser_key]),
            2,
            "Script Parameter group is shown but hidden",
        )
        param = ScriptParameter.objects.get(script_version=script_version, slug="out")
        param.hidden = True
        param.save()
        form = utils.get_form_groups(script_version=script_version)
        slug = test_utils.get_subparser_form_slug(script_version, "out")
        self.assertNotIn(
            slug,
            form["parsers"][subparser_key][1]["form"].fields,
            "Script Parameter is hidden but shown",
        )
        param.hidden = False
        param.save()
        form = utils.get_form_groups(script_version=script_version)
        self.assertIn(
            slug,
            form["parsers"][subparser_key][1]["form"].fields,
            "Script Parameter is shown but hidden",
        )

    def test_multiplechoice_form(self):
        script_version = self.choice_script
        form = utils.get_master_form(script_version=script_version)
        # check our wrapper is in the form render
        form_str = str(form)
        self.assertTrue(forms_config.WOOEY_MULTI_WIDGET_ANCHOR in form_str)
        self.assertTrue(forms_config.WOOEY_MULTI_WIDGET_ATTR in form_str)

        qdict = {}
        for key, value in config.SCRIPT_DATA["choices"]["data"].items():
            try:
                form_slug = test_utils.get_subparser_form_slug(script_version, key)
                qdict[form_slug] = value
            except Exception:
                qdict[key] = value
        qdict = self.get_mvdict(qdict)

        fdict = {}
        for key, value in config.SCRIPT_DATA["choices"]["files"].items():
            try:
                form_slug = test_utils.get_subparser_form_slug(script_version, key)
                fdict[form_slug] = value
            except Exception:
                fdict[key] = value

        uploaded_files = {}
        storage = utils.get_storage(local=True)
        for file_key, files in fdict.items():
            uploaded_files[file_key] = []
            for file_location in files:
                file_name = os.path.split(file_location.name)[1]
                uploaded_files[file_key].append(
                    SimpleUploadedFile(file_name, file_location.read())
                )

        utils.validate_form(form=form, data=qdict, files=uploaded_files)
        self.assertTrue(form.is_valid())
        # test we can create a job from this form
        # this is implemented to put data and files in the same dictionary, so update it
        form.cleaned_data.update(uploaded_files)
        job = utils.create_wooey_job(
            script_version_pk=script_version.pk, data=form.cleaned_data
        )
        # check the files are here
        file_param = test_utils.get_subparser_form_slug(
            script_version, "multiple_file_choices"
        )
        files = [
            i.value for i in job.get_parameters() if i.parameter.form_slug == file_param
        ]
        self.assertEqual(len(files), len(uploaded_files.get(file_param)))

    def test_without_args_form(self):
        script_version = self.without_args
        form = utils.get_master_form(script_version=script_version)
        # check our wrapper is in the form render
        # in a normal POST request, the data will contain {'wooey_type': script_pk}. This will cause the is_bound
        # attribute of the form to be set. Otherwise, this test will always fail. It is OK to set this data
        # in the test, since it is how it will be recapitulated in the normal use case
        form_data = form.initial
        form_data.update(config.SCRIPT_DATA["without_args"].get("data"))
        utils.validate_form(form=form, data=form_data)
        self.assertTrue(form.is_valid(), form.errors)
        # test we can create a job from this form
        job = utils.create_wooey_job(
            script_version_pk=script_version.pk, data=form.cleaned_data
        )
        job.submit_to_celery()
        # get the job again
        job = WooeyJob.objects.get(pk=job.pk)
        self.assertEqual(job.status, WooeyJob.COMPLETED)

    def test_wooey_form(self):
        # Make sure wooey form exists and has everything we need in it
        script_version = self.without_args
        forms = utils.get_form_groups(script_version=self.without_args)
        wooey_form = forms["wooey_form"]
        self.assertDictEqual(wooey_form.initial, {"wooey_type": script_version.pk})

    def test_multiple_file_field_initial_value(self):
        # Addresses https://github.com/wooey/Wooey/issues/248
        script_version = self.choice_script
        multiple_files_param = script_version.scriptparameter_set.get(
            script_param="multiple_file_choices"
        )
        # Upload some fake files
        storage = utils.get_storage(local=False)
        storage.save("file1", SimpleUploadedFile("file1", b"abc"))
        file1_path = storage.path("file1")
        storage.save("file2", SimpleUploadedFile("file2", b"abc"))
        file2_path = storage.path("file2")
        form = utils.get_form_groups(
            script_version=script_version,
            initial_dict={multiple_files_param.form_slug: ["file1", "file2"]},
        )
        # TODO: Make a function to ease this
        initial_files = [
            i.path
            for i in form["parsers"][
                (multiple_files_param.parser.pk, multiple_files_param.parser.name)
            ][1]["form"]
            .fields[multiple_files_param.form_slug]
            .initial
        ]
        self.assertIn(file1_path, initial_files)
        self.assertIn(file2_path, initial_files)

    def test_form_with_custom_widget(self):
        script_version = self.choice_script
        # Associate a custom widget with a field
        choice_param = script_version.scriptparameter_set.get(script_param="one_choice")
        widget = factories.WooeyWidgetFactory(
            widget_class="django.forms.TextInput",
            input_class="custom",
            input_properties="custom-property",
            input_attributes='attr1="custom1" attr2="custom2"',
        )
        choice_param.custom_widget = widget
        choice_param.save()
        form = utils.get_form_groups(script_version=script_version)

        from django.forms import TextInput

        field = form["parsers"][(choice_param.parser.pk, choice_param.parser.name)][1][
            "form"
        ].fields[choice_param.form_slug]
        self.assertTrue(isinstance(field.widget, TextInput))
        self.assertEquals(
            field.widget.attrs,
            {
                "custom-property": True,
                "attr1": "custom1",
                "attr2": "custom2",
                "class": "custom",
            },
        )

    def test_handles_special_characters(self):
        script_version = self.choice_script
        # Associate a custom widget with a field
        choice_str_slug = test_utils.get_subparser_form_slug(
            script_version, "choices_str"
        )
        choice_str = "This & is special"
        form = utils.get_master_form(script_version=script_version)
        qdict = self.get_mvdict(
            {
                choice_str_slug: [choice_str],
                "wooey_type": [script_version.pk],
                "job_name": ["abc"],
            }
        )
        utils.validate_form(form=form, data=qdict)
        self.assertTrue(form.is_valid(), form.errors)
        self.assertEqual(form.cleaned_data[choice_str_slug], [choice_str])

    def test_subparser_selection(self):
        script_version = self.subparser_script
        subparser = script_version.scriptparser_set.get(name="subparser2")
        form = utils.get_master_form(script_version=script_version, parser=subparser.pk)
        sp1_slug = test_utils.get_subparser_form_slug(script_version, "sp1")
        sp2_slug = test_utils.get_subparser_form_slug(script_version, "sp2")
        test_arg_slug = test_utils.get_subparser_form_slug(script_version, "test_arg")

        # The sp1 slug is only present in subparser1, so we should not see it, but we should
        # see the sp2 slug and the general subparser options of test_arg
        self.assertIn(sp2_slug, form.fields)
        self.assertNotIn(sp1_slug, form.fields)
        self.assertIn(test_arg_slug, form.fields)
