# -*- coding: utf-8 -*-
# This coding header is significant for tests, as the debug view is parsing
# files to search for such a header to decode the source file content
from __future__ import unicode_literals

import importlib
import inspect
import os
import re
import shutil
import sys
from tempfile import NamedTemporaryFile, mkdtemp, mkstemp
from unittest import skipIf

from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
from django.template.base import TemplateDoesNotExist
from django.test import TestCase, RequestFactory, override_settings
from django.test.utils import override_with_test_loader
from django.utils.encoding import force_text, force_bytes
from django.utils import six
from django.views.debug import CallableSettingWrapper, ExceptionReporter

from .. import BrokenException, except_args
from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
    custom_exception_reporter_filter_view, sensitive_method_view,
    sensitive_args_function_caller, sensitive_kwargs_function_caller,
    multivalue_dict_key_error)


class CallableSettingWrapperTests(TestCase):
    """ Unittests for CallableSettingWrapper
    """
    def test_repr(self):
        class WrappedCallable(object):
            def __repr__(self):
                return "repr from the wrapped callable"

            def __call__(self):
                pass

        actual = repr(CallableSettingWrapper(WrappedCallable()))
        self.assertEqual(actual, "repr from the wrapped callable")


@override_settings(DEBUG=True, TEMPLATE_DEBUG=True,
                   ROOT_URLCONF="view_tests.urls")
