from unittest.mock import MagicMock

from django.contrib.auth.models import Group, User
from django.test import TestCase

from themis.adapters import CasdoorAccountAdapter, LocalAccountAdapter


class CasdoorAdapterTest(TestCase):

    def setUp(self):
        self.adapter = CasdoorAccountAdapter()

    def test_signup_always_open(self):
        self.assertTrue(self.adapter.is_open_for_signup(MagicMock(), MagicMock()))

    def test_superuser_never_set_via_sso(self):
        sociallogin = MagicMock()
        data = {"email": "admin@example.com", "groups": ["admin"]}
        user = self.adapter.populate_user(MagicMock(), sociallogin, data)
        self.assertFalse(user.is_superuser)

    def test_email_used_as_username(self):
        sociallogin = MagicMock()
        data = {"email": "jane@example.com"}
        user = self.adapter.populate_user(MagicMock(), sociallogin, data)
        self.assertEqual(user.username, "jane@example.com")

    def test_staff_flag_from_groups(self):
        sociallogin = MagicMock()
        for group in ["staff", "sme", "admin"]:
            data = {"email": "user@example.com", "groups": [group]}
            user = self.adapter.populate_user(MagicMock(), sociallogin, data)
            self.assertTrue(user.is_staff, f"is_staff should be True for group '{group}'")

    def test_view_only_not_staff(self):
        sociallogin = MagicMock()
        data = {"email": "viewer@example.com", "groups": ["view_only"]}
        user = self.adapter.populate_user(MagicMock(), sociallogin, data)
        self.assertFalse(user.is_staff)

    def test_name_fallback_parsing(self):
        sociallogin = MagicMock()
        data = {"email": "user@example.com", "name": "Jane Doe"}
        user = self.adapter.populate_user(MagicMock(), sociallogin, data)
        self.assertEqual(user.first_name, "Jane")
        self.assertEqual(user.last_name, "Doe")

    def test_name_fallback_single_word(self):
        sociallogin = MagicMock()
        data = {"email": "user@example.com", "name": "Cher"}
        user = self.adapter.populate_user(MagicMock(), sociallogin, data)
        self.assertEqual(user.first_name, "Cher")
        self.assertEqual(user.last_name, "")

    def test_given_family_name_takes_precedence_over_name(self):
        sociallogin = MagicMock()
        data = {
            "email": "user@example.com",
            "given_name": "Alice",
            "family_name": "Smith",
            "name": "Should Not Use This",
        }
        user = self.adapter.populate_user(MagicMock(), sociallogin, data)
        self.assertEqual(user.first_name, "Alice")
        self.assertEqual(user.last_name, "Smith")

    def test_group_mapping(self):
        Group.objects.create(name="View Only")
        Group.objects.create(name="Staff")
        user = User.objects.create_user("test@example.com", "test@example.com")
        self.adapter._assign_groups(user, ["view_only", "staff"])
        group_names = set(user.groups.values_list("name", flat=True))
        self.assertEqual(group_names, {"View Only", "Staff"})

    def test_group_mapping_clears_previous_groups(self):
        Group.objects.create(name="Admin")
        Group.objects.create(name="View Only")
        user = User.objects.create_user("test@example.com", "test@example.com")
        user.groups.add(Group.objects.get(name="Admin"))
        self.adapter._assign_groups(user, ["view_only"])
        group_names = set(user.groups.values_list("name", flat=True))
        self.assertEqual(group_names, {"View Only"})

    def test_unknown_casdoor_group_ignored(self):
        user = User.objects.create_user("test@example.com", "test@example.com")
        self.adapter._assign_groups(user, ["unknown_role"])
        self.assertEqual(user.groups.count(), 0)

    def test_superuser_sso_login_blocked(self):
        from allauth.core.exceptions import ImmediateHttpResponse

        user = User.objects.create_superuser(
            "admin@example.com", "admin@example.com", "pass"
        )
        sociallogin = MagicMock()
        sociallogin.user = user
        sociallogin.user.id = user.id
        with self.assertRaises(ImmediateHttpResponse):
            self.adapter.pre_social_login(MagicMock(), sociallogin)

    def test_groups_resync_on_returning_login(self):
        Group.objects.create(name="Admin")
        Group.objects.create(name="Staff")
        user = User.objects.create_user("user@example.com", "user@example.com")
        user.groups.add(Group.objects.get(name="Staff"))

        sociallogin = MagicMock()
        sociallogin.user = user
        sociallogin.user.id = user.id
        sociallogin.account.extra_data = {
            "groups": ["admin"],
            "organization": "",
        }
        self.adapter.pre_social_login(MagicMock(), sociallogin)
        group_names = set(user.groups.values_list("name", flat=True))
        self.assertEqual(group_names, {"Admin"})

    def test_new_user_skips_pre_social_login_sync(self):
        sociallogin = MagicMock()
        sociallogin.user.id = None
        # Should not raise or attempt any DB operations
        self.adapter.pre_social_login(MagicMock(), sociallogin)


class LocalAdapterTest(TestCase):

    def test_local_signup_disabled(self):
        adapter = LocalAccountAdapter()
        self.assertFalse(adapter.is_open_for_signup(MagicMock()))
