import builtins
import pathlib
from unittest.case import TestCase

import polib
import importlib
from arches.app.models.fields.i18n import I18n_String, I18n_JSON
from arches.app.models.models import CardModel, CardXNodeXWidget
from arches.app.utils.i18n import ArchesPOFileFetcher, ArchesPOLoader, ArchesPOWriter
from arches.app.models.system_settings import settings
from unittest.mock import Mock, MagicMock

# these tests can be run from the command line via
# python manage.py test tests.localization.po_file_tests --settings="tests.test_settings"


class PoFileTests(TestCase):
    def setUp(self):
        self.cardxnodexwidget_all = CardXNodeXWidget.objects.all
        self.cardmodel_all = CardModel.objects.all
        self.open = builtins.open
        self.mkdir = pathlib.Path.mkdir
        self.pofactory = polib.pofile
        self.languages = settings.LANGUAGES

    def tearDown(self):
        CardXNodeXWidget.objects.all = self.cardxnodexwidget_all
        CardModel.objects.all = CardModel.objects.all
        builtins.open = self.open
        pathlib.Path.mkdir = self.mkdir
        polib.pofile = self.pofactory
        settings.LANGUAGES = self.languages

    def test_populate(self):
        m_po_file = Mock(polib.POFile)
        m_all_method = Mock()
        m_all_method.return_value = []
        writer = ArchesPOWriter(m_po_file, "en", "en")
        CardXNodeXWidget.objects.all = m_all_method
        CardModel.objects.all = m_all_method
        writer.populate()
        self.assertEqual(m_all_method.call_count, 2)

    def test_populate_from_card_x_node_x_widget(self):
        "Test to ensure PO Entries are appended with appropriate english messageids (no translations)"
        m_po_file = Mock(polib.POFile)
        writer = ArchesPOWriter(m_po_file, "en", "en")
        model = MagicMock(CardXNodeXWidget)
        prop_dict = {"en": "configuration"}
        config_dict = {
            "i18n_properties": ["prop", "bob", "fred"],
            "prop": prop_dict,
            "fred": prop_dict,
            "label": prop_dict,
        }
        model.config.__getitem__.side_effect = config_dict.__getitem__
        model.config.prop.__getitem__.side_effect = prop_dict.__getitem__
        label_dict = {"en": "label"}
        model.label.__getitem__.side_effect = label_dict.__getitem__
        writer.populate_from_card_x_node_x_widget([model])
        self.assertEqual(m_po_file.append.call_count, 3)

    def test_missing_i18n_properties(self):
        "Test to ensure PO Entries are appended with appropriate english messageids (no translations)"
        m_po_file = Mock(polib.POFile)
        writer = ArchesPOWriter(m_po_file, "en", "en")
        model = MagicMock(CardXNodeXWidget)
        prop_dict = {"en": "configuration"}
        config_dict = {"label": prop_dict}
        model.config.__getitem__.side_effect = config_dict.__getitem__
        model.config.prop.__getitem__.side_effect = prop_dict.__getitem__
        label_dict = {"en": "label"}
        model.label.__getitem__.side_effect = label_dict.__getitem__
        writer.populate_from_card_x_node_x_widget([model])
        self.assertEqual(m_po_file.append.call_count, 1)

    def test_populate_from_cards(self):
        "Test to ensure PO Entries are appended with appropriate english messageids (no translations)"
        m_po_file = Mock(polib.POFile)
        writer = ArchesPOWriter(m_po_file, "en", "en")
        model = MagicMock(CardModel)
        name_dict = {"en": "name"}
        description_dict = {"en": "description"}
        instructions_dict = {"en": "instructions"}
        helptitle_dict = {"en": "helptitle"}
        helptext_dict = {"en": "helptext"}
        model.name.__getitem__.side_effect = name_dict.__getitem__
        model.description.__getitem__.side_effect = description_dict.__getitem__
        model.instructions.__getitem__.side_effect = instructions_dict.__getitem__
        model.helptitle.__getitem__.side_effect = helptitle_dict.__getitem__
        model.helptext.__getitem__.side_effect = helptext_dict.__getitem__
        writer.populate_from_cards([model])
        self.assertEqual(m_po_file.append.call_count, 5)
        self.assertEqual(m_po_file.append.call_args_list[0][0][0].msgid, "name")
        self.assertEqual(m_po_file.append.call_args_list[0][0][0].msgstr, "")
        self.assertEqual(m_po_file.append.call_args_list[4][0][0].msgid, "helptext")
        self.assertEqual(m_po_file.append.call_args_list[4][0][0].msgstr, "")

    def test_populate_from_cards_spanish(self):
        "Test to ensure PO Entries are appended with appropriate spanish translations"
        m_po_file = Mock(polib.POFile)
        writer = ArchesPOWriter(m_po_file, "en", "es")
        model = MagicMock(CardModel)
        name_dict = {"en": "name", "es": "nombre"}
        description_dict = {"en": "description", "es": "descripción"}
        instructions_dict = {"en": "instructions", "es": "instrucciones"}
        helptitle_dict = {"en": "helptitle", "es": "texto de ayuda"}
        helptext_dict = {"en": "helptext", "es": "título de la ayuda"}
        model.name.__getitem__.side_effect = name_dict.__getitem__
        model.name.__contains__.side_effect = name_dict.__contains__
        model.description.__getitem__.side_effect = description_dict.__getitem__
        model.description.__contains__.side_effect = description_dict.__contains__
        model.instructions.__getitem__.side_effect = instructions_dict.__getitem__
        model.instructions.__contains__.side_effect = instructions_dict.__contains__
        model.helptitle.__getitem__.side_effect = helptitle_dict.__getitem__
        model.helptitle.__contains__.side_effect = helptitle_dict.__contains__
        model.helptext.__getitem__.side_effect = helptext_dict.__getitem__
        model.helptext.__contains__.side_effect = helptext_dict.__contains__
        writer.populate_from_cards([model])
        self.assertEqual(m_po_file.append.call_count, 5)
        self.assertEqual(m_po_file.append.call_args_list[0][0][0].msgid, "name")
        self.assertEqual(m_po_file.append.call_args_list[0][0][0].msgstr, "nombre")
        self.assertEqual(m_po_file.append.call_args_list[4][0][0].msgid, "helptext")
        self.assertEqual(
            m_po_file.append.call_args_list[4][0][0].msgstr, "título de la ayuda"
        )

    def test_po_write_duplicate_exception_caught(self):
        def throw_value_error(val):
            raise ValueError()

        m_po_file = MagicMock(polib.POFile)
        m_po_file.append.side_effect = throw_value_error
        model = MagicMock(CardXNodeXWidget)
        prop_dict = {"en": "configuration"}
        config_dict = {"i18n_properties": ["prop"], "prop": prop_dict}
        model.config.__getitem__.side_effect = config_dict.__getitem__
        model.config.prop.__getitem__.side_effect = prop_dict.__getitem__
        label_dict = {"en": "label"}
        model.label.__getitem__.side_effect = label_dict.__getitem__
        writer = ArchesPOWriter(m_po_file, "en", "es")
        writer.populate_from_card_x_node_x_widget([model])

    def test_arches_po_loader(self):
        """happy path test of arches po loader"""
        m_po_file = MagicMock(polib.POFile)

        m_all_method_cardxnodexwidgets = Mock()
        m_cardxnodexwidget = MagicMock(CardXNodeXWidget)
        i18n_json_dict = {
            "i18n_properties": ["test", "test2"],
            "test": {"en": "test"},
            "test2": {"en": "test2"},
        }
        m_i18n_json_field = MagicMock(I18n_JSON)
        m_i18n_json_field.__getitem__ = Mock()
        m_i18n_json_field.__getitem__.side_effect = i18n_json_dict.__getitem__
        m_cardxnodexwidget.config = m_i18n_json_field
        m_cardxnodexwidget.save.return_value = None
        m_all_method_cardxnodexwidgets.return_value = [m_cardxnodexwidget]
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets

        m_all_method_cardmodel = Mock()
        m_card = MagicMock(CardModel)
        m_card.save.return_value = None
        m_all_method_cardmodel.return_value = [m_card]
        CardModel.objects.all = m_all_method_cardmodel

        loader = ArchesPOLoader(m_po_file, "en", "es")
        loader.load()

        m_card.save.assert_called()
        m_cardxnodexwidget.save.assert_called()

    def test_malformed_i18n_properties(self):
        """missing i18n_properties"""
        m_po_file = MagicMock(polib.POFile)

        m_all_method_cardxnodexwidgets = Mock()
        m_cardxnodexwidget = MagicMock(CardXNodeXWidget)
        i18n_json_dict = {"i18n_properties": ["test", "test2"], "test": {"en": "test"}}
        m_i18n_json_field = MagicMock(I18n_JSON)
        m_i18n_json_field.__getitem__ = Mock()
        m_i18n_json_field.__getitem__.side_effect = i18n_json_dict.__getitem__
        m_cardxnodexwidget.config = m_i18n_json_field
        m_cardxnodexwidget.save.return_value = None
        m_all_method_cardxnodexwidgets.return_value = [m_cardxnodexwidget]
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets

        m_all_method_cardmodel = Mock()
        m_card = MagicMock(CardModel)
        m_card.save.return_value = None
        m_all_method_cardmodel.return_value = [m_card]
        CardModel.objects.all = m_all_method_cardmodel

        loader = ArchesPOLoader(m_po_file, "en", "es")
        loader.load()

        m_card.save.assert_called()
        m_cardxnodexwidget.save.assert_called()

    def test_po_loader_no_i18n_properties(self):
        """Tests removing entries from the database when PO entry is empty string"""
        m_po_entry = MagicMock(polib.POEntry)
        m_po_entry.msgid = "doom"
        m_po_entry.msgstr = ""
        m_po_file = MagicMock(polib.POFile)
        m_po_file.find.return_value = m_po_entry

        m_all_method_cardmodel = Mock()
        m_card = MagicMock(CardModel)
        m_card.save.return_value = None
        m_i18n_string = MagicMock(I18n_String)
        m_card.name = m_i18n_string
        i18n_string_dict = {"en": "doom", "es": ""}
        m_i18n_string.__getitem__.side_effect = i18n_string_dict.__getitem__
        m_i18n_string.pop = Mock()
        m_all_method_cardmodel.return_value = [m_card]

        m_all_method_cardxnodexwidgets = Mock()
        m_cardxnodexwidget = MagicMock(CardXNodeXWidget)
        i18n_json_dict = {"test": {"en": "test"}, "label": "doom"}
        m_i18n_json_field = MagicMock(I18n_JSON)
        m_i18n_json_field.__getitem__ = Mock()
        m_i18n_json_field.__getitem__.side_effect = i18n_json_dict.__getitem__
        m_cardxnodexwidget.config = m_i18n_json_field
        m_cardxnodexwidget.save.return_value = None
        m_all_method_cardxnodexwidgets.return_value = [m_cardxnodexwidget]
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets

        loader = ArchesPOLoader(m_po_file, "en", "es")
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets
        CardModel.objects.all = m_all_method_cardmodel
        loader.load()

        m_card.save.assert_called()
        m_cardxnodexwidget.save.assert_called()
        self.assertEqual(m_i18n_string.pop.call_count, 1)

    def test_arches_po_loader_removal(self):
        """Tests removing entries from the database when PO entry is empty string"""
        m_po_entry = MagicMock(polib.POEntry)
        m_po_entry.msgid = "doom"
        m_po_entry.msgstr = ""
        m_po_file = MagicMock(polib.POFile)
        m_po_file.find.return_value = m_po_entry

        m_all_method_cardmodel = Mock()
        m_card = MagicMock(CardModel)
        m_card.save.return_value = None
        m_i18n_string = MagicMock(I18n_String)
        m_card.name = m_i18n_string
        i18n_string_dict = {"en": "doom", "es": ""}
        m_i18n_string.__getitem__.side_effect = i18n_string_dict.__getitem__
        m_i18n_string.pop = Mock()
        m_all_method_cardmodel.return_value = [m_card]

        m_all_method_cardxnodexwidgets = Mock()
        m_cardxnodexwidget = MagicMock(CardXNodeXWidget)
        i18n_json_dict = {"i18n_properties": ["test", "test2"], "test": {"en": "test"}}
        m_i18n_json_field = MagicMock(I18n_JSON)
        m_i18n_json_field.__getitem__ = Mock()
        m_i18n_json_field.__getitem__.side_effect = i18n_json_dict.__getitem__
        m_cardxnodexwidget.config = m_i18n_json_field
        m_cardxnodexwidget.save.return_value = None
        m_all_method_cardxnodexwidgets.return_value = [m_cardxnodexwidget]
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets

        loader = ArchesPOLoader(m_po_file, "en", "es")
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets
        CardModel.objects.all = m_all_method_cardmodel
        loader.load()

        m_card.save.assert_called()
        m_cardxnodexwidget.save.assert_called()
        self.assertEqual(m_i18n_string.pop.call_count, 1)

        i18n_json_dict["test2"] = m_i18n_string
        loader.load()
        self.assertEqual(m_i18n_string.pop.call_count, 3)

        # test that a msgid key won't blow up the whole import
        loader = ArchesPOLoader(m_po_file, "ar", "es")
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets
        CardModel.objects.all = m_all_method_cardmodel
        loader.load()

    def test_arches_po_loader_no_load_same_language(self):
        """Tests attempting to load same language - do not"""
        m_po_entry = MagicMock(polib.POEntry)
        m_po_entry.msgid = "doom"
        m_po_entry.msgstr = ""
        m_po_file = MagicMock(polib.POFile)
        m_po_file.find.return_value = m_po_entry

        m_all_method_cardmodel = Mock()
        m_card = MagicMock(CardModel)
        m_card.save.return_value = None
        m_all_method_cardmodel.return_value = [m_card]

        m_all_method_cardxnodexwidgets = Mock()
        m_cardxnodexwidget = MagicMock(CardXNodeXWidget)
        m_cardxnodexwidget.save.return_value = None
        m_all_method_cardxnodexwidgets.return_value = [m_cardxnodexwidget]

        loader = ArchesPOLoader(m_po_file, "en", "en")
        CardXNodeXWidget.objects.all = m_all_method_cardxnodexwidgets
        CardModel.objects.all = m_all_method_cardmodel
        loader.load()

        m_card.save.assert_not_called()
        m_cardxnodexwidget.save.assert_not_called()

    def test_get_all_po_files(self):
        fetcher = ArchesPOFileFetcher()
        m_mkdir = MagicMock()
        pathlib.Path.mkdir = m_mkdir
        settings.LANGUAGES = [
            ("de", ("German")),
            ("en", ("English")),
            ("en-gb", ("British English")),
            ("es", ("Spanish")),
            ("ar", ("Arabic")),
        ]
        m_pofile = MagicMock(polib.POFile)
        m_pofactory = MagicMock()
        m_pofactory.return_value = m_pofile

        builtins.open = MagicMock()
        polib.pofile = m_pofactory
        files = fetcher.get_po_files(None, True)

        m_pofactory.assert_called()
        m_mkdir.assert_called()
        self.assertEqual(len(settings.LANGUAGES), len(files))
