import os
import tempfile
import zipfile
from pathlib import Path

from django.test import TestCase, TransactionTestCase

from ..backend import utils

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


class TestUtils(mixins.ScriptFactoryMixin, mixins.FileMixin, TransactionTestCase):
    def test_sanitize_name(self):
        assert (utils.sanitize_name("abc")) == "abc"
        assert (utils.sanitize_name("ab c")) == "ab_c"
        assert (utils.sanitize_name("ab-c")) == "ab_c"

    def test_sanitize_string(self):
        assert (utils.sanitize_string('ab"c')) == 'ab\\"c'

    def test_anonymous_users(self):
        from .. import settings as wooey_settings
        from django.contrib.auth.models import AnonymousUser

        user = AnonymousUser()
        script_version = self.translate_script
        script = script_version.script
        d = utils.valid_user(script, user)
        self.assertTrue(d["valid"])
        wooey_settings.WOOEY_ALLOW_ANONYMOUS = False
        d = utils.valid_user(script, user)
        self.assertFalse(d["valid"])

    def test_valid_user(self):
        user = factories.UserFactory()
        script_version = self.translate_script
        script = script_version.script
        d = utils.valid_user(script, user)
        self.assertTrue(d["valid"])
        from .. import settings as wooey_settings

        self.assertEqual("disabled", d["display"])
        wooey_settings.WOOEY_SHOW_LOCKED_SCRIPTS = False
        d = utils.valid_user(script, user)
        self.assertEqual("hide", d["display"])
        from django.contrib.auth.models import Group

        test_group = Group(name="test")
        test_group.save()
        script.user_groups.add(test_group)
        d = utils.valid_user(script, user)
        self.assertFalse(d["valid"])
        user.groups.add(test_group)
        d = utils.valid_user(script, user)
        self.assertTrue(d["valid"])

    def test_job_file_outputs(self):
        # Run a script that creates a file
        from wooey.models import WooeyJob, UserFile

        script = os.path.join(config.WOOEY_TEST_SCRIPTS, "file_maker.py")
        with open(script) as o:
            new_file = self.storage.save(self.filename_func("file_maker.py"), o)
        res = utils.add_wooey_script(script_path=new_file, group=None)
        self.assertEqual(res["valid"], True, res["errors"])

        output_slug = test_utils.get_subparser_form_slug(res["script"], "output")
        job = utils.create_wooey_job(
            script_version_pk=res["script"].pk,
            data={
                "job_name": "abc",
                output_slug: "test_file",
            },
        )
        # Get the new job
        job.submit_to_celery()
        job = WooeyJob.objects.get(pk=job.pk)

        # Make sure the file info is correct
        self.assertEqual(UserFile.objects.filter(job=job).count(), 4)
        for job_file in UserFile.objects.filter(job=job):
            wooey_file = job_file.system_file
            self.assertEqual(
                os.path.getsize(self.storage.path(wooey_file.filepath.name)),
                wooey_file.size_bytes,
            )

        # Check for the zip file overwrite in for https://github.com/wooey/Wooey/issues/202
        zip_file = UserFile.objects.get(job=job, filename__endswith="zip")
        _zip = zipfile.ZipFile(zip_file.system_file.filepath)
        files = [filename.filename for filename in _zip.filelist]
        self.assertCountEqual(
            files, ["abc/", "abc/test_file", "abc/test_dir/test_file"]
        )

    def test_duplicate_scriptversion_checksums(self):
        # Tests that script versions with duplicate checksums are correctly returned
        script = os.path.join(config.WOOEY_TEST_SCRIPTS, "versioned_script", "v1.py")
        with open(script) as o:
            v1 = self.storage.save(self.filename_func("v1.py"), o)
        res = utils.add_wooey_script(script_path=v1, script_name="test_versions")
        first_version = res["script"]
        first_version_pk = first_version.pk
        self.assertEqual(res["valid"], True, res["errors"])

        # Force a duplication of the Script Version, this mimics legacy code that may
        # have several script versions that were uploaded.

        first_version.pk = None
        first_version.script_iteration = first_version.script_iteration + 1
        first_version.save()
        second_version_pk = first_version.pk

        # Add the script again, which should not fail on MultipleObjectsReturned
        res = utils.add_wooey_script(script_path=v1, script_name="test_versions")
        self.assertEqual(res["script"].pk, second_version_pk)

    def test_add_wooey_script(self):
        from wooey.models import ScriptParser, ScriptVersion

        script = os.path.join(config.WOOEY_TEST_SCRIPTS, "versioned_script", "v1.py")
        with open(script) as o:
            v1 = self.storage.save(self.filename_func("v1.py"), o)
        res = utils.add_wooey_script(script_path=v1, script_name="test_versions")
        first_version = res["script"]
        self.assertEqual(res["valid"], True, res["errors"])
        parser = ScriptParser.objects.get(script_version=res["script"])
        self.assertEqual(parser.name, "")

        # Test that adding the script again doesn't update anything
        res = utils.add_wooey_script(script_path=v1, script_name="test_versions")
        self.assertEqual(res["valid"], False)
        self.assertEqual(
            str(res["errors"]), ScriptVersion.error_messages["duplicate_script"]
        )
        dup_first_version = res["script"]
        self.assertEqual(first_version.script_version, dup_first_version.script_version)
        self.assertEqual(first_version.script_iteration, 1)
        self.assertEqual(dup_first_version.script_iteration, 1)

        # Test updating to script2 keeps the same reference to the --one parameter
        # between scripts
        script = os.path.join(config.WOOEY_TEST_SCRIPTS, "versioned_script", "v2.py")
        with open(script) as o:
            v2 = self.storage.save(self.filename_func("v2.py"), o)

        res = utils.add_wooey_script(script_path=v2, script_name="test_versions")
        second_version = res["script"]
        first_params = {i.pk for i in first_version.get_parameters()}
        second_params = {i.pk for i in second_version.get_parameters()}
        self.assertTrue(first_params.issubset(second_params))
        self.assertTrue(len(second_params), 2)

        # update to v3, drops the second param
        script = os.path.join(config.WOOEY_TEST_SCRIPTS, "versioned_script", "v3.py")
        with open(script) as o:
            v3 = self.storage.save(self.filename_func("v3.py"), o)

        res = utils.add_wooey_script(script_path=v3, script_name="test_versions")
        third_version = res["script"]
        third_params = {i.pk for i in third_version.get_parameters()}
        self.assertEqual(first_params, third_params)


