package de.effigenix.infrastructure.usermanagement.persistence.mapper;

import de.effigenix.domain.usermanagement.*;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.HashSet;
import java.util.Set;

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

/**
 * Unit tests for RoleMapper.
 * Tests bidirectional mapping between Role domain entity and RoleEntity JPA entity.
 */
@DisplayName("RoleMapper")
class RoleMapperTest {

    private final RoleMapper roleMapper = new RoleMapper();

    private Role domainRole;
    private RoleEntity jpaEntity;
    private Set<Permission> permissions;

    @BeforeEach
    void setUp() {
        permissions = new HashSet<>(Set.of(
                Permission.USER_READ,
                Permission.USER_WRITE,
                Permission.ROLE_READ
        ));

        // Create JPA entity first
        jpaEntity = new RoleEntity(
                "role-123",
                RoleName.ADMIN,
                new HashSet<>(permissions),
                "Administrator role with full access"
        );

        // Create domain role via mapper (which internally uses reconstitute)
        domainRole = roleMapper.toDomain(jpaEntity);
    }

    @Test
    @DisplayName("should_MapRoleToEntity_When_DomainRoleProvided")
    void should_MapRoleToEntity_When_DomainRoleProvided() {
        // Act
        RoleEntity entity = roleMapper.toEntity(domainRole);

        // Assert
        assertThat(entity).isNotNull();
        assertThat(entity.getId()).isEqualTo("role-123");
        assertThat(entity.getName()).isEqualTo(RoleName.ADMIN);
        assertThat(entity.getDescription()).isEqualTo("Administrator role with full access");
        assertThat(entity.getPermissions()).containsAll(permissions);
    }

    @Test
    @DisplayName("should_MapRoleToDomain_When_JpaEntityProvided")
    void should_MapRoleToDomain_When_JpaEntityProvided() {
        // Act
        Role role = roleMapper.toDomain(jpaEntity);

        // Assert
        assertThat(role).isNotNull();
        assertThat(role.id().value()).isEqualTo("role-123");
        assertThat(role.name()).isEqualTo(RoleName.ADMIN);
        assertThat(role.description()).isEqualTo("Administrator role with full access");
        assertThat(role.permissions()).containsAll(permissions);
    }

    @Test
    @DisplayName("should_ReturnNull_When_NullRoleProvidedToToEntity")
    void should_ReturnNull_When_NullRoleProvidedToToEntity() {
        // Act
        RoleEntity entity = roleMapper.toEntity(null);

        // Assert
        assertThat(entity).isNull();
    }

    @Test
    @DisplayName("should_ReturnNull_When_NullEntityProvidedToToDomain")
    void should_ReturnNull_When_NullEntityProvidedToToDomain() {
        // Act
        Role role = roleMapper.toDomain(null);

        // Assert
        assertThat(role).isNull();
    }

    @Test
    @DisplayName("should_PreserveAllRoleFields_When_MappingToEntity")
    void should_PreserveAllRoleFields_When_MappingToEntity() {
        // Arrange
        Set<Permission> perms = new HashSet<>(Set.of(
                Permission.BATCH_READ,
                Permission.BATCH_WRITE,
                Permission.BATCH_COMPLETE
        ));
        Role role = roleMapper.toDomain(new RoleEntity(
                "role-prod-manager",
                RoleName.PRODUCTION_MANAGER,
                new HashSet<>(perms),
                "Production Manager role"
        ));

        // Act
        RoleEntity entity = roleMapper.toEntity(role);

        // Assert
        assertThat(entity.getId()).isEqualTo("role-prod-manager");
        assertThat(entity.getName()).isEqualTo(RoleName.PRODUCTION_MANAGER);
        assertThat(entity.getDescription()).isEqualTo("Production Manager role");
        assertThat(entity.getPermissions()).containsAll(perms);
    }

