from ipware.ip import get_client_ip
import logging
import pghistory
from typing import TYPE_CHECKING

from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest

if TYPE_CHECKING:
    from concordia_tests.latchstring.models import User

from .choices import ORIGIN_SERVER

logger = logging.getLogger(__name__)


class ConcordiaWSGIRequest(WSGIRequest):
    """
    Nearly exact copy of `pghistory.middleware.WSGIRequest` to set
    the context fields we're interested in.

    Although Django's auth middleware sets the user in middleware,
    apps like django-rest-framework set the user in the view layer.
    This creates issues for pghistory tracking since the context needs
    to be set before DB operations happen.

    This special WSGIRequest updates pghistory context when
    the `request.user` attribute is updated.
    """

    def __setattr__(self, attr, value):
        if attr == 'user' and value and value.is_authenticated:
            pghistory.context(**user_context(value))

        return super().__setattr__(attr, value)


def ConcordiaHistoryMiddleware(get_response):  # noqa
    """
    Add User details to History context.
    """

    def middleware(request):
        if (
            request.method in settings.PGHISTORY_MIDDLEWARE_METHODS
            and request.path not in settings.SKIP_MIDDLEWARE_URLS
        ):
            user = getattr(request, 'user', None)
            if user and user.is_authenticated:
                context = user_context(user)
            else:
                context = {'user': None}

            ip, _ = get_client_ip(request)

            # Console Sync activity will be local, and we're already
            # adding the appliance source, so skip the IP to save
            # database space.
            if ip != '127.0.0.1':
                context['ip'] = ip

            # The `context` must be initialized in a context manager!
            # So, context must always be initialized here, even if the
            # user has not authenticated, so the `pghistory.context`
            # function call in ConcordiaWSGIRequest can update pre-existing
            # context object.
            with pghistory.context(**context):
                if isinstance(request, WSGIRequest):
                    request.__class__ = ConcordiaWSGIRequest

                return get_response(request)

        return get_response(request)

    return middleware


def user_context(user: 'User') -> dict:
    """
    Return user context dictionary
    """
    try:
        return {
            'user': user.pk,
            'user_cid': user.cid,
            'username': user.username,
            ORIGIN_SERVER.CID: settings.CONCORD_MQ_TENANT,
            ORIGIN_SERVER.Name: settings.SYSTEM_NAME,
        }
    except AttributeError:
        logger.error('Unable to set pghistory.context for User: %s', user)
        return {}
