import json
from unittest.mock import Mock, patch

from django.contrib.auth import get_user_model
from django.contrib.sites.shortcuts import get_current_site
from django.core.signing import TimestampSigner
from django.test import RequestFactory
from django.urls import reverse

from sites_microsoft_auth.views import AuthenticateCallbackView

from . import TestCase

STATE = TimestampSigner().sign(
    "e4675ea8d28a41b8b416fe9ed1fb52b1e4675ea8d28a41b8b416fe9ed1fb52b1"
)
EXPIRED_STATE = (
    "e4675ea8d28a41b8b416fe9ed1fb52b1e4675ea8d28a41b8b416fe9ed1fb52b1:"
    "1h5CgL:G-QiLZ3hetUPgrdpJlvAfXkZ2RQ"
)
TEST_ERROR = "test"
TEST_ERROR_DESCRIPTION = "some_error"


class ViewsTests(TestCase):
    def setUp(self):
        super().setUp()

        User = get_user_model()
        self.factory = RequestFactory()
        self.request = self.factory.get("/")
        self.site = get_current_site(self.request)

        self.user = User.objects.create(username="test", site=self.site)

    def test_authenticate_callback_bad_method(self):
        response = self.client.get(reverse("sites_microsoft_auth:auth-callback"))

        self.assertEqual(405, response.status_code)

    def test_authenticate_callback_no_params(self):
        response = self.client.post(reverse("sites_microsoft_auth:auth-callback"))
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("bad_state", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["bad_state"],
            message["error_description"],
        )

    def test_authenticate_callback_bad_state_format(self):
        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"), {"state": "test"}
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("bad_state", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["bad_state"],
            message["error_description"],
        )

    def test_authenticate_callback_bad_state_length(self):
        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"), {"state": "001464"}
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("bad_state", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["bad_state"],
            message["error_description"],
        )

    def test_authenticate_callback_bad_state(self):
        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"), {"state": STATE[:-1]}
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("bad_state", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["bad_state"],
            message["error_description"],
        )

    def test_authenticate_callback_bad_state_expired(self):
        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"), {"state": EXPIRED_STATE}
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("bad_state", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["bad_state"],
            message["error_description"],
        )

    def test_authenticate_callback_missing_code(self):

        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"), {"state": STATE}
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("missing_code", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["missing_code"],
            message["error_description"],
        )

    def test_authenticate_callback_error(self):
        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"),
            {
                "state": STATE,
                "error": TEST_ERROR,
                "error_description": TEST_ERROR_DESCRIPTION,
            },
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual(TEST_ERROR, message["error"])
        self.assertEqual(TEST_ERROR_DESCRIPTION, message["error_description"])

    @patch("sites_microsoft_auth.views.authenticate")
    def test_authenticate_callback_fail_auth(self, mock_auth):
        mock_auth.return_value = None

        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"),
            {"state": STATE, "code": "test_code"},
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(400, response.status_code)
        self.assertEqual("login_failed", message["error"])
        self.assertEqual(
            AuthenticateCallbackView.messages["login_failed"],
            message["error_description"],
        )

    @patch("sites_microsoft_auth.views.authenticate")
    @patch("sites_microsoft_auth.views.login")
    def test_authenticate_callback_success(self, mock_login, mock_auth):
        mock_auth.return_value = self.user

        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"),
            {"state": STATE, "code": "test_code"},
        )
        message = json.loads(response.context["message"])["microsoft_auth"]

        self.assertEqual(200, response.status_code)
        self.assertEqual({}, message)
        mock_login.assert_called_with(response.wsgi_request, self.user)

    @patch("sites_microsoft_auth.views.authenticate")
    @patch("sites_microsoft_auth.views.login")
    @patch("sites_microsoft_auth.views.get_hook")
    def test_callback_hook(self, mock_get_hook, mock_login, mock_auth):
        def callback(request, context):
            return context

        mock_auth.return_value = self.user

        mock_hook = Mock(side_effect=callback)
        mock_get_hook.return_value = mock_hook

        response = self.client.post(
            reverse("sites_microsoft_auth:auth-callback"),
            {"state": STATE, "code": "test_code"},
        )

        expected_context = {}
        expected_context["base_url"] = response.context["base_url"]
        expected_context["message"] = response.context["message"]

        self.assertIsInstance(expected_context, dict)
        mock_hook.assert_called_with(response.wsgi_request, expected_context)