class TestFileDetectors(TestCase):
    def test_detector(self):
        self.file = os.path.join(config.WOOEY_TEST_DATA, "fasta.fasta")
        res, preview = utils.test_fastx(self.file)
        self.assertEqual(res, True, "Fastx parser fail")
        self.assertEqual(preview, open(self.file).readlines(), "Fastx Preview Fail")

    def test_delimited(self):
        self.file = os.path.join(config.WOOEY_TEST_DATA, "delimited.tsv")
        res, preview = utils.test_delimited(self.file)
        self.assertEqual(res, True, "Delimited parser fail")
        self.assertEqual(
            preview,
            [i.strip().split("\t") for i in open(self.file).readlines()],
            "Delimited Preview Fail",
        )


class TestGetAvailableFile(TransactionTestCase):
    def test_returns_file_unchanged_if_doesnt_exist(self):
        td = tempfile.mkdtemp()
        assert utils.get_available_file(td, "a_file", "txt") == os.path.join(
            td, "a_file.txt"
        )

    def test_appends_number_if_file_exists(self):
        td = tempfile.mkdtemp()
        with tempfile.NamedTemporaryFile(dir=td, suffix=".txt") as tf:
            filename = os.path.splitext(os.path.basename(tf.name))[0]
            available_filename = os.path.join(td, "{}_1.txt".format(filename))
            assert utils.get_available_file(td, filename, "txt") == available_filename
            Path(available_filename).touch()
            assert utils.get_available_file(td, filename, "txt") == os.path.join(
                td, "{}_2.txt".format(filename)
            )
