from __future__ import unicode_literals

import datetime
import os
import re
import sys
import types

from django.conf import settings
from django.http import (HttpResponse, HttpResponseServerError,
    HttpResponseNotFound, HttpRequest, build_request_repr)
from django.template import Template, Context, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint
from django.utils.datastructures import MultiValueDict
from django.utils.html import escape
from django.utils.encoding import force_bytes, smart_text
from django.utils.module_loading import import_by_path
from django.utils import six

HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE')

CLEANSED_SUBSTITUTE = '********************'


def linebreak_iter(template_source):
    yield 0
    p = template_source.find('\n')
    while p >= 0:
        yield p + 1
        p = template_source.find('\n', p + 1)
    yield len(template_source) + 1


def cleanse_setting(key, value):
    """Cleanse an individual setting key/value of sensitive content.

    If the value is a dictionary, recursively cleanse the keys in
    that dictionary.
    """
    try:
        if HIDDEN_SETTINGS.search(key):
            cleansed = CLEANSED_SUBSTITUTE
        else:
            if isinstance(value, dict):
                cleansed = dict((k, cleanse_setting(k, v)) for k, v in value.items())
            else:
                cleansed = value
    except TypeError:
        # If the key isn't regex-able, just return as-is.
        cleansed = value

    if callable(cleansed):
        cleansed.do_not_call_in_templates = True

    return cleansed


def get_safe_settings():
    "Returns a dictionary of the settings module, with sensitive settings blurred out."
    settings_dict = {}
    for k in dir(settings):
        if k.isupper():
            settings_dict[k] = cleanse_setting(k, getattr(settings, k))
    return settings_dict


def technical_500_response(request, exc_type, exc_value, tb):
    """
    Create a technical server error response. The last three arguments are
    the values returned from sys.exc_info() and friends.
    """
    reporter = ExceptionReporter(request, exc_type, exc_value, tb)
    if request.is_ajax():
        text = reporter.get_traceback_text()
        return HttpResponseServerError(text, content_type='text/plain')
    else:
        html = reporter.get_traceback_html()
        return HttpResponseServerError(html, content_type='text/html')

# Cache for the default exception reporter filter instance.
default_exception_reporter_filter = None


def get_exception_reporter_filter(request):
    global default_exception_reporter_filter
    if default_exception_reporter_filter is None:
        # Load the default filter for the first time and cache it.
        default_exception_reporter_filter = import_by_path(
            settings.DEFAULT_EXCEPTION_REPORTER_FILTER)()
    if request:
        return getattr(request, 'exception_reporter_filter', default_exception_reporter_filter)
    else:
        return default_exception_reporter_filter


class ExceptionReporterFilter(object):
    """
    Base for all exception reporter filter classes. All overridable hooks
    contain lenient default behaviors.
    """

    def get_request_repr(self, request):
        if request is None:
            return repr(None)
        else:
            return build_request_repr(request, POST_override=self.get_post_parameters(request))

    def get_post_parameters(self, request):
        if request is None:
            return {}
        else:
            return request.POST

    def get_traceback_frame_variables(self, request, tb_frame):
        return list(six.iteritems(tb_frame.f_locals))


class SafeExceptionReporterFilter(ExceptionReporterFilter):
    """
    Use annotations made by the sensitive_post_parameters and
    sensitive_variables decorators to filter out sensitive information.
    """

    def is_active(self, request):
        """
        This filter is to add safety in production environments (i.e. DEBUG
        is False). If DEBUG is True then your site is not safe anyway.
        This hook is provided as a convenience to easily activate or
        deactivate the filter on a per request basis.
        """
        return settings.DEBUG is False

    def get_cleansed_multivaluedict(self, request, multivaluedict):
        """
        Replaces the keys in a MultiValueDict marked as sensitive with stars.
        This mitigates leaking sensitive POST parameters if something like
        request.POST['nonexistent_key'] throws an exception (#21098).
        """
        sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', [])