# -*- coding: utf-8 -*-
import collections.abc
import json

from copy import copy
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.handlers.base import BaseHandler
from django.db.models.sql.compiler import SQLCompiler
from django.http import QueryDict
from django.test import TestCase, override_settings
from django.utils.translation import override
from psycopg2 import OperationalError
from requests.exceptions import HTTPError
from requests.structures import CaseInsensitiveDict
from unittest.mock import patch


def apply_middlewares_to_request(request):
    # todo: test me
    handler = BaseHandler()
    handler.load_middleware()
    for middleware_method in handler._request_middleware:
        middleware_method(request)


def get_override_lang_code_for_view(view_class):
    # todo: test me
    def overrider(lang_code):
        """Патчит локаль. Срабатывает даже на уровне client.get"""
        old_dispatch = view_class.dispatch
        def new_dispatch(self, *args, **kwargs):
            with override(lang_code):
                return old_dispatch(self, *args, **kwargs)
        return patch.object(view_class, 'dispatch', new_dispatch)

    return overrider


class TestConditionBase(TestCase):
    fixtures = ['initial_data.json']
#    model = ModelClass
#    queryset_method_name = 'queryset_filter_method_name'
#    instance_property_name = 'model_instance_property_name'

    def setUp(self):
        super(TestConditionBase, self).setUp()
        self.instance = self.create_instance()
        if self.queryset_method_name is not None:
            self.queryset_filter_method = getattr(self.model.objects, self.queryset_method_name)

    def create_instance(self):
        """Creates instance of model"""
        raise NotImplementedError('Subclasses must define this method.')

    def assertInstanceMethodResponseReturns(self, response, msg, args, kwargs):
        if not hasattr(self, 'instance_property_name'):
            return
        instance_property = getattr(self.instance, self.instance_property_name)
        if isinstance(instance_property, collections.abc.Callable):
            self.assertEqual(instance_property(*args, **kwargs), response, msg=msg)
        else:
            self.assertEqual(instance_property, response, msg=msg)

    def assertFound(self, msg, args, kwargs):
        # leave only 1 model instance
        self.model.objects.all().exclude(pk=self.instance.pk).delete()
        self.assertEqual(self.queryset_filter_method(*args, **kwargs).count(), 1, msg=msg)
        self.assertEqual(self.queryset_filter_method(*args, **kwargs)[0], self.instance, msg=msg)

    def assertNotFound(self, msg, args, kwargs):
        # leave only 1 model instance
        self.model.objects.all().exclude(pk=self.instance.pk).delete()
        self.assertEqual(self.queryset_filter_method(*args, **kwargs).count(), 0, msg=msg)

    def assertConditionTrue(self, msg=None, args=None, kwargs=None):
        if not args:
            args = []
        if not kwargs:
            kwargs = {}

        # test instance method
        self.assertInstanceMethodResponseReturns(True, msg=msg, args=args, kwargs=kwargs)

        # test QuerySet filter method
        if self.queryset_method_name is not None:
            self.assertFound(msg=msg, args=args, kwargs=kwargs)

    def assertConditionFalse(self, msg=None, args=None, kwargs=None):
        if not args:
            args = []
        if not kwargs:
            kwargs = {}

        # test instance method
        self.assertInstanceMethodResponseReturns(False, msg=msg, args=args, kwargs=kwargs)

        # test QuerySet filter method
        if self.queryset_method_name is not None:
            self.assertNotFound(msg=msg, args=args, kwargs=kwargs)


def make_missing_permissions():
    codenames = [
        'view_{model}',
        'change_{model}',
    ]

    for ct in ContentType.objects.all():
        for codename in codenames:
            Permission.objects.get_or_create(
                content_type=ct,
                codename=codename.format(model=ct.model),
            )


class MockResponse(object):
    def __init__(self, json_data=None, status_code=200, headers=None):
        self.status_code = status_code
        self.json_data = json_data or {}
        self.text = json.dumps(self.json_data)
        self.content = self.text.encode()
        self.url = ''
        self.headers = CaseInsensitiveDict(headers or {'Content-Type': 'application/json'})
        self.request = MockRequest()

    def raise_for_status(self):
        if self.status_code != 200:
            raise HTTPError

    def json(self):
        return self.json_data


class MockRequest(object):
    def __init__(self, method='GET', path='', query_params='',
                 GET='', POST='', PUT='', DELETE='', META='', COOKIES=None,
                 LANGUAGE_CODE='ru', path_info=''):
        self.GET = copy(QueryDict(GET))
        self.POST = copy(QueryDict(POST))
        self.PUT = copy(QueryDict(PUT))
        self.DELETE = copy(QueryDict(DELETE))
        self.META = copy(QueryDict(META))
        self.COOKIES = COOKIES or {}
        self.path = path
        self.method = method
        self.LANGUAGE_CODE = LANGUAGE_CODE
        self.path_info = path_info
        self.query_params = copy(QueryDict(query_params))

        # yauser context
        self.yauser = None
        self.is_secure = lambda: True

        self.META['REMOTE_ADDR'] = '5.255.219.135'  # просто некоторая старая машинка
        self.META['SERVER_PORT'] = 80

    def get_full_path(self, *args, **kwargs):
        return self.path

    def get_host(self):
        return 'yandex.ru'

    def is_ajax(self):
        return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'


def kill_master():
    old_execute_sql = SQLCompiler.execute_sql

    def new_execute_sql(self, *args, **kwargs):
        if self.using == 'default':
            raise OperationalError('master is dead (x_x)')
        old_execute_sql(self, *args, **kwargs)

    return patch.object(SQLCompiler, 'execute_sql', new_execute_sql)


class override_cache_settings(override_settings):
    def __init__(self):
        _caches = {
            name: {
                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
            }
            for name in settings.CACHES
        }
        super().__init__(CACHES=_caches)
