# System documented in https://zulip.readthedocs.io/en/latest/subsystems/logging.html

import logging
import platform
import os
import subprocess
import traceback
from typing import Any, Dict, Optional

from django.conf import settings
from django.http import HttpRequest
from django.views.debug import get_exception_reporter_filter

from zerver.lib.logging_util import find_log_caller_module
from zerver.lib.queue import queue_json_publish
from version import ZULIP_VERSION

def try_git_describe() -> Optional[str]:
    try:  # nocoverage
        return subprocess.check_output(
            ['git', 'describe', '--tags', '--always', '--dirty', '--long'],
            stderr=subprocess.PIPE,
            cwd=os.path.join(os.path.dirname(__file__), '..'),
        ).strip().decode('utf-8')
    except Exception:  # nocoverage
        return None

def add_deployment_metadata(report: Dict[str, Any]) -> None:
    report['git_described'] = try_git_describe()
    report['zulip_version_const'] = ZULIP_VERSION

    version_path = os.path.join(os.path.dirname(__file__), '../version')
    if os.path.exists(version_path):
        report['zulip_version_file'] = open(version_path).read().strip()  # nocoverage

def add_request_metadata(report: Dict[str, Any], request: HttpRequest) -> None:
    report['has_request'] = True

    report['path'] = request.path
    report['method'] = request.method
    report['remote_addr'] = request.META.get('REMOTE_ADDR', None),
    report['query_string'] = request.META.get('QUERY_STRING', None),
    report['server_name'] = request.META.get('SERVER_NAME', None),
    try:
        from django.contrib.auth.models import AnonymousUser
        user_profile = request.user
        if isinstance(user_profile, AnonymousUser):
            user_full_name = None
            user_email = None
        else:
            user_full_name = user_profile.full_name
            user_email = user_profile.email
    except Exception:
        # Unexpected exceptions here should be handled gracefully
        traceback.print_exc()
        user_full_name = None
        user_email = None
    report['user_email'] = user_email
    report['user_full_name'] = user_full_name

    exception_filter = get_exception_reporter_filter(request)
    try:
        report['data'] = request.GET if request.method == 'GET' else \
            exception_filter.get_post_parameters(request)
    except Exception:
        # exception_filter.get_post_parameters will throw
        # RequestDataTooBig if there's a really big file uploaded
        report['data'] = {}

    try:
        report['host'] = request.get_host().split(':')[0]
    except Exception:
        # request.get_host() will throw a DisallowedHost
        # exception if the host is invalid
        report['host'] = platform.node()

class AdminNotifyHandler(logging.Handler):
    """An logging handler that sends the log/exception to the queue to be
       turned into an email and/or a Zulip message for the server admins.
    """

    # adapted in part from django/utils/log.py

    def __init__(self) -> None:
        logging.Handler.__init__(self)

    def emit(self, record: logging.LogRecord) -> None:
        report = {}  # type: Dict[str, Any]

        # This parameter determines whether Zulip should attempt to
        # send Zulip messages containing the error report.  If there's
        # syntax that makes the markdown processor throw an exception,
        # we really don't want to send that syntax into a new Zulip
        # message in exception handler (that's the stuff of which
        # recursive exception loops are made).
        #
        # We initialize is_bugdown_rendering_exception to `True` to
        # prevent the infinite loop of zulip messages by ERROR_BOT if
        # the outer try block here throws an exception before we have
        # a chance to check the exception for whether it comes from
        # bugdown.
        is_bugdown_rendering_exception = True

        try:
            report['node'] = platform.node()
            report['host'] = platform.node()

            add_deployment_metadata(report)

            if record.exc_info:
                stack_trace = ''.join(traceback.format_exception(*record.exc_info))
                message = str(record.exc_info[1])
                is_bugdown_rendering_exception = record.msg.startswith('Exception in Markdown parser')
            else:
                stack_trace = 'No stack trace available'
                message = record.getMessage()
                if '\n' in message:
                    # Some exception code paths in queue processors
                    # seem to result in super-long messages
                    stack_trace = message
                    message = message.split('\n')[0]
                is_bugdown_rendering_exception = False
            report['stack_trace'] = stack_trace
            report['message'] = message

            report['logger_name'] = record.name
            report['log_module'] = find_log_caller_module(record)
            report['log_lineno'] = record.lineno

            if hasattr(record, "request"):
                add_request_metadata(report, record.request)  # type: ignore  # record.request is added dynamically

        except Exception:
            report['message'] = "Exception in preparing exception report!"
            logging.warning(report['message'], exc_info=True)
            report['stack_trace'] = "See /var/log/zulip/errors.log"

        if settings.DEBUG_ERROR_REPORTING:  # nocoverage
            logging.warning("Reporting an error to admins...")
            logging.warning("Reporting an error to admins: {} {} {} {} {}" .format(
                record.levelname, report['logger_name'], report['log_module'],
                report['message'], report['stack_trace']))

        try:
            if settings.STAGING_ERROR_NOTIFICATIONS:
                # On staging, process the report directly so it can happen inside this
                # try/except to prevent looping
                from zerver.lib.error_notify import notify_server_error
                notify_server_error(report, is_bugdown_rendering_exception)
            else:
                queue_json_publish('error_reports', dict(
                    type = "server",
                    report = report,
                ))
        except Exception:
            # If this breaks, complain loudly but don't pass the traceback up the stream
            # However, we *don't* want to use logging.exception since that could trigger a loop.
            logging.warning("Reporting an exception triggered an exception!", exc_info=True)
