from unittest.mock import Mock, patch

from django.contrib.auth import authenticate, get_user_model
from django.contrib.sites.shortcuts import get_current_site
from django.test import RequestFactory, override_settings

from sites_microsoft_auth.conf import get_conf
from sites_microsoft_auth.old_conf import LOGIN_TYPE_XBL
from sites_microsoft_auth.models import XboxLiveAccount

from .. import TestCase

CODE = "test_code"
TOKEN = {"access_token": "test_token", "scope": ["test"]}
XBOX_TOKEN = {"Token": "test_token"}
LAST = "User"
MISSING_ID = "some_missing_id"
GAMERTAG = "Some Gamertag"


@override_settings(
    AUTHENTICATION_BACKENDS=[
        "sites_microsoft_auth.backends.MicrosoftAuthenticationBackend",
        "django.contrib.auth.backends.ModelBackend",
    ],
)
class XboxLiveBackendsTests(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)

        config = get_conf(self.request)
        config.login_type = LOGIN_TYPE_XBL
        config.save()

        self.linked_account = XboxLiveAccount.objects.create(
            xbox_id="test_id", gamertag="test_gamertag", site=self.site
        )
        self.linked_account.user = User.objects.create(username="user1", site=self.site)
        self.linked_account.save()

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_bad_xbox_token(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = {}
        mock_auth.valid_scopes.return_value = True

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)

        self.assertFalse(mock_auth.get_xbox_profile.called)
        self.assertIs(user, None)

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_existing_user(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": self.linked_account.xbox_id,
            "gtg": self.linked_account.gamertag,
        }

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)

        self.assertIsNot(user, None)
        self.assertEqual(user.id, self.linked_account.user.id)

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_existing_user_new_gamertag(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": self.linked_account.xbox_id,
            "gtg": GAMERTAG,
        }

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)
        self.linked_account.refresh_from_db()

        self.assertIsNot(user, None)
        self.assertEqual(user.id, self.linked_account.user.id)
        self.assertEqual(GAMERTAG, self.linked_account.gamertag)

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_no_autocreate(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": MISSING_ID,
            "gtg": GAMERTAG,
        }

        conf = get_conf(self.request)
        conf.auto_create = False
        conf.save()

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)

        self.assertIs(user, None)

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_autocreate(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": MISSING_ID,
            "gtg": GAMERTAG,
        }

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)

        self.assertIsNot(user, None)
        self.assertEqual(GAMERTAG, user.username)
        self.assertEqual(MISSING_ID, user.xbox_live_account.xbox_id)

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_no_sync_username(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": self.linked_account.xbox_id,
            "gtg": GAMERTAG,
        }

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)
        self.linked_account.refresh_from_db()

        self.assertIsNot(user, None)
        self.assertEqual(user.id, self.linked_account.user.id)
        self.assertNotEqual(GAMERTAG, self.linked_account.user.username)

    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    def test_authenticate_sync_username(self, mock_client):
        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": self.linked_account.xbox_id,
            "gtg": GAMERTAG,
        }

        mock_client.return_value = mock_auth

        conf = get_conf(self.request)
        conf.xbl_sync_username = True
        conf.save()

        user = authenticate(self.request, code=CODE)
        self.linked_account.refresh_from_db()
        self.linked_account.user.refresh_from_db()

        self.assertIsNot(user, None)
        self.assertEqual(user.id, self.linked_account.user.id)
        self.assertEqual(GAMERTAG, self.linked_account.user.username)

    @override_settings(
        MICROSOFT_AUTH_AUTHENTICATE_HOOK="tests.test_backends.test_xbox.hook_callback"  # noqa
    )
    @patch("sites_microsoft_auth.backends.MicrosoftClient")
    @patch("sites_microsoft_auth.backends.get_hook")
    def test_authenticate_hook(self, mock_get_hook, mock_client):
        mock_hook = Mock()
        mock_get_hook.return_value = mock_hook

        mock_auth = Mock()
        mock_auth.fetch_token.return_value = TOKEN
        mock_auth.fetch_xbox_token.return_value = XBOX_TOKEN
        mock_auth.valid_scopes.return_value = True
        mock_auth.get_xbox_profile.return_value = {
            "xid": MISSING_ID,
            "gtg": GAMERTAG,
        }
        mock_auth.xbox_token = XBOX_TOKEN

        mock_client.return_value = mock_auth

        user = authenticate(self.request, code=CODE)

        mock_hook.assert_called_with(user, XBOX_TOKEN)