class DebugViewTests(TestCase):

    def test_files(self):
        response = self.client.get('/raises/')
        self.assertEqual(response.status_code, 500)

        data = {
            'file_data.txt': SimpleUploadedFile('file_data.txt', b'haha'),
        }
        response = self.client.post('/raises/', data)
        self.assertContains(response, 'file_data.txt', status_code=500)
        self.assertNotContains(response, 'haha', status_code=500)

    def test_400(self):
        # Ensure that when DEBUG=True, technical_500_template() is called.
        response = self.client.get('/raises400/')
        self.assertContains(response, '<div class="context" id="', status_code=400)

    def test_403(self):
        # Ensure no 403.html template exists to test the default case.
        with override_with_test_loader({}):
            response = self.client.get('/raises403/')
            self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)

    def test_403_template(self):
        # Set up a test 403.html template.
        with override_with_test_loader({'403.html': 'This is a test template '
                                        'for a 403 Forbidden error.'}):
            response = self.client.get('/raises403/')
            self.assertContains(response, 'test template', status_code=403)

    def test_404(self):
        response = self.client.get('/raises404/')
        self.assertEqual(response.status_code, 404)

    def test_raised_404(self):
        response = self.client.get('/views/raises404/')
        self.assertContains(response, "<code>not-in-urls</code>, didn't match", status_code=404)

    def test_404_not_in_urls(self):
        response = self.client.get('/not-in-urls')
        self.assertNotContains(response, "Raised by:", status_code=404)
        self.assertContains(response, "<code>not-in-urls</code>, didn't match", status_code=404)

    def test_technical_404(self):
        response = self.client.get('/views/technical404/')
        self.assertContains(response, "Raised by:", status_code=404)
        self.assertContains(response, "view_tests.views.technical404", status_code=404)

    def test_classbased_technical_404(self):
        response = self.client.get('/views/classbased404/')
        self.assertContains(response, "Raised by:", status_code=404)
        self.assertContains(response, "view_tests.views.Http404View", status_code=404)

    def test_view_exceptions(self):
        for n in range(len(except_args)):
            self.assertRaises(BrokenException, self.client.get,
                reverse('view_exception', args=(n,)))

    def test_non_l10ned_numeric_ids(self):
        """
        Numeric IDs and fancy traceback context blocks line numbers shouldn't be localized.
        """
        with self.settings(DEBUG=True, USE_L10N=True):
            response = self.client.get('/raises500/')
            # We look for a HTML fragment of the form
            # '<div class="context" id="c38123208">', not '<div class="context" id="c38,123,208"'
            self.assertContains(response, '<div class="context" id="', status_code=500)
            match = re.search(b'<div class="context" id="(?P<id>[^"]+)">', response.content)
            self.assertIsNotNone(match)
            id_repr = match.group('id')
            self.assertFalse(re.search(b'[^c0-9]', id_repr),
                             "Numeric IDs in debug response HTML page shouldn't be localized (value: %s)." % id_repr)

    def test_template_exceptions(self):
        for n in range(len(except_args)):
            try:
                self.client.get(reverse('template_exception', args=(n,)))
            except Exception:
                raising_loc = inspect.trace()[-1][-2][0].strip()
                self.assertNotEqual(raising_loc.find('raise BrokenException'), -1,
                    "Failed to find 'raise BrokenException' in last frame of traceback, instead found: %s" %
                        raising_loc)

    def test_template_loader_postmortem(self):
        """Tests for not existing file"""
        template_name = "notfound.html"
        with NamedTemporaryFile(prefix=template_name) as tempfile:
            tempdir = os.path.dirname(tempfile.name)
            template_path = os.path.join(tempdir, template_name)
            with override_settings(TEMPLATE_DIRS=(tempdir,)):
                response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
            self.assertContains(response, "%s (File does not exist)" % template_path, status_code=500, count=1)

    @skipIf(sys.platform == "win32", "Python on Windows doesn't have working os.chmod() and os.access().")
    def test_template_loader_postmortem_notreadable(self):
        """Tests for not readable file"""
        with NamedTemporaryFile() as tempfile:
            template_name = tempfile.name
            tempdir = os.path.dirname(tempfile.name)
            template_path = os.path.join(tempdir, template_name)
            os.chmod(template_path, 0o0222)
            with override_settings(TEMPLATE_DIRS=(tempdir,)):
                response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
            self.assertContains(response, "%s (File is not readable)" % template_path, status_code=500, count=1)

    def test_template_loader_postmortem_notafile(self):
        """Tests for not being a file"""
        try:
            template_path = mkdtemp()
            template_name = os.path.basename(template_path)
            tempdir = os.path.dirname(template_path)
            with override_settings(TEMPLATE_DIRS=(tempdir,)):
                response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
            self.assertContains(response, "%s (Not a file)" % template_path, status_code=500, count=1)
        finally:
            shutil.rmtree(template_path)

    def test_no_template_source_loaders(self):
        """
        Make sure if you don't specify a template, the debug view doesn't blow up.
        """
        self.assertRaises(TemplateDoesNotExist, self.client.get, '/render_no_template/')

    @override_settings(ROOT_URLCONF='view_tests.default_urls')
    def test_default_urlconf_template(self):
        """
        Make sure that the default urlconf template is shown shown instead
        of the technical 404 page, if the user has not altered their
        url conf yet.
        """
        response = self.client.get('/')
        self.assertContains(
            response,
            "<h2>Congratulations on your first Django-powered page.</h2>"
        )

    @override_settings(ROOT_URLCONF='view_tests.regression_21530_urls')
    def test_regression_21530(self):
        """
        Regression test for bug #21530.

        If the admin app include is replaced with exactly one url
        pattern, then the technical 404 template should be displayed.

        The bug here was that an AttributeError caused a 500 response.
        """
        response = self.client.get('/')
        self.assertContains(
            response,
            "Page not found <span>(404)</span>",
            status_code=404
        )


class ExceptionReporterTests(TestCase):
    rf = RequestFactory()

    def test_request_and_exception(self):
        "A simple exception report can be generated"
        try:
            request = self.rf.get('/test_view/')
            raise ValueError("Can't find my keys")
        except ValueError:
            exc_type, exc_value, tb = sys.exc_info()
        reporter = ExceptionReporter(request, exc_type, exc_value, tb)
        html = reporter.get_traceback_html()
        self.assertIn('<h1>ValueError at /test_view/</h1>', html)
        self.assertIn('<pre class="exception_value">Can&#39;t find my keys</pre>', html)
        self.assertIn('<th>Request Method:</th>', html)
        self.assertIn('<th>Request URL:</th>', html)
        self.assertIn('<th>Exception Type:</th>', html)
        self.assertIn('<th>Exception Value:</th>', html)
        self.assertIn('<h2>Traceback ', html)
        self.assertIn('<h2>Request information</h2>', html)