"""passbook Core Authentication Test"""
import string
from random import SystemRandom

from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import RequestFactory, TestCase
from django.urls import reverse

from passbook.core.models import User
from passbook.factors.dummy.models import DummyFactor
from passbook.factors.password.models import PasswordFactor
from passbook.factors.view import AuthenticationView


class TestFactorAuthentication(TestCase):
    """passbook Core Authentication Test"""

    def setUp(self):
        super().setUp()
        self.password = "".join(
            SystemRandom().choice(string.ascii_uppercase + string.digits)
            for _ in range(8)
        )
        self.factor, _ = PasswordFactor.objects.get_or_create(
            slug="password",
            defaults={
                "name": "password",
                "slug": "password",
                "order": 0,
                "backends": ["django.contrib.auth.backends.ModelBackend"],
            },
        )
        self.user = User.objects.create_user(
            username="test", email="test@test.test", password=self.password
        )

    def test_unauthenticated_raw(self):
        """test direct call to AuthenticationView"""
        response = self.client.get(reverse("passbook_core:auth-process"))
        # Response should be 400 since no pending user is set
        self.assertEqual(response.status_code, 400)

    def test_unauthenticated_prepared(self):
        """test direct call but with pending_uesr in session"""
        request = RequestFactory().get(reverse("passbook_core:auth-process"))
        request.user = AnonymousUser()
        request.session = {}
        request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk

        response = AuthenticationView.as_view()(request)
        self.assertEqual(response.status_code, 200)

    def test_no_factors(self):
        """Test with all factors disabled"""
        self.factor.enabled = False
        self.factor.save()
        request = RequestFactory().get(reverse("passbook_core:auth-process"))
        request.user = AnonymousUser()
        request.session = {}
        request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk

        response = AuthenticationView.as_view()(request)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, reverse("passbook_core:auth-denied"))
        self.factor.enabled = True
        self.factor.save()

    def test_authenticated(self):
        """Test with already logged in user"""
        self.client.force_login(self.user)
        response = self.client.get(reverse("passbook_core:auth-process"))
        # Response should be 400 since no pending user is set
        self.assertEqual(response.status_code, 400)
        self.client.logout()

    def test_unauthenticated_post(self):
        """Test post request as unauthenticated user"""
        request = RequestFactory().post(
            reverse("passbook_core:auth-process"), data={"password": self.password}
        )
        request.user = AnonymousUser()
        middleware = SessionMiddleware()
        middleware.process_request(request)
        request.session.save()  # pylint: disable=no-member
        request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk

        response = AuthenticationView.as_view()(request)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, reverse("passbook_core:overview"))
        self.client.logout()

    def test_unauthenticated_post_invalid(self):
        """Test post request as unauthenticated user"""
        request = RequestFactory().post(
            reverse("passbook_core:auth-process"),
            data={"password": self.password + "a"},
        )
        request.user = AnonymousUser()
        middleware = SessionMiddleware()
        middleware.process_request(request)
        request.session.save()  # pylint: disable=no-member
        request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk

        response = AuthenticationView.as_view()(request)
        self.assertEqual(response.status_code, 200)
        self.client.logout()

    def test_multifactor(self):
        """Test view with multiple active factors"""
        DummyFactor.objects.get_or_create(name="dummy", slug="dummy", order=1)
        request = RequestFactory().post(
            reverse("passbook_core:auth-process"), data={"password": self.password}
        )
        request.user = AnonymousUser()
        middleware = SessionMiddleware()
        middleware.process_request(request)
        request.session.save()  # pylint: disable=no-member
        request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk

        response = AuthenticationView.as_view()(request)
        session_copy = request.session.items()
        self.assertEqual(response.status_code, 302)
        # Verify view redirects to itself after auth
        self.assertEqual(response.url, reverse("passbook_core:auth-process"))

        # Run another request with same session which should result in a logged in user
        request = RequestFactory().post(reverse("passbook_core:auth-process"))
        request.user = AnonymousUser()
        middleware = SessionMiddleware()
        middleware.process_request(request)
        for key, value in session_copy:
            request.session[key] = value
        request.session.save()  # pylint: disable=no-member
        response = AuthenticationView.as_view()(request)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, reverse("passbook_core:overview"))