"""This module contains the tests for utils of the ``common`` app."""

import io
import os
import shutil
import uuid
from datetime import datetime, timedelta

from PIL import Image

from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.core.files.uploadedfile import SimpleUploadedFile, TemporaryUploadedFile
from django.test import TestCase
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _, override

from .test_cases import WithMediaOnDisk
from ..utils import (
    filter_list_of_dicts,
    get_dict_in_list,
    handle_uploaded_file,
    sort_list_of_dicts,
    start_of_day,
    string_to_timedelta,
    timedelta_to_string,
    mod_lazy,
)


class CommonUtilsTestCase(TestCase):
    """TestCase to test the functions found in mtp.apps.common.utils"""

    def setUp(self):
        super().setUp()

        # Create a little list of dictionaries to test the related helpers.
        self.lst = [
            {'first_name': 'Kara', 'last_name': 'Thrace', 'call_sign': 'Starbuck'},
            {'first_name': 'Lee', 'last_name': 'Adama', 'call_sign': 'Apollo'},
            {'first_name': 'William', 'last_name': 'Adama', 'call_sign': 'Husker'},
        ]

    def test_sort_list_of_dicts(self):
        """Test the ``sort_list_of_dicts`` function."""

        # Check that sorting works on all the keys.
        expected = [
            {'first_name': 'Kara', 'last_name': 'Thrace', 'call_sign': 'Starbuck'},
            {'first_name': 'Lee', 'last_name': 'Adama', 'call_sign': 'Apollo'},
            {'first_name': 'William', 'last_name': 'Adama', 'call_sign': 'Husker'},
        ]
        computed = sort_list_of_dicts(self.lst, 'first_name')
        self.assertEqual(expected, computed)

        expected = [
            {'first_name': 'Lee', 'last_name': 'Adama', 'call_sign': 'Apollo'},
            {'first_name': 'William', 'last_name': 'Adama', 'call_sign': 'Husker'},
            {'first_name': 'Kara', 'last_name': 'Thrace', 'call_sign': 'Starbuck'},
        ]
        computed = sort_list_of_dicts(self.lst, 'last_name')
        self.assertEqual(expected, computed)

        expected = [
            {'first_name': 'Lee', 'last_name': 'Adama', 'call_sign': 'Apollo'},
            {'first_name': 'William', 'last_name': 'Adama', 'call_sign': 'Husker'},
            {'first_name': 'Kara', 'last_name': 'Thrace', 'call_sign': 'Starbuck'},
        ]
        computed = sort_list_of_dicts(self.lst, 'call_sign')
        self.assertEqual(expected, computed)

        # Check that sorting on a non-existing key raises a ``KeyError``.
        with self.assertRaises(KeyError):
            sort_list_of_dicts(self.lst, 'is_a_cylon')

    def test_filter_list_in_dicts(self):
        """Test the ``filter_list_of_dicts`` function."""

        # Check that filtering with an existing value works.
        expected = [
            {'first_name': 'Lee', 'last_name': 'Adama', 'call_sign': 'Apollo'},
            {'first_name': 'William', 'last_name': 'Adama', 'call_sign': 'Husker'},
        ]
        computed = filter_list_of_dicts(self.lst, 'last_name', 'Adama')
        self.assertEqual(expected, computed)

        # Check that filtering with a non-existing value returns an empty list.
        expected = []
        computed = filter_list_of_dicts(self.lst, 'last_name', 'Valerii')
        self.assertEqual(expected, computed)

        # Check that filtering with a non-existing key returns an empty list.
        expected = []
        computed = filter_list_of_dicts(self.lst, 'home_world', 'Caprica')
        self.assertEqual(expected, computed)

    def test_get_dict_in_lists(self):
        """Test the ``get_dict_in_list`` function."""

        # Check that the nominal case works.
        expected = {
            'first_name': 'Kara', 'last_name': 'Thrace', 'call_sign': 'Starbuck'
        }
        computed = get_dict_in_list(self.lst, 'call_sign', 'Starbuck')
        self.assertEqual(expected, computed)

        # Check that specifying a non-existing key raises ``ObjectDoesNotExist``.
        with self.assertRaises(ObjectDoesNotExist):
            get_dict_in_list(self.lst, 'home_world', 'Caprica')

        # Check that specifying a non-existing value raises ``ObjectDoesNotExist``.
        with self.assertRaises(ObjectDoesNotExist):
            get_dict_in_list(self.lst, 'call_sign', 'Boomer')

        # Check that getting multiple results raises ``MultipleObjectsReturned``.
        with self.assertRaises(MultipleObjectsReturned):
            get_dict_in_list(self.lst, 'last_name', 'Adama')

    def test_timedelta_to_string(self):
        """Test the ``timedelta_to_string`` function."""

        time = timedelta(days=3, seconds=3569)
        self.assertEqual(
            timedelta_to_string(time),
            '3 days, 59 minutes, 29 seconds'
        )

        time = timedelta(days=1, seconds=1)
        self.assertEqual(
            timedelta_to_string(time),
            '1 day, 1 second'
        )

        time = timedelta(hours=3, seconds=90)
        self.assertEqual(
            timedelta_to_string(time),
            '3 hours, 1 minute, 30 seconds'
        )

        time = timedelta(hours=7)
        self.assertEqual(
            timedelta_to_string(time),
            '7 hours'
        )

        time = timedelta(seconds=3569)
        self.assertEqual(
            timedelta_to_string(time),
            '59 minutes, 29 seconds'
        )

        self.assertEqual(
            timedelta_to_string(None),
            ''
        )

    def test_string_to_timedelta(self):
        """Test the ``string_to_timedelta`` function."""

        time_text = '3 days, 59 minutes, 29 seconds'
        self.assertEqual(
            string_to_timedelta(time_text),
            timedelta(days=3, seconds=3569)
        )

        time_text = '1 day, 1 second'
        self.assertEqual(
            string_to_timedelta(time_text),
            timedelta(days=1, seconds=1)
        )

        time_text = '3 hours, 1 minute, 30 seconds'
        self.assertEqual(
            string_to_timedelta(time_text),
            timedelta(hours=3, seconds=90)
        )

        time_text = '7 hours'
        self.assertEqual(
            string_to_timedelta(time_text),
            timedelta(hours=7)
        )

        time_text = '59 minutes, 29 seconds'
        self.assertEqual(
            string_to_timedelta(time_text),
            timedelta(seconds=3569)
        )

        time_text = '59 foo, 29 bar'
        self.assertEqual(
            string_to_timedelta(time_text),
            None
        )

    def test_start_of_day(self):
        """Test the ``start_of_day`` function."""

        date_time = datetime(2017, 7, 6, 12, 41, 29, tzinfo=utc)
        date = start_of_day(date_time)
        self.assertEqual(
            date,
            datetime(2017, 7, 6, 0, 0, 0, tzinfo=utc)
        )

    @override('en')
    def test_mod_lazy(self):
        """Test the ``mod_lazy`` function."""

        # both exists in django ;)
        str1 = "Successfully deleted %(count)d %(items)s."
        str2 = "Change %s"

        # check default behaviour
        str1_default = _(str1) % {'count': 3, 'items': 'foo'}
        str2_default = _(str2) % 'foo'

        str1_mod_lazy = mod_lazy(_(str1), count=3, items='foo')
        str2_mod_lazy = mod_lazy(_(str2), 'foo')

        with override('fr'):
            self.assertEqual(str(str1_default), 'Successfully deleted 3 foo.')
            self.assertEqual(str(str2_default), 'Change foo')

            self.assertEqual(str(str1_mod_lazy), 'La suppression de 3 foo a réussi.')
            self.assertEqual(str(str2_mod_lazy), 'Modification de foo')