    @Test
    @DisplayName("should_PreserveAllEntityFields_When_MappingToDomain")
    void should_PreserveAllEntityFields_When_MappingToDomain() {
        // Arrange
        Set<Permission> perms = new HashSet<>(Set.of(
                Permission.STOCK_READ,
                Permission.STOCK_WRITE
        ));
        RoleEntity entity = new RoleEntity(
                "role-warehouse",
                RoleName.WAREHOUSE_WORKER,
                perms,
                "Warehouse Worker role"
        );

        // Act
        Role role = roleMapper.toDomain(entity);

        // Assert
        assertThat(role.id().value()).isEqualTo("role-warehouse");
        assertThat(role.name()).isEqualTo(RoleName.WAREHOUSE_WORKER);
        assertThat(role.description()).isEqualTo("Warehouse Worker role");
        assertThat(role.permissions()).containsAll(perms);
    }

    @Test
    @DisplayName("should_HandleEmptyPermissions_When_RoleHasNoPermissions")
    void should_HandleEmptyPermissions_When_RoleHasNoPermissions() {
        // Arrange
        Role roleNoPerms = roleMapper.toDomain(new RoleEntity(
                "role-empty",
                RoleName.ADMIN,
                new HashSet<>(),
                "Empty role"
        ));

        // Act
        RoleEntity entity = roleMapper.toEntity(roleNoPerms);

        // Assert
        assertThat(entity.getPermissions()).isEmpty();
    }

    @Test
    @DisplayName("should_HandleNullPermissions_When_MappingToEntity")
    void should_HandleNullPermissions_When_MappingToEntity() {
        // Arrange
        Role roleNullPerms = roleMapper.toDomain(new RoleEntity(
                "role-null",
                RoleName.ADMIN,
                null,
                "Role with null permissions"
        ));

        // Act
        RoleEntity entity = roleMapper.toEntity(roleNullPerms);

        // Assert
        assertThat(entity.getPermissions()).isEmpty();
    }

