import os.path
from importlib.machinery import SourceFileLoader

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.template import TemplateDoesNotExist, loader
from django.test.utils import override_settings
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _

from cms import constants
from cms.test_utils.testcases import CMSTestCase
from cms.utils.conf import get_cms_setting

PATH_PREFIX = os.path.join("inner_dir", "custom_templates")
GOOD_PATH = os.path.join(settings.PROJECT_PATH, "project", "templates", PATH_PREFIX)
BAD_PATH = os.path.join(settings.PROJECT_PATH, "project", "custom_templates")
SITE_PATH = {
    1: os.path.join(settings.PROJECT_PATH, "project", "templates", PATH_PREFIX),
    2: os.path.join(settings.PROJECT_PATH, "project", "templates", "%s_2" % PATH_PREFIX),
}


class TemplatesConfig(CMSTestCase):
    def test_templates(self):
        """
        Tests that the plain CMS_TEMPLATES works as usual
        """
        original_files = [template[0] for template in settings.CMS_TEMPLATES]
        files = [template[0] for template in get_cms_setting("TEMPLATES")]
        if get_cms_setting("TEMPLATE_INHERITANCE"):
            original_files.append(constants.TEMPLATE_INHERITANCE_MAGIC)
        self.assertEqual(len(files), 6)
        self.assertEqual(set(files), set(original_files))

    @override_settings(CMS_TEMPLATES_DIR=GOOD_PATH)
    def test_custom_templates(self):
        """
        Test that using CMS_TEMPLATES_DIR both template list and template labels are extracted from the new directory
        """
        config_path = os.path.join(settings.CMS_TEMPLATES_DIR, "__init__.py")
        mod = None
        try:
            mod = SourceFileLoader("mod", config_path).load_module()
        except FileNotFoundError:
            print(f"file not found at {config_path}")
        except Exception as e:
            print(f"An error occurred: {e}")
        if mod:
            original_labels = [force_str(_(template[1])) for template in mod.TEMPLATES.items()]
            original_files = [os.path.join(PATH_PREFIX, template[0].strip()) for template in mod.TEMPLATES.items()]
            templates = get_cms_setting("TEMPLATES")
            self.assertEqual(len(templates), 3)
            labels = [force_str(template[1]) for template in templates]
            files = [template[0] for template in templates]
            if get_cms_setting("TEMPLATE_INHERITANCE"):
                original_labels.append(force_str(_("Inherit the template of the nearest ancestor")))
                original_files.append(constants.TEMPLATE_INHERITANCE_MAGIC)
            self.assertEqual(set(labels), set(original_labels))
            self.assertEqual(set(files), set(original_files))

    @override_settings(CMS_TEMPLATES_DIR=GOOD_PATH)
    def test_custom_templates_loading(self):
        """
        Checking that templates can be loaded by the template loader
        """
        templates = get_cms_setting("TEMPLATES")
        for template in templates:
            if template[0] != constants.TEMPLATE_INHERITANCE_MAGIC:
                tpl = loader.get_template(template[0])
                self.assertEqual(tpl.template.name, template[0])

    @override_settings(CMS_TEMPLATES_DIR=SITE_PATH)
    def test_multisite(self):
        """
        Checking that templates can be loaded by the template loader
        """
        templates = get_cms_setting("TEMPLATES")
        for template in templates:
            if template[0] != constants.TEMPLATE_INHERITANCE_MAGIC:
                self.assertTrue(template[0].find("%s/" % SITE_PATH[1]) >= -1)
        with self.settings(SITE_ID=2):
            templates = get_cms_setting("TEMPLATES")
            for template in templates:
                if template[0] != constants.TEMPLATE_INHERITANCE_MAGIC:
                    self.assertTrue(template[0].find("%s/" % SITE_PATH[2]) >= -1)

    @override_settings(CMS_TEMPLATES_DIR=BAD_PATH)
    def test_custom_templates_bad_dir(self):
        """
        Checking that templates can be loaded by the template loader
        """
        templates = get_cms_setting("TEMPLATES")
        for template in templates:
            if template[0] != constants.TEMPLATE_INHERITANCE_MAGIC:
                with self.assertRaises(TemplateDoesNotExist):
                    loader.get_template(template[0])

    def test_accepts_custom_django_template_backend(self):
        """
        Test that django CMS accepts template engines that inherit from DjangoTemplates
        """
        custom_templates_settings = [
            {
                "BACKEND": "cms.test_utils.project.template_backends.CustomDjangoTemplates",
                "DIRS": [],
                "OPTIONS": {
                    "context_processors": ["django.template.context_processors.request"],
                },
            }
        ]

        with override_settings(TEMPLATES=custom_templates_settings):
            from cms.utils.setup import validate_settings

            validate_settings()

    def test_rejects_non_django_template_backend(self):
        """
        Test that django CMS rejects template engines that don't inherit from DjangoTemplates
        """
        non_django_templates_settings = [
            {
                "BACKEND": "cms.test_utils.project.template_backends.NonDjangoTemplates",
                "DIRS": [],
                "OPTIONS": {
                    "context_processors": ["django.template.context_processors.request"],
                },
            }
        ]

        with override_settings(TEMPLATES=non_django_templates_settings):
            from cms.utils.setup import validate_settings

            with self.assertRaises(ImproperlyConfigured) as context:
                validate_settings()

            self.assertIn(
                "django.template.backends.django.DjangoTemplates",
                str(context.exception),
                "Should reject non-Django template backends",
            )
