"""
Tests for testing the modulestore settings migration code.
"""
import copy
import ddt
from tempfile import mkdtemp

from unittest import TestCase
from xmodule.modulestore.modulestore_settings import (
    convert_module_store_setting_if_needed,
    update_module_store_settings,
    get_mixed_stores,
)


@ddt.ddt
class ModuleStoreSettingsMigration(TestCase):
    """
    Tests for the migration code for the module store settings
    """

    OLD_CONFIG = {
        "default": {
            "ENGINE": "xmodule.modulestore.xml.XMLModuleStore",
            "OPTIONS": {
                "data_dir": "directory",
                "default_class": "xmodule.hidden_module.HiddenDescriptor",
            },
            "DOC_STORE_CONFIG": {},
        }
    }

    OLD_CONFIG_WITH_DIRECT_MONGO = {
        "default": {
            "ENGINE": "xmodule.modulestore.mongo.MongoModuleStore",
            "OPTIONS": {
                "collection": "modulestore",
                "db": "edxapp",
                "default_class": "xmodule.hidden_module.HiddenDescriptor",
                "fs_root": mkdtemp(),
                "host": "localhost",
                "password": "password",
                "port": 27017,
                "render_template": "edxmako.shortcuts.render_to_string",
                "user": "edxapp"
            },
            "DOC_STORE_CONFIG": {},
        }
    }

    OLD_MIXED_CONFIG_WITH_DICT = {
        "default": {
            "ENGINE": "xmodule.modulestore.mixed.MixedModuleStore",
            "OPTIONS": {
                "mappings": {},
                "stores": {
                    "an_old_mongo_store": {
                        "DOC_STORE_CONFIG": {},
                        "ENGINE": "xmodule.modulestore.mongo.MongoModuleStore",
                        "OPTIONS": {
                            "collection": "modulestore",
                            "db": "test",
                            "default_class": "xmodule.hidden_module.HiddenDescriptor",
                        }
                    },
                    "default": {
                        "ENGINE": "the_default_store",
                        "OPTIONS": {
                            "option1": "value1",
                            "option2": "value2"
                        },
                        "DOC_STORE_CONFIG": {}
                    },
                    "xml": {
                        "ENGINE": "xmodule.modulestore.xml.XMLModuleStore",
                        "OPTIONS": {
                            "data_dir": "directory",
                            "default_class": "xmodule.hidden_module.HiddenDescriptor"
                        },
                        "DOC_STORE_CONFIG": {}
                    }
                }
            }
        }
    }

    ALREADY_UPDATED_MIXED_CONFIG = {
        'default': {
            'ENGINE': 'xmodule.modulestore.mixed.MixedModuleStore',
            'OPTIONS': {
                'mappings': {},
                'stores': [
                    {
                        'NAME': 'split',
                        'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore',
                        'DOC_STORE_CONFIG': {},
                        'OPTIONS': {
                            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
                            'fs_root': "fs_root",
                            'render_template': 'edxmako.shortcuts.render_to_string',
                        }
                    },
                    {
                        'NAME': 'draft',
                        'ENGINE': 'xmodule.modulestore.mongo.draft.DraftModuleStore',
                        'DOC_STORE_CONFIG': {},
                        'OPTIONS': {
                            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
                            'fs_root': "fs_root",
                            'render_template': 'edxmako.shortcuts.render_to_string',
                        }
                    },
                ]
            }
        }
    }

    def assertStoreValuesEqual(self, store_setting1, store_setting2):
        """
        Tests whether the fields in the given store_settings are equal.
        """
        store_fields = ["OPTIONS", "DOC_STORE_CONFIG"]
        for field in store_fields:
            self.assertEqual(store_setting1[field], store_setting2[field])

    def assertMigrated(self, old_setting):
        """
        Migrates the given setting and checks whether it correctly converted
        to an ordered list of stores within Mixed.
        """
        # pass a copy of the old setting since the migration modifies the given setting
        new_mixed_setting = convert_module_store_setting_if_needed(copy.deepcopy(old_setting))

        # check whether the configuration is encapsulated within Mixed.
        self.assertEqual(new_mixed_setting["default"]["ENGINE"], "xmodule.modulestore.mixed.MixedModuleStore")

        # check whether the stores are in an ordered list
        new_stores = get_mixed_stores(new_mixed_setting)
        self.assertIsInstance(new_stores, list)

        return new_mixed_setting, new_stores[0]

    def is_split_configured(self, mixed_setting):
        """
        Tests whether the split module store is configured in the given setting.
        """
        stores = get_mixed_stores(mixed_setting)
        split_settings = [store for store in stores if store['ENGINE'].endswith('.DraftVersioningModuleStore')]
        if len(split_settings):
            # there should only be one setting for split
            self.assertEquals(len(split_settings), 1)
            # verify name
            self.assertEquals(split_settings[0]['NAME'], 'split')
            # verify split config settings equal those of mongo
            self.assertStoreValuesEqual(
                split_settings[0],
                next((store for store in stores if 'DraftModuleStore' in store['ENGINE']), None)
            )
        return len(split_settings) > 0

    def test_convert_into_mixed(self):
        old_setting = self.OLD_CONFIG
        new_mixed_setting, new_default_store_setting = self.assertMigrated(old_setting)
        self.assertStoreValuesEqual(new_default_store_setting, old_setting["default"])
        self.assertEqual(new_default_store_setting["ENGINE"], old_setting["default"]["ENGINE"])
        self.assertFalse(self.is_split_configured(new_mixed_setting))

    def test_convert_from_old_mongo_to_draft_store(self):
        old_setting = self.OLD_CONFIG_WITH_DIRECT_MONGO
        new_mixed_setting, new_default_store_setting = self.assertMigrated(old_setting)
        self.assertStoreValuesEqual(new_default_store_setting, old_setting["default"])
        self.assertEqual(new_default_store_setting["ENGINE"], "xmodule.modulestore.mongo.draft.DraftModuleStore")
        self.assertTrue(self.is_split_configured(new_mixed_setting))

    def test_convert_from_dict_to_list(self):
        old_mixed_setting = self.OLD_MIXED_CONFIG_WITH_DICT
        new_mixed_setting, new_default_store_setting = self.assertMigrated(old_mixed_setting)
        self.assertEqual(new_default_store_setting["ENGINE"], "the_default_store")
        self.assertTrue(self.is_split_configured(new_mixed_setting))

        # exclude split when comparing old and new, since split was added as part of the migration
        new_stores = [store for store in get_mixed_stores(new_mixed_setting) if store['NAME'] != 'split']
        old_stores = get_mixed_stores(self.OLD_MIXED_CONFIG_WITH_DICT)

        # compare each store configured in mixed
        self.assertEqual(len(new_stores), len(old_stores))
        for new_store in new_stores:
            self.assertStoreValuesEqual(new_store, old_stores[new_store['NAME']])

    def test_no_conversion(self):
        # make sure there is no migration done on an already updated config
        old_mixed_setting = self.ALREADY_UPDATED_MIXED_CONFIG
        new_mixed_setting, new_default_store_setting = self.assertMigrated(old_mixed_setting)
        self.assertTrue(self.is_split_configured(new_mixed_setting))
        self.assertEquals(old_mixed_setting, new_mixed_setting)

    @ddt.data('draft', 'split')
    def test_update_settings(self, default_store):
        mixed_setting = self.ALREADY_UPDATED_MIXED_CONFIG
        update_module_store_settings(mixed_setting, default_store=default_store)
        self.assertTrue(get_mixed_stores(mixed_setting)[0]['NAME'] == default_store)

    def test_update_settings_error(self):
        mixed_setting = self.ALREADY_UPDATED_MIXED_CONFIG
        with self.assertRaises(Exception):
            update_module_store_settings(mixed_setting, default_store='non-existent store')
