# (c) cavaliba.com - test_user_class.py

import yaml
from django.test import TestCase

import app_home.cache as cache
from app_data.group import Group
from app_data.loader import load_broker
from app_data.models import DataInstance
from app_data.role import Role
from app_data.user import User


class UserFromTest(TestCase):
    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user01
              displayname: User One
              email: user01@test.test
            """)
        aaa = {"perms": ["p_user_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_from_keyname(self):
        user = User.from_keyname(keyname="user01")
        self.assertIsNotNone(user)
        self.assertIsInstance(user, User)
        self.assertEqual(user.login, "user01")

    def test_from_keyname_missing(self):
        user = User.from_keyname(keyname="does_not_exist")
        self.assertIsNone(user)

    def test_from_id(self):
        ref = User.from_keyname(keyname="user01")
        user = User.from_id(id=ref.id)
        self.assertIsNotNone(user)
        self.assertIsInstance(user, User)
        self.assertEqual(user.login, "user01")

    def test_from_id_missing(self):
        user = User.from_id(id=999999)
        self.assertIsNone(user)

    def test_from_iobj(self):
        iobj = DataInstance.objects.filter(classname="user", keyname="user01").first()
        self.assertIsNotNone(iobj)
        user = User.from_iobj(iobj=iobj)
        self.assertIsNotNone(user)
        self.assertIsInstance(user, User)
        self.assertEqual(user.login, "user01")

    def test_get_by_email(self):
        user = User.get_by_email("user01@test.test")
        self.assertIsNotNone(user)
        self.assertIsInstance(user, User)
        self.assertEqual(user.login, "user01")

    def test_get_by_email_missing(self):
        user = User.get_by_email("notfound@test.test")
        self.assertIsNone(user)

    def test_get_by_email_empty(self):
        user = User.get_by_email("")
        self.assertIsNone(user)

    def test_get_by_email_none(self):
        user = User.get_by_email(None)
        self.assertIsNone(user)

    def test_email_property(self):
        user = User.from_keyname(keyname="user01")
        self.assertEqual(user.email, "user01@test.test")

    def test_login_property(self):
        user = User.from_keyname(keyname="user01")
        self.assertEqual(user.login, user.keyname)


class UserCRUDTest(TestCase):
    def setUp(self):
        cache.clear()

    def test_create(self):
        user = User(keyname="user_create")
        user.set_field_value_single(fieldname="email", value="create@test.test")
        result = user.create()
        self.assertTrue(result)
        self.assertTrue(user.is_bound)
        found = User.from_keyname(keyname="user_create")
        self.assertIsNotNone(found)
        self.assertEqual(found.email, "create@test.test")

    def test_update(self):
        user = User(keyname="user_update")
        user.create()
        user.set_field_value_single(fieldname="email", value="updated@test.test")
        result = user.update()
        self.assertTrue(result)
        found = User.from_keyname(keyname="user_update")
        self.assertEqual(found.email, "updated@test.test")

    def test_delete(self):
        user = User(keyname="user_delete")
        user.create()
        self.assertTrue(user.is_bound)
        result = user.delete()
        self.assertTrue(result)
        self.assertFalse(user.is_bound)
        found = User.from_keyname(keyname="user_delete")
        self.assertIsNone(found)

    def test_enable(self):
        user = User(keyname="user_enable")
        user.is_enabled = False
        user.create()
        result = user.enable()
        self.assertTrue(result)
        self.assertTrue(user.is_enabled)
        found = User.from_keyname(keyname="user_enable")
        self.assertTrue(found.is_enabled)

    def test_disable(self):
        user = User(keyname="user_disable")
        user.create()
        self.assertTrue(user.is_enabled)
        result = user.disable()
        self.assertTrue(result)
        self.assertFalse(user.is_enabled)
        found = User.from_keyname(keyname="user_disable")
        self.assertFalse(found.is_enabled)


class UserGetDirectGroupTest(TestCase):
    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user01
              displayname: User One

            - classname: group
              keyname: group01
              displayname: Group One
              users:
                - user01

            - classname: group
              keyname: group02
              displayname: Group Two
              users:
                - user01

            - classname: group
              keyname: group03
              displayname: Group Three
            """)
        aaa = {"perms": ["p_user_create", "p_group_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_returns_list(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_direct_groups()
        self.assertIsInstance(result, list)

    def test_user_in_one_groups(self):
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user_single
              displayname: User Single

            - classname: group
              keyname: group_single
              displayname: Group Single
              users:
                - user_single
            """)
        aaa = {"perms": ["p_user_create", "p_group_create"]}
        load_broker(datalist=datalist, aaa=aaa)

        user = User.from_keyname(keyname="user_single")
        result = user.get_direct_groups()
        self.assertEqual(len(result), 1)
        self.assertIsInstance(result[0], Group)
        self.assertEqual(result[0].keyname, "group_single")

    def test_user_in_multiple_groups(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_direct_groups()
        keynames = {g.keyname for g in result}
        self.assertIn("group01", keynames)
        self.assertIn("group02", keynames)
        self.assertNotIn("group03", keynames)

    def test_user_in_no_groups(self):
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user_lonely
              displayname: User Lonely
            """)
        aaa = {"perms": ["p_user_create"]}
        load_broker(datalist=datalist, aaa=aaa)

        user = User.from_keyname(keyname="user_lonely")
        result = user.get_direct_groups()
        self.assertEqual(result, [])

    def test_result_are_group_instances(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_direct_groups()
        for g in result:
            self.assertIsInstance(g, Group)
            self.assertTrue(g.is_bound)


class UserGetParentGroupTest(TestCase):
    """
    Hierarchy:
        grandparent
            └── parent
                    └── child   ← user01 is a direct member
    """

    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user01
              displayname: User One

            - classname: group
              keyname: child
              displayname: Child
              users:
                - user01

            - classname: group
              keyname: parent
              displayname: Parent
              subgroups:
                - child

            - classname: group
              keyname: grandparent
              displayname: Grandparent
              subgroups:
                - parent

            - classname: group
              keyname: unrelated
              displayname: Unrelated
            """)
        aaa = {"perms": ["p_user_create", "p_group_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_returns_list(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        self.assertIsInstance(result, list)

    def test_first_level_parent(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        keynames = {g.keyname for g in result}
        self.assertIn("parent", keynames)

    def test_second_level_parent(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        keynames = {g.keyname for g in result}
        self.assertIn("grandparent", keynames)

    def test_excludes_direct_groups(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        keynames = {g.keyname for g in result}
        self.assertNotIn("child", keynames)

    def test_excludes_unrelated_groups(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        keynames = {g.keyname for g in result}
        self.assertNotIn("unrelated", keynames)

    def test_no_duplicates(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        keynames = [g.keyname for g in result]
        self.assertEqual(len(keynames), len(set(keynames)))

    def test_user_in_no_group_returns_empty(self):
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user_lonely
              displayname: User Lonely
            """)
        aaa = {"perms": ["p_user_create"]}
        load_broker(datalist=datalist, aaa=aaa)
        user = User.from_keyname(keyname="user_lonely")
        result = user.get_parent_groups()
        self.assertEqual(result, [])

    def test_result_are_bound_group_instances(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_parent_groups()
        for g in result:
            self.assertIsInstance(g, Group)
            self.assertTrue(g.is_bound)


class UserGetAutoGroupTest(TestCase):
    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user01
              displayname: User One

            - classname: user
              keyname: user02
              displayname: User Two

            - classname: group
              keyname: grp_auto
              displayname: Group Auto
              autogroup_users:
                - user01

            - classname: group
              keyname: grp_other
              displayname: Group Other
              autogroup_users:
                - user02

            - classname: group
              keyname: grp_empty
              displayname: Group Empty
            """)
        aaa = {"perms": ["p_user_create", "p_group_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_returns_list(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_auto_groups()
        self.assertIsInstance(result, list)

    def test_finds_group_where_user_is_autogroup_member(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_auto_groups()
        keynames = {g.keyname for g in result}
        self.assertIn("grp_auto", keynames)

    def test_excludes_group_with_other_user(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_auto_groups()
        keynames = {g.keyname for g in result}
        self.assertNotIn("grp_other", keynames)

    def test_excludes_group_with_no_autogroup_users(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_auto_groups()
        keynames = {g.keyname for g in result}
        self.assertNotIn("grp_empty", keynames)

    def test_user_not_in_any_autogroup_returns_empty(self):
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user_orphan
              displayname: User Orphan
            """)
        aaa = {"perms": ["p_user_create"]}
        load_broker(datalist=datalist, aaa=aaa)
        user = User.from_keyname(keyname="user_orphan")
        result = user.get_auto_groups()
        self.assertEqual(result, [])

    def test_returns_bound_group_instances(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_auto_groups()
        for g in result:
            self.assertIsInstance(g, Group)
            self.assertTrue(g.is_bound)


class UserGetGroupsTest(TestCase):
    """
    Hierarchy:
        parent  (subgroups: [child])
            child   (users: [user01])   <- direct group
        auto    (autogroup_users: [user01])
        unrelated
    """

    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user01
              displayname: User One

            - classname: group
              keyname: child
              displayname: Child
              users:
                - user01

            - classname: group
              keyname: parent
              displayname: Parent
              subgroups:
                - child

            - classname: group
              keyname: auto
              displayname: Auto
              autogroup_users:
                - user01

            - classname: group
              keyname: unrelated
              displayname: Unrelated
            """)
        aaa = {"perms": ["p_user_create", "p_group_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_returns_list_of_group_instances(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_groups()
        for g in result:
            self.assertIsInstance(g, Group)
            self.assertTrue(g.is_bound)

    def test_includes_all_three_sources(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_groups()
        keynames = {g.keyname for g in result}
        self.assertIn("child", keynames)
        self.assertIn("parent", keynames)
        self.assertIn("auto", keynames)

    def test_excludes_unrelated(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_groups()
        keynames = {g.keyname for g in result}
        self.assertNotIn("unrelated", keynames)

    def test_no_duplicates(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_groups()
        keynames = [g.keyname for g in result]
        self.assertEqual(len(keynames), len(set(keynames)))

    def test_user_with_no_groups_returns_empty(self):
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user_lone
              displayname: Lone User
            """)
        aaa = {"perms": ["p_user_create"]}
        load_broker(datalist=datalist, aaa=aaa)
        user = User.from_keyname(keyname="user_lone")
        self.assertEqual(user.get_groups(), [])


class UserGetRolesTest(TestCase):
    """
    user01 is direct member of role_direct (users field).
    user01 belongs to group01, which is in role_group (groups field).
    role_both is reachable by both paths — must appear exactly once.
    role_other belongs to user02 only.
    role_unrelated has no users or groups.
    """

    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user01
              displayname: User One

            - classname: user
              keyname: user02
              displayname: User Two

            - classname: group
              keyname: group01
              displayname: Group One
              users:
                - user01

            - classname: role
              keyname: role_direct
              displayname: Role Direct
              users:
                - user01

            - classname: role
              keyname: role_group
              displayname: Role Group
              groups:
                - group01

            - classname: role
              keyname: role_both
              displayname: Role Both
              users:
                - user01
              groups:
                - group01

            - classname: role
              keyname: role_other
              displayname: Role Other
              users:
                - user02

            - classname: role
              keyname: role_unrelated
              displayname: Role Unrelated
            """)
        aaa = {"perms": ["p_user_create", "p_group_create", "p_role_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_returns_list_of_role_instances(self):
        user = User.from_keyname(keyname="user01")
        result = user.get_roles()
        self.assertIsInstance(result, list)
        for r in result:
            self.assertIsInstance(r, Role)
            self.assertTrue(r.is_bound)

    def test_direct_role_included(self):
        user = User.from_keyname(keyname="user01")
        keynames = {r.keyname for r in user.get_roles()}
        self.assertIn("role_direct", keynames)

    def test_group_role_included(self):
        user = User.from_keyname(keyname="user01")
        keynames = {r.keyname for r in user.get_roles()}
        self.assertIn("role_group", keynames)

    def test_no_duplicates_when_role_reachable_by_both_paths(self):
        user = User.from_keyname(keyname="user01")
        keynames = [r.keyname for r in user.get_roles()]
        self.assertEqual(len(keynames), len(set(keynames)))
        self.assertIn("role_both", keynames)

    def test_excludes_other_user_direct_role(self):
        user = User.from_keyname(keyname="user01")
        keynames = {r.keyname for r in user.get_roles()}
        self.assertNotIn("role_other", keynames)

    def test_excludes_unrelated_role(self):
        user = User.from_keyname(keyname="user01")
        keynames = {r.keyname for r in user.get_roles()}
        self.assertNotIn("role_unrelated", keynames)

    def test_user_with_no_roles_returns_empty(self):
        datalist = yaml.safe_load("""
            - classname: user
              keyname: user_norole
              displayname: User No Role
            """)
        aaa = {"perms": ["p_user_create"]}
        load_broker(datalist=datalist, aaa=aaa)
        user = User.from_keyname(keyname="user_norole")
        self.assertEqual(user.get_roles(), [])


class UserGetPermissionsTest(TestCase):
    """
    Fixture:
      perm_read, perm_write, perm_delete  (_permission objects)
      group01  (users: [user01])
      role_direct  (users: [user01],  permissions: [perm_read])
      role_group   (groups: [group01], permissions: [perm_write])
      role_overlap (users: [user01], groups: [group01], permissions: [perm_read, perm_write])
      role_unrelated  (permissions: [perm_delete])
      user02  — no roles
    """

    def setUp(self):
        cache.clear()
        datalist = yaml.safe_load("""
            - classname: _permission
              keyname: perm_read
              displayname: Perm Read

            - classname: _permission
              keyname: perm_write
              displayname: Perm Write

            - classname: _permission
              keyname: perm_delete
              displayname: Perm Delete

            - classname: user
              keyname: user01
              displayname: User One

            - classname: user
              keyname: user02
              displayname: User Two

            - classname: group
              keyname: group01
              displayname: Group One
              users:
                - user01

            - classname: role
              keyname: role_direct
              displayname: Role Direct
              users:
                - user01
              permissions:
                - perm_read

            - classname: role
              keyname: role_group
              displayname: Role Group
              groups:
                - group01
              permissions:
                - perm_write

            - classname: role
              keyname: role_overlap
              displayname: Role Overlap
              users:
                - user01
              groups:
                - group01
              permissions:
                - perm_read
                - perm_write

            - classname: role
              keyname: role_unrelated
              displayname: Role Unrelated
              permissions:
                - perm_delete
            """)
        aaa = {"perms": ["p_user_create", "p_group_create", "p_role_create", "p_permission_create"]}
        load_broker(datalist=datalist, aaa=aaa)

    def test_returns_list(self):
        user = User.from_keyname(keyname="user01")
        self.assertIsInstance(user.get_permissions(), list)

    def test_returns_sirenepermission_objects(self):
        from app_user.models import SirenePermission

        user = User.from_keyname(keyname="user01")
        for p in user.get_permissions():
            self.assertIsInstance(p, SirenePermission)

    def test_direct_role_permission_included(self):
        user = User.from_keyname(keyname="user01")
        keynames = {p.keyname for p in user.get_permissions()}
        self.assertIn("perm_read", keynames)

    def test_group_role_permission_included(self):
        user = User.from_keyname(keyname="user01")
        keynames = {p.keyname for p in user.get_permissions()}
        self.assertIn("perm_write", keynames)

    def test_no_duplicates_across_roles(self):
        user = User.from_keyname(keyname="user01")
        keynames = [p.keyname for p in user.get_permissions()]
        self.assertEqual(len(keynames), len(set(keynames)))

    def test_excludes_unrelated_role_permission(self):
        user = User.from_keyname(keyname="user01")
        keynames = {p.keyname for p in user.get_permissions()}
        self.assertNotIn("perm_delete", keynames)

    def test_populates_roles_as_side_effect(self):
        user = User.from_keyname(keyname="user01")
        self.assertIsNone(user.roles)
        user.get_permissions()
        self.assertIsNotNone(user.roles)

    def test_user_with_no_roles_gets_role_default_perms(self):
        from app_data.role import Role

        user = User.from_keyname(keyname="user02")
        perm_keynames = {p.keyname for p in user.get_permissions()}
        role_default = Role.from_keyname(keyname="role_default")
        expected = {p.keyname for p in role_default.get_permissions()}
        self.assertEqual(perm_keynames, expected)
