import datetime

from django.apps import apps
from django.contrib.auth.models import AnonymousUser, Group, User
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import connection
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.state import ProjectState
from django.test import RequestFactory, TestCase
from request_profiler import settings
from request_profiler.middleware import ProfilingMiddleware, request_profile_complete
from request_profiler.models import ProfilingRecord, RuleSet, RuleSetQuerySet

from .models import CustomUser
from .utils import skipIfCustomUser, skipIfDefaultUser


def dummy_view_func(request, **kwargs):
    """Fake function to pass into the process_view method."""
    pass


class DummyView(object):
    """Fake callable object to pass into the process_view method."""

    def __call__(self, request, **kwargs):
        pass


class MockSession:
    def __init__(self, session_key):
        self.session_key = session_key


class MockResponse:
    def __init__(self, status_code):
        self.status_code = status_code
        self.content = "Hello, World!"
        self.values = {}

    def __getitem__(self, key):
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value


@skipIfCustomUser
class ProfilingMiddlewareDefaultUserTests(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.anon = AnonymousUser()
        self.bob = User.objects.create_user("bob")
        self.god = User.objects.create_superuser("god", "iamthelaw", "")
        self.test_group = Group(name="test")
        self.test_group.save()
        # remove any existing external signal listeners
        request_profile_complete.receivers = []

    def test_match_rules(self):
        # rule1 - to match all users
        r1 = RuleSet()
        self.assertTrue(r1.match_user(self.anon))

        request = self.factory.get("/")
        request.user = self.anon
        self.assertTrue(r1.match_uri, request.path)

        middleware = ProfilingMiddleware()
        self.assertEqual(middleware.match_rules(request, [r1]), [r1])

        # now change the uri_regex so we no longer get a match
        r1.uri_regex = "^xyz$"
        self.assertEqual(middleware.match_rules(request, [r1]), [])

        # now change the user_groups so we no longer get a match
        request.user = self.bob
        r1.uri_regex = ""
        r1.user_filter_type = RuleSet.USER_FILTER_GROUP
        r1.user_group_filter = "test"
        self.assertEqual(middleware.match_rules(request, [r1]), [])
        # add bob to the group
        self.bob.groups.add(self.test_group)
        self.assertEqual(middleware.match_rules(request, [r1]), [r1])

    def test_process_request(self):
        request = self.factory.get("/")
        ProfilingMiddleware().process_request(request)
        # this implicitly checks that the profile is attached,
        # and that start() has been called.
        self.assertIsNotNone(request.profiler.elapsed)

    def test_process_view(self):
        request = self.factory.get("/")
        request.profiler = ProfilingRecord()
        ProfilingMiddleware().process_view(request, dummy_view_func, [], {})
        self.assertEqual(request.profiler.view_func_name, "dummy_view_func")

    def test_process_view__as_callable_object(self):
        request = self.factory.get("/")
        request.profiler = ProfilingRecord()
        ProfilingMiddleware().process_view(request, DummyView(), [], {})
        self.assertEqual(request.profiler.view_func_name, "DummyView")

    def test_process_response(self):

        request = self.factory.get("/")
        middleware = ProfilingMiddleware()
        with self.assertRaises(ValueError):
            middleware.process_response(request, None)

        # try no matching rules
        request.profiler = ProfilingRecord().start()
        response = middleware.process_response(request, MockResponse(200))
        self.assertEqual(response.status_code, 200)
        self.assertFalse(hasattr(request, "profiler"))

        # try matching a rule, and checking response values
        r1 = RuleSet()
        r1.save()
        request.profiler = ProfilingRecord().start()
        response = middleware.process_response(request, MockResponse(200))
        self.assertIsNotNone(response)
        self.assertTrue(request.profiler.response_status_code, response.status_code)
        self.assertTrue(response["X-Profiler-Duration"], request.profiler.duration)

    def test_process_response_signal_cancellation(self):

        request = self.factory.get("/")
        request.profiler = ProfilingRecord().start()
        middleware = ProfilingMiddleware()

        # try matching a rule, anc checking response values
        r1 = RuleSet()
        r1.save()

        self.signal_received = False

        def on_request_profile_complete(sender, **kwargs):
            self.signal_received = True
            kwargs.get("instance").cancel()

        request_profile_complete.connect(on_request_profile_complete)
        middleware.process_response(request, MockResponse(200))
        # because we returned False from the signal receiver,
        # we should have stopped profiling.
        self.assertTrue(self.signal_received)
        # because we called cancel(), the record is not saved.
        self.assertIsNone(request.profiler.id)

    def test_global_exclude_function(self):

        # set the func to ignore everything
        RuleSet().save()
        request = self.factory.get("/")
        request.profiler = ProfilingRecord().start()
        middleware = ProfilingMiddleware()
        # process normally, record is saved.
        middleware.process_response(request, MockResponse(200))
        self.assertIsNotNone(request.profiler.id)

        # NB for some reason (prb. due to imports, the standard
        # 'override_settings' decorator doesn't work here.)
        settings.GLOBAL_EXCLUDE_FUNC = lambda x: False
        request.profiler = ProfilingRecord().start()
        # process now, and profiler is cancelled
        middleware.process_response(request, MockResponse(200))
        self.assertFalse(hasattr(request, "profiler"))
        settings.GLOBAL_EXCLUDE_FUNC = lambda x: True


@skipIfDefaultUser
class ProfilingMiddlewareCustomUserTests(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.anon = AnonymousUser()
        self.bob = CustomUser.objects.create_user(
            mobile_number="+886-999888777", password="pass11"
        )
        self.god = CustomUser.objects.create_superuser(
            mobile_number="+886-999888000", password="pass11"
        )
        self.test_group = Group(name="test")
        self.test_group.save()
        # remove any existing external signal listeners
        request_profile_complete.receivers = []

    def test_match_rules(self):
        # rule1 - to match all users
        r1 = RuleSet()
        self.assertTrue(r1.match_user(self.anon))

        request = self.factory.get("/")
        request.user = self.anon
        self.assertTrue(r1.match_uri, request.path)

        middleware = ProfilingMiddleware()
        self.assertEqual(middleware.match_rules(request, [r1]), [r1])

        # now change the uri_regex so we no longer get a match
        r1.uri_regex = "^xyz$"
        self.assertEqual(middleware.match_rules(request, [r1]), [])

        # now change the user_groups so we no longer get a match
        request.user = self.bob
        r1.uri_regex = ""
        r1.user_filter_type = RuleSet.USER_FILTER_GROUP
        r1.user_group_filter = "test"
        self.assertEqual(middleware.match_rules(request, [r1]), [])
        # add bob to the group
        self.bob.groups.add(self.test_group)
        self.assertEqual(middleware.match_rules(request, [r1]), [r1])

    def test_process_request(self):
        request = self.factory.get("/")
        ProfilingMiddleware().process_request(request)
        # this implicitly checks that the profile is attached,
        # and that start() has been called.
        self.assertIsNotNone(request.profiler.elapsed)

    def test_process_view(self):
        request = self.factory.get("/")
        request.profiler = ProfilingRecord()
        ProfilingMiddleware().process_view(request, dummy_view_func, [], {})
        self.assertEqual(request.profiler.view_func_name, "dummy_view_func")

    def test_process_view__as_callable_object(self):
        request = self.factory.get("/")
        request.profiler = ProfilingRecord()
        ProfilingMiddleware().process_view(request, DummyView(), [], {})
        self.assertEqual(request.profiler.view_func_name, "DummyView")

    def test_process_response(self):

        request = self.factory.get("/")
        middleware = ProfilingMiddleware()
        with self.assertRaises(AssertionError):
            middleware.process_response(request, None)

        # try no matching rules
        request.profiler = ProfilingRecord().start()
        response = middleware.process_response(request, MockResponse(200))
        self.assertEqual(response.status_code, 200)
        self.assertFalse(hasattr(request, "profiler"))

        # try matching a rule, and checking response values
        r1 = RuleSet()
        r1.save()
        request.profiler = ProfilingRecord().start()
        response = middleware.process_response(request, MockResponse(200))
        self.assertIsNotNone(response)
        self.assertTrue(request.profiler.response_status_code, response.status_code)
        self.assertTrue(response["X-Profiler-Duration"], request.profiler.duration)

    def test_process_response_signal_cancellation(self):

        request = self.factory.get("/")
        request.profiler = ProfilingRecord().start()
        middleware = ProfilingMiddleware()

        # try matching a rule, anc checking response values
        r1 = RuleSet()
        r1.save()

        self.signal_received = False

        def on_request_profile_complete(sender, **kwargs):
            self.signal_received = True
            kwargs.get("instance").cancel()

        request_profile_complete.connect(on_request_profile_complete)
        middleware.process_response(request, MockResponse(200))
        # because we returned False from the signal receiver,
        # we should have stopped profiling.
        self.assertTrue(self.signal_received)
        # because we called cancel(), the record is not saved.
        self.assertIsNone(request.profiler.id)

    def test_global_exclude_function(self):

        # set the func to ignore everything
        RuleSet().save()
        request = self.factory.get("/")
        request.profiler = ProfilingRecord().start()
        middleware = ProfilingMiddleware()
        # process normally, record is saved.
        middleware.process_response(request, MockResponse(200))
        self.assertIsNotNone(request.profiler.id)

        # NB for some reason (prb. due to imports, the standard
        # 'override_settings' decorator doesn't work here.)
        settings.GLOBAL_EXCLUDE_FUNC = lambda x: False
        request.profiler = ProfilingRecord().start()
        # process now, and profiler is cancelled
        middleware.process_response(request, MockResponse(200))
        self.assertFalse(hasattr(request, "profiler"))
        settings.GLOBAL_EXCLUDE_FUNC = lambda x: True


class MigrationsTests(TestCase):
    def test_for_missing_migrations(self):
        """Checks if there're models changes which aren't reflected in migrations."""
        migrations_loader = MigrationExecutor(connection).loader
        migrations_detector = MigrationAutodetector(
            from_state=migrations_loader.project_state(),
            to_state=ProjectState.from_apps(apps),
        )
        if migrations_detector.changes(graph=migrations_loader.graph):
            self.fail(
                "Your models have changes that are not yet reflected "
                "in a migration. You should add them now."
            )