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

import logging
import sys

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest, HttpResponse
from django.test import RequestFactory
from functools import wraps
from mock import patch
if False:
    from mypy_extensions import NoReturn
from typing import Any, Callable, Dict, Mapping, Optional, Text

from zerver.lib.request import JsonableError
from zerver.lib.test_classes import ZulipTestCase
from zerver.logging_handlers import AdminZulipHandler
from zerver.middleware import JsonErrorHandler
from zerver.views.compatibility import check_compatibility
from zerver.worker.queue_processors import QueueProcessingWorker

captured_request = None  # type: Optional[HttpRequest]
captured_exc_info = None
def capture_and_throw(domain=None):
    # type: (Optional[Text]) -> Callable
    def wrapper(view_func):
        # type: (Callable[..., HttpResponse]) -> Callable[..., HttpResponse]
        @wraps(view_func)
        def wrapped_view(request, *args, **kwargs):
            # type: (HttpRequest, *Any, **Any) -> NoReturn
            global captured_request
            captured_request = request
            try:
                raise Exception("Request error")
            except Exception as e:
                global captured_exc_info
                captured_exc_info = sys.exc_info()
                raise e
        return wrapped_view
    return wrapper

class AdminZulipHandlerTest(ZulipTestCase):
    logger = logging.getLogger('django')

    def setUp(self):
        # type: () -> None
        self.handler = AdminZulipHandler()
        # Prevent the exceptions we're going to raise from being printed
        # You may want to disable this when debugging tests
        settings.LOGGING_NOT_DISABLED = False

    def tearDown(self):
        # type: () -> None
        settings.LOGGING_NOT_DISABLED = True

    def get_admin_zulip_handler(self, logger):
        # type: (logging.Logger) -> Any

        # Ensure that AdminEmailHandler does not get filtered out
        # even with DEBUG=True.
        admin_email_handler = [
            h for h in logger.handlers
            if h.__class__.__name__ == "AdminZulipHandler"
        ][0]
        return admin_email_handler

    def test_basic(self):
        # type: () -> None
        """A random exception passes happily through AdminZulipHandler"""
        handler = self.get_admin_zulip_handler(self.logger)
        try:
            raise Exception("Testing Error!")
        except Exception:
            exc_info = sys.exc_info()
        record = self.logger.makeRecord('name', logging.ERROR, 'function', 16, 'message', None, exc_info)  # type: ignore # https://github.com/python/typeshed/pull/1100
        handler.emit(record)

    def run_handler(self, record):
        # type: (logging.LogRecord) -> Dict[str, Any]
        with patch('zerver.logging_handlers.queue_json_publish') as patched_publish:
            self.handler.emit(record)
            patched_publish.assert_called_once()
            event = patched_publish.call_args[0][1]
            self.assertIn("report", event)
            return event["report"]

    def test_request(self):
        # type: () -> None
        """A normal request is handled properly"""
        self.login("hamlet@zulip.com")
        with patch("zerver.decorator.rate_limit") as rate_limit_patch:
            rate_limit_patch.side_effect = capture_and_throw
            result = self.client_get("/json/users")
            self.assert_json_error(result, "Internal server error", status_code=500)
            rate_limit_patch.assert_called_once()

            global captured_request
            global captured_exc_info
            record = self.logger.makeRecord('name', logging.ERROR, 'function', 15, 'message', None, captured_exc_info)  # type: ignore # https://github.com/python/typeshed/pull/1100
            record.request = captured_request

            report = self.run_handler(record)
            self.assertIn("user_email", report)
            self.assertIn("message", report)
            self.assertIn("stack_trace", report)

            # Test that `add_request_metadata` throwing an exception is fine
            with patch("zerver.logging_handlers.traceback.print_exc"):
                with patch("zerver.logging_handlers.add_request_metadata",
                           side_effect=Exception("Unexpected exception!")):
                    report = self.run_handler(record)
            self.assertNotIn("user_email", report)
            self.assertIn("message", report)
            self.assertEqual(report["stack_trace"], "See /var/log/zulip/errors.log")

            # Check anonymous user is handled correctly
            record.request.user = AnonymousUser()
            report = self.run_handler(record)
            self.assertIn("user_email", report)
            self.assertIn("message", report)
            self.assertIn("stack_trace", report)

            # Now simulate a DisallowedHost exception
            def get_host_error():
                # type: () -> None
                raise Exception("Get Host Failure!")
            orig_get_host = record.request.get_host
            record.request.get_host = get_host_error
            report = self.run_handler(record)
            record.request.get_host = orig_get_host
            self.assertIn("user_email", report)
            self.assertIn("message", report)
            self.assertIn("stack_trace", report)

            # Test an exception_filter exception
            with patch("zerver.logging_handlers.get_exception_reporter_filter",
                       return_value=15):
                record.request.method = "POST"
                report = self.run_handler(record)
                record.request.method = "GET"
            self.assertIn("user_email", report)
            self.assertIn("message", report)
            self.assertIn("stack_trace", report)

            # Test the catch-all exception handler doesn't throw
            with patch('zerver.logging_handlers.queue_json_publish',
                       side_effect=Exception("queue error")):
                self.handler.emit(record)

            # Test the STAGING_ERROR_NOTIFICATIONS code path
            with self.settings(STAGING_ERROR_NOTIFICATIONS=True):
                with patch('zerver.lib.error_notify.notify_server_error',
                           side_effect=Exception("queue error")):
                    self.handler.emit(record)

            # Test no exc_info
            record.exc_info = None
            report = self.run_handler(record)
            self.assertIn("user_email", report)
            self.assertIn("message", report)
            self.assertEqual(report["stack_trace"], None)

            # Test arbitrary exceptions from request.user
            record.request.user = None
            with patch("zerver.logging_handlers.traceback.print_exc"):
                report = self.run_handler(record)
            self.assertIn("user_email", report)
            self.assertIn("message", report)
            self.assertIn("stack_trace", report)
