# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import os.path
import sys
import tempfile
import types
import unittest
from contextlib import contextmanager

from django.template import Context, TemplateDoesNotExist
from django.template.engine import Engine
from django.test import SimpleTestCase, override_settings
from django.utils import six

from .utils import TEMPLATE_DIR

try:
    import pkg_resources
except ImportError:
    pkg_resources = None


class CachedLoaderTests(SimpleTestCase):

    def create_engine(self, **kwargs):
        return Engine(
            loaders=[
                ('django.template.loaders.cached.Loader', [
                    'django.template.loaders.filesystem.Loader',
                ]),
            ],
        )

    def test_templatedir_caching(self):
        """
        #13573 -- Template directories should be part of the cache key.
        """
        engine = self.create_engine()

        # Retrieve a template specifying a template directory to check
        t1, name = engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'first'),))
        # Now retrieve the same template name, but from a different directory
        t2, name = engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'second'),))

        # The two templates should not have the same content
        self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))

    def test_missing_template_is_cached(self):
        """
        #19949 -- TemplateDoesNotExist exceptions should be cached.
        """
        engine = self.create_engine()
        loader = engine.template_loaders[0]

        self.assertFalse('missing.html' in loader.template_cache)

        with self.assertRaises(TemplateDoesNotExist):
            loader.load_template("missing.html")

        self.assertEqual(
            loader.template_cache["missing.html"],
            TemplateDoesNotExist,
            "Cached loader failed to cache the TemplateDoesNotExist exception",
        )


@unittest.skipUnless(pkg_resources, 'setuptools is not installed')
class EggLoaderTests(SimpleTestCase):

    @contextmanager
    def create_egg(self, name, resources):
        """
        Creates a mock egg with a list of resources.

        name: The name of the module.
        resources: A dictionary of template names mapped to file-like objects.
        """

        if six.PY2:
            name = name.encode('utf-8')

        class MockLoader(object):
            pass

        class MockProvider(pkg_resources.NullProvider):
            def __init__(self, module):
                pkg_resources.NullProvider.__init__(self, module)
                self.module = module

            def _has(self, path):
                return path in self.module._resources

            def _isdir(self, path):
                return False

            def get_resource_stream(self, manager, resource_name):
                return self.module._resources[resource_name]

            def _get(self, path):
                return self.module._resources[path].read()

            def _fn(self, base, resource_name):
                return os.path.normcase(resource_name)

        egg = types.ModuleType(name)
        egg.__loader__ = MockLoader()
        egg.__path__ = ['/some/bogus/path/']
        egg.__file__ = '/some/bogus/path/__init__.pyc'
        egg._resources = resources
        sys.modules[name] = egg
        pkg_resources._provider_factories[MockLoader] = MockProvider

        try:
            yield
        finally:
            del sys.modules[name]
            del pkg_resources._provider_factories[MockLoader]

    def setUp(self):
        engine = Engine(loaders=[
            'django.template.loaders.eggs.Loader',
        ])
        self.loader = engine.template_loaders[0]

    def test_existing(self):
        templates = {
            os.path.normcase('templates/y.html'): six.StringIO("y"),
        }

        with self.create_egg('egg', templates):
            with override_settings(INSTALLED_APPS=['egg']):
                contents, template_name = self.loader.load_template_source("y.html")
                self.assertEqual(contents, "y")
                self.assertEqual(template_name, "egg:egg:templates/y.html")

    def test_non_existing(self):
        """
        Template loading fails if the template is not in the egg.
        """
        with self.create_egg('egg', {}):
            with override_settings(INSTALLED_APPS=['egg']):
                with self.assertRaises(TemplateDoesNotExist):
                    self.loader.load_template_source("not-existing.html")

    def test_not_installed(self):
        """
        Template loading fails if the egg is not in INSTALLED_APPS.
        """
        templates = {
            os.path.normcase('templates/y.html'): six.StringIO("y"),
        }

        with self.create_egg('egg', templates):
            with self.assertRaises(TemplateDoesNotExist):
                self.loader.load_template_source("y.html")