    @Test
    @DisplayName("should_HandleNullPermissions_When_MappingToDomain")
    void should_HandleNullPermissions_When_MappingToDomain() {
        // Arrange
        RoleEntity entityNullPerms = new RoleEntity(
                "role-null",
                RoleName.ADMIN,
                null,
                "Entity with null permissions"
        );

        // Act
        Role role = roleMapper.toDomain(entityNullPerms);

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

    @Test
    @DisplayName("should_BidirectionalMapping_When_MappingRoleBackAndForth")
    void should_BidirectionalMapping_When_MappingRoleBackAndForth() {
        // Act
        RoleEntity entity = roleMapper.toEntity(domainRole);
        Role mappedBackRole = roleMapper.toDomain(entity);

        // Assert
        assertThat(mappedBackRole.id().value()).isEqualTo(domainRole.id().value());
        assertThat(mappedBackRole.name()).isEqualTo(domainRole.name());
        assertThat(mappedBackRole.description()).isEqualTo(domainRole.description());
        assertThat(mappedBackRole.permissions()).containsAll(domainRole.permissions());
    }

    @Test
    @DisplayName("should_CreateNewSetForPermissions_When_MappingToEntity")
    void should_CreateNewSetForPermissions_When_MappingToEntity() {
        // Act
        RoleEntity entity = roleMapper.toEntity(domainRole);

        // Assert
        assertThat(entity.getPermissions()).isNotNull();
        assertThat(entity.getPermissions()).isInstanceOf(Set.class);
    }

    @Test
    @DisplayName("should_CreateNewSetForPermissions_When_MappingToDomain")
    void should_CreateNewSetForPermissions_When_MappingToDomain() {
        // Act
        Role role = roleMapper.toDomain(jpaEntity);

        // Assert
        assertThat(role.permissions()).isNotNull();
        assertThat(role.permissions()).isInstanceOf(Set.class);
    }

    @Test
    @DisplayName("should_MapAllRoleNames_When_DifferentRoleNamesUsed")
    void should_MapAllRoleNames_When_DifferentRoleNamesUsed() {
        // Act
        Role adminRole = roleMapper.toDomain(
                new RoleEntity("id-1", RoleName.ADMIN, new HashSet<>(), "Admin")
        );
        Role prodWorkerRole = roleMapper.toDomain(
                new RoleEntity("id-2", RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker")
        );
        Role warehouseRole = roleMapper.toDomain(
                new RoleEntity("id-3", RoleName.WAREHOUSE_WORKER, new HashSet<>(), "Warehouse")
        );

        // Assert
        assertThat(adminRole.name()).isEqualTo(RoleName.ADMIN);
        assertThat(prodWorkerRole.name()).isEqualTo(RoleName.PRODUCTION_WORKER);
        assertThat(warehouseRole.name()).isEqualTo(RoleName.WAREHOUSE_WORKER);
    }

    @Test
    @DisplayName("should_MapAllPermissionTypes_When_DifferentPermissionsUsed")
    void should_MapAllPermissionTypes_When_DifferentPermissionsUsed() {
        // Arrange
        Set<Permission> allPermissions = new HashSet<>(Set.of(
                Permission.USER_READ,
                Permission.BATCH_WRITE,
                Permission.STOCK_READ,
                Permission.ORDER_DELETE,
                Permission.REPORT_GENERATE
        ));
        Role roleWithVariousPerms = roleMapper.toDomain(new RoleEntity(
                "role-various",
                RoleName.ADMIN,
                new HashSet<>(allPermissions),
                "Role with various permissions"
        ));

        // Act
        RoleEntity entity = roleMapper.toEntity(roleWithVariousPerms);

        // Assert
        assertThat(entity.getPermissions()).containsAll(allPermissions);
        assertThat(entity.getPermissions()).hasSize(5);
    }

    @Test
    @DisplayName("should_HandleNullDescription_When_MappingToEntity")
    void should_HandleNullDescription_When_MappingToEntity() {
        // Arrange
        Role roleNullDesc = roleMapper.toDomain(new RoleEntity(
                "role-no-desc",
                RoleName.ADMIN,
                new HashSet<>(permissions),
                null
        ));

        // Act
        RoleEntity entity = roleMapper.toEntity(roleNullDesc);

        // Assert
        assertThat(entity.getDescription()).isNull();
    }

    @Test
    @DisplayName("should_HandleNullDescription_When_MappingToDomain")
    void should_HandleNullDescription_When_MappingToDomain() {
        // Arrange
        RoleEntity entityNullDesc = new RoleEntity(
                "role-no-desc",
                RoleName.ADMIN,
                permissions,
                null
        );

        // Act
        Role role = roleMapper.toDomain(entityNullDesc);

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

    @Test
    @DisplayName("should_MapLargePermissionSet_When_RoleHasManyPermissions")
    void should_MapLargePermissionSet_When_RoleHasManyPermissions() {
        // Arrange
        Set<Permission> largePermSet = new HashSet<>(Set.of(
                Permission.values()  // All permissions
        ));
        Role roleWithManyPerms = roleMapper.toDomain(new RoleEntity(
                "role-admin-full",
                RoleName.ADMIN,
                new HashSet<>(largePermSet),
                "Super admin with all permissions"
        ));

        // Act
        RoleEntity entity = roleMapper.toEntity(roleWithManyPerms);
        Role mappedBack = roleMapper.toDomain(entity);

        // Assert
        assertThat(entity.getPermissions()).hasSize(largePermSet.size());
        assertThat(mappedBack.permissions()).hasSize(largePermSet.size());
    }

    @Test
    @DisplayName("should_PreservePermissionImmutability_When_MappingToEntity")
    void should_PreservePermissionImmutability_When_MappingToEntity() {
        // Act
        RoleEntity entity = roleMapper.toEntity(domainRole);

        // Assert - mapped entity should have same permissions
        assertThat(entity.getPermissions()).containsAll(domainRole.permissions());
    }
}