class HandleUploadedFileTestCase(WithMediaOnDisk):
    """Test cases for the ``handle_uploaded_file``"""

    @classmethod
    def setUpClass(cls):
        """Create the ``TemporaryUploadedFile`` instance and setup the path"""

        super().setUpClass()

        cls.uploaded_file = TemporaryUploadedFile('test', 'test', 'utf-8', 'test')
        cls.save_filename = '%s.xls' % (uuid.uuid4())

        cls.save_path_dir = os.path.join(settings.MEDIA_ROOT, 'anything')
        cls.save_path_name = os.path.join(cls.save_path_dir, cls.save_filename)

    def setUp(self):
        """Make sure the path don't exists"""

        super().setUp()

        shutil.rmtree(self.save_path_dir, ignore_errors=True)

    def tearDown(self):
        """Make sure the path is deleted"""

        super().tearDown()

        shutil.rmtree(self.save_path_dir, ignore_errors=True)

    def test_handle_uploaded_file_should_work_with_existing_path(self):
        """Test that the ``handle_uploaded_file`` should work with an existing path"""

        # First test with a pre-existing path
        os.makedirs(self.save_path_dir)

        handle_uploaded_file(self.uploaded_file, self.save_path_name)

        # The file was correctly created
        self.assertTrue(
            os.path.exists(self.save_path_name)
        )

    def test_handle_uploaded_file_should_work_with_non_existing_path(self):
        """Test that the ``handle_uploaded_file`` should work without an existing path"""

        # The folder doesn't exist (yet)
        self.assertFalse(os.path.exists(self.save_path_dir))

        handle_uploaded_file(self.uploaded_file, self.save_path_name)

        # The folder exists
        self.assertTrue(os.path.exists(self.save_path_dir))

        # The file was correctly created
        self.assertTrue(
            os.path.exists(self.save_path_name)
        )


def create_fake_image_file():
    """Function to create a fake image file for ``ImageField`` field.

    Returns
    -------
    SimpleUploadedFile
        The fake file.

    """

    image = Image.new(mode='RGB', size=(200, 200))

    image_io = io.BytesIO()
    image.save(image_io, 'jpeg')

    return SimpleUploadedFile(
        name='fake.png',
        content=image_io.getvalue(),
        content_type='image/png',
    )
