package de.effigenix.domain.usermanagement;

import de.effigenix.shared.common.Result;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.*;

import static org.assertj.core.api.Assertions.*;

/**
 * Unit tests for Role Entity.
 * Tests validation, immutable permission management, factory methods, and equality.
 */
@DisplayName("Role Entity")
class RoleTest {

    private RoleId roleId;
    private RoleName roleName;
    private Set<Permission> permissions;
    private String description;

    @BeforeEach
    void setUp() {
        roleId = RoleId.generate();
        roleName = RoleName.ADMIN;
        permissions = new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE, Permission.ROLE_READ));
        description = "Administrator role with full access";
    }

    @Test
    @DisplayName("should_CreateRole_When_ValidDataProvided")
    void should_CreateRole_When_ValidDataProvided() {
        Role role = Role.reconstitute(roleId, roleName, permissions, description);

        assertThat(role.id()).isEqualTo(roleId);
        assertThat(role.name()).isEqualTo(roleName);
        assertThat(role.permissions()).contains(Permission.USER_READ, Permission.USER_WRITE);
        assertThat(role.description()).isEqualTo(description);
    }

    @Test
    @DisplayName("should_ReturnFailure_When_NullRoleNameProvided")
    void should_ReturnFailure_When_NullRoleNameProvided() {
        Result<UserError, Role> result = Role.create(null, permissions, description);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullRole.class);
    }

    @Test
    @DisplayName("should_CreateRoleWithEmptyPermissions_When_NullPermissionsProvided")
    void should_CreateRoleWithEmptyPermissions_When_NullPermissionsProvided() {
        Role role = Role.reconstitute(roleId, roleName, null, description);

        assertThat(role.permissions()).isEmpty();
    }

    @Test
    @DisplayName("should_CreateRoleWithNullDescription_When_DescriptionNotProvided")
    void should_CreateRoleWithNullDescription_When_DescriptionNotProvided() {
        Role role = Role.reconstitute(roleId, roleName, permissions, null);

        assertThat(role.description()).isNull();
    }

    @Test
    @DisplayName("should_CreateRole_When_FactoryMethodCalled")
    void should_CreateRole_When_FactoryMethodCalled() {
        Role role = Role.create(roleName, permissions, description).unsafeGetValue();

        assertThat(role.id()).isNotNull();
        assertThat(role.name()).isEqualTo(roleName);
        assertThat(role.permissions()).isEqualTo(permissions);
        assertThat(role.description()).isEqualTo(description);
    }

    @Test
    @DisplayName("should_ReturnNewRoleWithPermission_When_AddPermissionCalled")
    void should_ReturnNewRoleWithPermission_When_AddPermissionCalled() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);

        Result<UserError, Role> result = role.addPermission(Permission.USER_DELETE);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue().permissions()).contains(Permission.USER_DELETE);
        assertThat(role.permissions()).doesNotContain(Permission.USER_DELETE); // original unchanged
    }

    @Test
    @DisplayName("should_ReturnFailure_When_NullPermissionProvidedToAddPermission")
    void should_ReturnFailure_When_NullPermissionProvidedToAddPermission() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);

        Result<UserError, Role> result = role.addPermission(null);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullPermission.class);
    }

    @Test
    @DisplayName("should_ReturnNewRoleWithMultiplePermissions_When_AddedSequentially")
    void should_ReturnNewRoleWithMultiplePermissions_When_AddedSequentially() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);

        Role updated = role.addPermission(Permission.USER_READ).unsafeGetValue()
                .addPermission(Permission.USER_WRITE).unsafeGetValue()
                .addPermission(Permission.USER_DELETE).unsafeGetValue();

        assertThat(updated.permissions()).hasSize(3);
        assertThat(updated.permissions()).contains(Permission.USER_READ, Permission.USER_WRITE, Permission.USER_DELETE);
        assertThat(role.permissions()).isEmpty(); // original unchanged
    }

    @Test
    @DisplayName("should_NotAddDuplicatePermission_When_SamePermissionAddedTwice")
    void should_NotAddDuplicatePermission_When_SamePermissionAddedTwice() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);

        Role updated = role.addPermission(Permission.USER_READ).unsafeGetValue()
                .addPermission(Permission.USER_READ).unsafeGetValue();

        assertThat(updated.permissions()).hasSize(1);
    }

    @Test
    @DisplayName("should_ReturnNewRoleWithoutPermission_When_RemovePermissionCalled")
    void should_ReturnNewRoleWithoutPermission_When_RemovePermissionCalled() {
        Set<Permission> initialPermissions = new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE));
        Role role = Role.reconstitute(roleId, roleName, initialPermissions, description);

        Result<UserError, Role> result = role.removePermission(Permission.USER_READ);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue().permissions()).doesNotContain(Permission.USER_READ);
        assertThat(result.unsafeGetValue().permissions()).contains(Permission.USER_WRITE);
        assertThat(role.permissions()).contains(Permission.USER_READ); // original unchanged
    }

    @Test
    @DisplayName("should_ReturnFailure_When_NullPermissionProvidedToRemovePermission")
    void should_ReturnFailure_When_NullPermissionProvidedToRemovePermission() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);

        Result<UserError, Role> result = role.removePermission(null);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullPermission.class);
    }

    @Test
    @DisplayName("should_ReturnNewRoleWithDescription_When_UpdateDescriptionCalled")
    void should_ReturnNewRoleWithDescription_When_UpdateDescriptionCalled() {
        Role role = Role.reconstitute(roleId, roleName, permissions, description);
        String newDescription = "Updated administrator role";

        Result<UserError, Role> result = role.updateDescription(newDescription);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue().description()).isEqualTo(newDescription);
        assertThat(role.description()).isEqualTo(description); // original unchanged
    }

    @Test
    @DisplayName("should_SetDescriptionToNull_When_NullDescriptionProvided")
    void should_SetDescriptionToNull_When_NullDescriptionProvided() {
        Role role = Role.reconstitute(roleId, roleName, permissions, description);

        Role updated = role.updateDescription(null).unsafeGetValue();

        assertThat(updated.description()).isNull();
    }

    @Test
    @DisplayName("should_CheckPermission_When_RoleHasPermission")
    void should_CheckPermission_When_RoleHasPermission() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE)), description);

        assertThat(role.hasPermission(Permission.USER_READ)).isTrue();
        assertThat(role.hasPermission(Permission.USER_WRITE)).isTrue();
    }

    @Test
    @DisplayName("should_CheckPermission_When_RoleLacksPermission")
    void should_CheckPermission_When_RoleLacksPermission() {
        Role role = Role.reconstitute(roleId, roleName, new HashSet<>(Set.of(Permission.USER_READ)), description);

        assertThat(role.hasPermission(Permission.USER_DELETE)).isFalse();
    }

    @Test
    @DisplayName("should_BeEqualToAnother_When_BothHaveSameId")
    void should_BeEqualToAnother_When_BothHaveSameId() {
        Role role1 = Role.reconstitute(roleId, RoleName.ADMIN, permissions, description);
        Role role2 = Role.reconstitute(roleId, RoleName.PRODUCTION_MANAGER, new HashSet<>(), "Different role");

        assertThat(role1).isEqualTo(role2);
        assertThat(role1.hashCode()).isEqualTo(role2.hashCode());
    }

    @Test
    @DisplayName("should_NotBeEqual_When_DifferentIds")
    void should_NotBeEqual_When_DifferentIds() {
        Role role1 = Role.reconstitute(RoleId.generate(), roleName, permissions, description);
        Role role2 = Role.reconstitute(RoleId.generate(), roleName, permissions, description);

        assertThat(role1).isNotEqualTo(role2);
    }

    @Test
    @DisplayName("should_ReturnUnmodifiablePermissionSet_When_PermissionsRetrieved")
    void should_ReturnUnmodifiablePermissionSet_When_PermissionsRetrieved() {
        Role role = Role.reconstitute(roleId, roleName, permissions, description);

        Set<Permission> retrievedPermissions = role.permissions();

        assertThatThrownBy(() -> retrievedPermissions.add(Permission.USER_DELETE))
                .isInstanceOf(UnsupportedOperationException.class);
    }

    @Test
    @DisplayName("should_PreserveImmutabilityOfPermissions_When_PermissionsModified")
    void should_PreserveImmutabilityOfPermissions_When_PermissionsModified() {
        Set<Permission> initialPermissions = new HashSet<>(Set.of(Permission.USER_READ));
        Role role = Role.reconstitute(roleId, roleName, initialPermissions, description);

        initialPermissions.add(Permission.USER_WRITE);

        assertThat(role.permissions()).doesNotContain(Permission.USER_WRITE);
    }

    @Test
    @DisplayName("should_SupportMultipleRoleNames_When_DifferentNamesUsed")
    void should_SupportMultipleRoleNames_When_DifferentNamesUsed() {
        Role adminRole = Role.reconstitute(RoleId.generate(), RoleName.ADMIN, permissions, "Admin");
        Role managerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_MANAGER, permissions, "Manager");
        Role workerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, permissions, "Worker");

        assertThat(adminRole.name()).isEqualTo(RoleName.ADMIN);
        assertThat(managerRole.name()).isEqualTo(RoleName.PRODUCTION_MANAGER);
        assertThat(workerRole.name()).isEqualTo(RoleName.PRODUCTION_WORKER);
    }
}
