import os
import re
import logging
import waffle

from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from django.http import JsonResponse
from django.utils import translation, cache
from django.utils.deprecation import MiddlewareMixin
from django.middleware.clickjacking import XFrameOptionsMiddleware as BaseXFrameOptionsMiddleware

from django_yauth.middleware import (
    YandexAuthMiddleware,
    YandexAuthRequiredMiddleware,
)
from django_yauth.user import YandexUser, AnonymousYandexUser
from tvm2.ticket import ServiceTicket

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response


User = get_user_model()


logger = logging.getLogger(__name__)


@api_view(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'])
def process_exception(request, exception):
    message = exception.message if hasattr(exception, 'message') else ''
    return Response(
        data={
            'detail': 'An error occured',
            'exception': exception.__class__.__name__,
            'message': message,
        },
        status=status.HTTP_500_INTERNAL_SERVER_ERROR
    )


class ExceptionMiddleware(MiddlewareMixin):
    def process_exception(self, request, exception):
        return process_exception(request, exception)


class YauthMiddleware(YandexAuthMiddleware):

    def process_request(self, request):
        response = super().process_request(request)
        yauser = request.yauser
        if (response is None
                and yauser.is_authenticated()
                and hasattr(yauser, 'fields')
                and 'language' in yauser.fields):
            translation.activate(request.yauser.fields['language'])

        if self._is_tvm_request(request):
            lang = translation.get_language_from_request(request)
            translation.activate(lang)

        return response

    def process_response(self, request, response):
        cache.patch_vary_headers(response, ('Accept-Language',))
        if 'Content-Language' not in response:
            response['Content-Language'] = translation.get_language()

        translation.deactivate()
        return response

    def assign_user(self, request):
        user = None
        if request.yauser.is_authenticated() and not self._is_allowed_service_request(request):
            uid = request.yauser.uid
            try:
                user = User.objects.select_related('department').get(uid=uid)
            except User.DoesNotExist:
                logger.warning('User with uid %s was not found', uid)
                request.yauser = AnonymousYandexUser()
                user = None
        request.user = user or AnonymousUser()

    def _is_service_request(self, request):
        return (
            self._is_tvm_request(request)
            and not request.yauser.is_impersonated
        )

    def _is_tvm_request(self, request):
        return (
            request.yauser.is_authenticated()
            and request.yauser.authenticated_by.mechanism_name == 'tvm'
        )

    def _is_allowed_service_request(self, request):
        if not self._is_service_request(request):
            return False
        for path in getattr(settings, 'YAUTH_SERVICE_PATHS', []):
            rgx = re.compile(path)
            if rgx.match(request.path):
                return True
        return False


class YauthRequiredMiddleware(YandexAuthRequiredMiddleware):

    def process_request(self, request):
        # Игнорируем ручки, которые не требуют авторизации
        for path in getattr(settings, 'YAUTH_IGNORE_PATHS', []):
            rgx = re.compile(path)
            if rgx.match(request.path):
                return

        return super().process_request(request)


class TestAuthMiddleware(MiddlewareMixin):
    """
    Аутентификация для использования в тестах
    """
    def process_request(self, request):
        username = getattr(settings, 'AUTH_TEST_USER', None)
        email = username and username + '@yandex-team.ru'
        tvm_client_id = getattr(settings, 'AUTH_TEST_TVM_CLIENT_ID', '123')
        mechanism_name = getattr(settings, 'AUTH_TEST_MECHANISM', 'cookie')
        service_ticket = None
        if mechanism_name == 'tvm':
            service_ticket = ServiceTicket({
                'src': tvm_client_id,
                'dst': '123',
                'debug_string': '',
                'logging_string': '',
                'scopes': [],
            })

        class FakeMechanism:
            def __init__(self):
                self.mechanism_name = mechanism_name

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            request.yauser = AnonymousYandexUser()
            request.user = AnonymousUser()
        else:
            request.user = user
            request.yauser = YandexUser(
                uid='000000',
                is_lite=False,
                fields={},
                need_reset=False,
                emails=[email],
                default_email=email,
                mechanism=FakeMechanism(),
                service_ticket=service_ticket,
            )


class FakeYauthMiddleware(MiddlewareMixin):

    def process_request(self, request):
        request.user = User.objects.get(username=os.environ['STAFF_USERNAME'])


class ForceReadOnlyMiddleware(MiddlewareMixin):

    def process_request(self, request):
        if request.path.startswith('/admin'):
            return
        if (request.method in ['POST', 'PUT', 'PATCH', 'DELETE']
                and waffle.switch_is_active('read_only_mode')):
            return JsonResponse(
                data={'detail': 'Service is in read-only mode.'},
                status=status.HTTP_405_METHOD_NOT_ALLOWED,
            )


class MagicLinksMiddleware(MiddlewareMixin):

    # TODO: после релиза FEMIDA-5361:
    #  - отказаться от явной проверки tvm-приложения;
    #  - поменять регулярку на rf'^/_api/magiclinks/(?P<model>{supported_models})'
    def process_request(self, request):
        if not request.yauser.is_authenticated():
            return

        is_magic_links = (
            request.yauser.authenticated_by.mechanism_name == 'tvm'
            and request.yauser.service_ticket.src == settings.TVM_MAGICLINKS_CLIENT_ID
        )
        if is_magic_links:
            supported_models = '|'.join(settings.MAGICLINKS_SUPPORTED_MODELS)
            path_rgx = re.compile(rf'^/(api|_api/magiclinks)/(?P<model>{supported_models})')
            match = path_rgx.match(request.path)
            if match and waffle.switch_is_active(f'enable_magiclinks_for_{match.group("model")}'):
                return
            return JsonResponse(
                data={'detail': 'MagicLinks are disabled'},
                status=status.HTTP_404_NOT_FOUND,
            )


class XFrameOptionsMiddleware(BaseXFrameOptionsMiddleware):

    def get_xframe_options_value(self, request, response):
        """
        Gets the value to set for the X_FRAME_OPTIONS header.

        By default this uses the value from the X_FRAME_OPTIONS Django
        settings. If not found in settings, defaults to 'SAMEORIGIN'.

        This method can be overridden if needed, allowing it to vary based on
        the request or response.
        """
        referer = request.META.get('HTTP_REFERER')
        if referer:
            for referer_pattern in settings.X_FRAME_OPTIONS_REGEX_WHITELIST:
                if re.match(referer_pattern, referer):
                    return 'ALLOWALL'
        return getattr(settings, 'X_FRAME_OPTIONS', 'SAMEORIGIN').upper()


class TestYauthMiddleware(YauthMiddleware):
    """
    Используется в тестах для деактивации языка пользователя (метод process_response)
    """
    def process_request(self, request):
        pass