class FileSystemLoaderTests(SimpleTestCase):

    def setUp(self):
        self.engine = Engine(dirs=[TEMPLATE_DIR])

    @contextmanager
    def set_dirs(self, dirs):
        original_dirs = self.engine.dirs
        self.engine.dirs = dirs
        try:
            yield
        finally:
            self.engine.dirs = original_dirs

    @contextmanager
    def source_checker(self, dirs):
        loader = self.engine.template_loaders[0]

        def check_sources(path, expected_sources):
            expected_sources = [os.path.abspath(s) for s in expected_sources]
            self.assertEqual(
                list(loader.get_template_sources(path)),
                expected_sources,
            )

        with self.set_dirs(dirs):
            yield check_sources

    def test_directory_security(self):
        with self.source_checker(['/dir1', '/dir2']) as check_sources:
            check_sources('index.html', ['/dir1/index.html', '/dir2/index.html'])
            check_sources('/etc/passwd', [])
            check_sources('etc/passwd', ['/dir1/etc/passwd', '/dir2/etc/passwd'])
            check_sources('../etc/passwd', [])
            check_sources('../../../etc/passwd', [])
            check_sources('/dir1/index.html', ['/dir1/index.html'])
            check_sources('../dir2/index.html', ['/dir2/index.html'])
            check_sources('/dir1blah', [])
            check_sources('../dir1blah', [])

    def test_unicode_template_name(self):
        with self.source_checker(['/dir1', '/dir2']) as check_sources:
            # UTF-8 bytestrings are permitted.
            check_sources(b'\xc3\x85ngstr\xc3\xb6m', ['/dir1/Ångström', '/dir2/Ångström'])
            # Unicode strings are permitted.
            check_sources('Ångström', ['/dir1/Ångström', '/dir2/Ångström'])

    def test_utf8_bytestring(self):
        """
        Invalid UTF-8 encoding in bytestrings should raise a useful error
        """
        engine = Engine()
        loader = engine.template_loaders[0]
        with self.assertRaises(UnicodeDecodeError):
            list(loader.get_template_sources(b'\xc3\xc3', ['/dir1']))

    def test_unicode_dir_name(self):
        with self.source_checker([b'/Stra\xc3\x9fe']) as check_sources:
            check_sources('Ångström', ['/Straße/Ångström'])
            check_sources(b'\xc3\x85ngstr\xc3\xb6m', ['/Straße/Ångström'])

    @unittest.skipUnless(
        os.path.normcase('/TEST') == os.path.normpath('/test'),
        "This test only runs on case-sensitive file systems.",
    )
    def test_case_sensitivity(self):
        with self.source_checker(['/dir1', '/DIR2']) as check_sources:
            check_sources('index.html', ['/dir1/index.html', '/DIR2/index.html'])
            check_sources('/DIR1/index.HTML', ['/DIR1/index.HTML'])

    def test_file_does_not_exist(self):
        with self.assertRaises(TemplateDoesNotExist):
            self.engine.get_template('doesnotexist.html')

    @unittest.skipIf(
        sys.platform == 'win32',
        "Python on Windows doesn't have working os.chmod().",
    )
    def test_permissions_error(self):
        with tempfile.NamedTemporaryFile() as tmpfile:
            tmpdir = os.path.dirname(tmpfile.name)
            tmppath = os.path.join(tmpdir, tmpfile.name)
            os.chmod(tmppath, 0o0222)
            with self.set_dirs([tmpdir]):
                with self.assertRaisesMessage(IOError, 'Permission denied'):
                    self.engine.get_template(tmpfile.name)

    def test_notafile_error(self):
        with self.assertRaises(IOError):
            self.engine.get_template('first')


class AppDirectoriesLoaderTest(SimpleTestCase):

    def setUp(self):
        self.engine = Engine(
            loaders=['django.template.loaders.app_directories.Loader'],
        )

    @override_settings(INSTALLED_APPS=['template_tests'])
    def test_load_template(self):
        self.engine.get_template('index.html')

    @override_settings(INSTALLED_APPS=[])
    def test_not_installed(self):
        with self.assertRaises(TemplateDoesNotExist):
            self.engine.get_template('index.html')