#       Licensed to the Apache Software Foundation (ASF) under one
#       or more contributor license agreements.  See the NOTICE file
#       distributed with this work for additional information
#       regarding copyright ownership.  The ASF licenses this file
#       to you under the Apache License, Version 2.0 (the
#       "License"); you may not use this file except in compliance
#       with the License.  You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#       Unless required by applicable law or agreed to in writing,
#       software distributed under the License is distributed on an
#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#       KIND, either express or implied.  See the License for the
#       specific language governing permissions and limitations
#       under the License.

import functools

import webob
import tg.decorators
from decorator import decorator
from tg import request
import pygments.plugin

from allura.lib import helpers as h

_patched = False


def apply():
    global _patched  # noqa: PLW0603
    if _patched:
        return
    _patched = True

    old_lookup_template_engine = tg.decorators.Decoration.lookup_template_engine

    @h.monkeypatch(tg.decorators.Decoration)
    def lookup_template_engine(self, request):
        '''Wrapper to handle totally borked-up HTTP-ACCEPT headers'''
        try:
            return old_lookup_template_engine(self, request)
        except Exception:
            pass
        environ = dict(request.environ, HTTP_ACCEPT='*/*')
        request = webob.Request(environ)
        return old_lookup_template_engine(self, request)

    @h.monkeypatch(tg, tg.decorators)
    def override_template(controller, template):
        '''Copy-pasted patch to allow multiple colons in a template spec'''
        if hasattr(controller, 'decoration'):
            decoration = controller.decoration
        else:
            return
        if hasattr(decoration, 'engines'):
            engines = decoration.engines
        else:
            return

        for content_type, content_engine in engines.items():
            template = template.split(':', 1)
            template.extend(content_engine[2:])
            try:
                override_mapping = request._override_mapping
            except AttributeError:
                override_mapping = request._override_mapping = {}
            override_mapping[controller.__func__] = {content_type: template}

    @h.monkeypatch(tg, tg.decorators)
    @decorator
    def without_trailing_slash(func, *args, **kwargs):
        '''Monkey-patched to use 301 redirects for SEO, and handle query strings, and HEAD'''
        __traceback_hide__ = 'before_and_this'  # for paste/werkzeug shorter traces
        response_type = getattr(request, 'response_type', None)
        if (request.method in ('GET', 'HEAD') and request.path.endswith('/') and not response_type):
            location = request.path_url[:-1]
            if request.query_string:
                location += '?' + request.query_string
            raise webob.exc.HTTPMovedPermanently(location=location)
        return func(*args, **kwargs)

    @h.monkeypatch(tg, tg.decorators)
    @decorator
    def with_trailing_slash(func, *args, **kwargs):
        '''Monkey-patched to use 301 redirects for SEO, and handle query strings, and HEAD'''
        __traceback_hide__ = 'before_and_this'  # for paste/werkzeug shorter traces
        response_type = getattr(request, 'response_type', None)
        if (request.method in ('GET', 'HEAD') and not request.path.endswith('/') and not response_type):
            location = request.path_url + '/'
            if request.query_string:
                location += '?' + request.query_string
            raise webob.exc.HTTPMovedPermanently(location=location)
        return func(*args, **kwargs)

    # http://blog.watchfire.com/wfblog/2011/10/json-based-xss-exploitation.html
    # change < to its unicode escape when rendering JSON out of turbogears
    # This is to avoid IE9 and earlier, which don't know the json content type
    # and may attempt to render JSON data as HTML if the URL ends in .html
    original_tg_jsonify_JSONEncoder_encode = tg.jsonify.JSONEncoder.encode

    @h.monkeypatch(tg.jsonify.JSONEncoder)
    def encode(self, o):
        return original_tg_jsonify_JSONEncoder_encode(self, o).replace('<', r'\u003C')


old_controller_call = tg.controllers.DecoratedController._call


def newrelic():
    @h.monkeypatch(tg.controllers.DecoratedController,
                   tg.controllers.decoratedcontroller.DecoratedController)
    def _call(self, controller, *args, **kwargs):
        '''Set NewRelic transaction name to actual controller name'''
        __traceback_hide__ = 'before_and_this'  # for paste/werkzeug shorter traces
        import newrelic.agent
        controller_name = newrelic.agent.callable_name(controller)
        # https://docs.newrelic.com/docs/apm/agents/python-agent/python-agent-api/settransactionname-python-agent-api/
        # if a second internal request for /error/document happens, use a lower (1) priority so original name stays
        name_priority = 1 if 'ErrorController' in controller_name else 2
        newrelic.agent.set_transaction_name(controller_name, priority=name_priority)
        return old_controller_call(self, controller, *args, **kwargs)

    import newrelic.api.error_trace
    import newrelic.api.function_trace
    # These are based on newrelic/hooks/framework_pylons.py since TG is similar to Pylons
    # capture exceptions:
    newrelic.api.error_trace.wrap_error_trace('tg.wsgiapp', 'TGApp.__call__')
    # record as its own component in transaction breakdown; should help distinguish middleware vs controller time
    newrelic.api.function_trace.wrap_function_trace('tg.controllers.tgcontroller', 'TGController.__call__')
