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

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

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Set;

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

/**
 * Unit tests for UserMapper.
 * Tests bidirectional mapping between User domain entity and UserEntity JPA entity.
 *
 * Uses real RoleMapper (no mocks) because both mappers are simple stateless components.
 * Domain User/Role objects are created via the mapper's toDomain() method which internally
 * calls the package-private reconstitute() factory method.
 */
@DisplayName("UserMapper")
class UserMapperTest {

    private final RoleMapper roleMapper = new RoleMapper();
    private final UserMapper userMapper = new UserMapper(roleMapper);

    private User domainUser;
    private UserEntity jpaEntity;
    private OffsetDateTime createdAt;

    @BeforeEach
    void setUp() {
        createdAt = OffsetDateTime.now(ZoneOffset.UTC);

        // Create JPA entity first
        jpaEntity = new UserEntity(
                "user-123",
                "john.doe",
                "john@example.com",
                "$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(),
                "branch-1",
                UserStatus.ACTIVE,
                createdAt,
                null
        );

        // Create domain user via mapper (which internally uses reconstitute)
        domainUser = userMapper.toDomain(jpaEntity);
    }

    @Test
    @DisplayName("should_MapUserToEntity_When_DomainUserProvided")
    void should_MapUserToEntity_When_DomainUserProvided() {
        // Act
        UserEntity entity = userMapper.toEntity(domainUser);

        // Assert
        assertThat(entity).isNotNull();
        assertThat(entity.getId()).isEqualTo("user-123");
        assertThat(entity.getUsername()).isEqualTo("john.doe");
        assertThat(entity.getEmail()).isEqualTo("john@example.com");
        assertThat(entity.getPasswordHash()).isEqualTo("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
        assertThat(entity.getBranchId()).isEqualTo("branch-1");
        assertThat(entity.getStatus()).isEqualTo(UserStatus.ACTIVE);
        assertThat(entity.getCreatedAt()).isEqualTo(createdAt);
        assertThat(entity.getLastLogin()).isNull();
    }

    @Test
    @DisplayName("should_MapUserToDomain_When_JpaEntityProvided")
    void should_MapUserToDomain_When_JpaEntityProvided() {
        // Act
        User user = userMapper.toDomain(jpaEntity);

        // Assert
        assertThat(user).isNotNull();
        assertThat(user.id().value()).isEqualTo("user-123");
        assertThat(user.username()).isEqualTo("john.doe");
        assertThat(user.email()).isEqualTo("john@example.com");
        assertThat(user.passwordHash().value()).isEqualTo("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
        assertThat(user.branchId()).isEqualTo("branch-1");
        assertThat(user.status()).isEqualTo(UserStatus.ACTIVE);
        assertThat(user.createdAt()).isEqualTo(createdAt);
        assertThat(user.lastLogin()).isNull();
    }

    @Test
    @DisplayName("should_ReturnNull_When_NullUserProvidedToToEntity")
    void should_ReturnNull_When_NullUserProvidedToToEntity() {
        // Act
        UserEntity entity = userMapper.toEntity(null);

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

    @Test
    @DisplayName("should_ReturnNull_When_NullEntityProvidedToToDomain")
    void should_ReturnNull_When_NullEntityProvidedToToDomain() {
        // Act
        User user = userMapper.toDomain(null);

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

    @Test
    @DisplayName("should_PreserveAllUserFields_When_MappingToEntity")
    void should_PreserveAllUserFields_When_MappingToEntity() {
        // Arrange
        OffsetDateTime lastLogin = OffsetDateTime.now(ZoneOffset.UTC);
        UserEntity sourceEntity = new UserEntity(
                "user-456",
                "jane.smith",
                "jane@example.com",
                "$2b$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(),
                "branch-2",
                UserStatus.LOCKED,
                createdAt,
                lastLogin
        );
        User userWithLastLogin = userMapper.toDomain(sourceEntity);

        // Act
        UserEntity entity = userMapper.toEntity(userWithLastLogin);

        // Assert
        assertThat(entity.getId()).isEqualTo("user-456");
        assertThat(entity.getUsername()).isEqualTo("jane.smith");
        assertThat(entity.getEmail()).isEqualTo("jane@example.com");
        assertThat(entity.getPasswordHash()).isEqualTo("$2b$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
        assertThat(entity.getBranchId()).isEqualTo("branch-2");
        assertThat(entity.getStatus()).isEqualTo(UserStatus.LOCKED);
        assertThat(entity.getCreatedAt()).isEqualTo(createdAt);
        assertThat(entity.getLastLogin()).isEqualTo(lastLogin);
    }

    @Test
    @DisplayName("should_PreserveAllEntityFields_When_MappingToDomain")
    void should_PreserveAllEntityFields_When_MappingToDomain() {
        // Arrange
        OffsetDateTime lastLogin = OffsetDateTime.now(ZoneOffset.UTC);
        UserEntity entityWithLastLogin = new UserEntity(
                "user-789",
                "bob.jones",
                "bob@example.com",
                "$2y$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(),
                "branch-3",
                UserStatus.INACTIVE,
                createdAt,
                lastLogin
        );

        // Act
        User user = userMapper.toDomain(entityWithLastLogin);

        // Assert
        assertThat(user.id().value()).isEqualTo("user-789");
        assertThat(user.username()).isEqualTo("bob.jones");
        assertThat(user.email()).isEqualTo("bob@example.com");
        assertThat(user.passwordHash().value()).isEqualTo("$2y$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
        assertThat(user.branchId()).isEqualTo("branch-3");
        assertThat(user.status()).isEqualTo(UserStatus.INACTIVE);
        assertThat(user.createdAt()).isEqualTo(createdAt);
        assertThat(user.lastLogin()).isEqualTo(lastLogin);
    }

    @Test
    @DisplayName("should_MapRoleEntitiesCorrectly_When_UserHasRoles")
    void should_MapRoleEntitiesCorrectly_When_UserHasRoles() {
        // Arrange
        RoleEntity roleEntity1 = new RoleEntity("role-1", RoleName.ADMIN, new HashSet<>(), "Admin");
        RoleEntity roleEntity2 = new RoleEntity("role-2", RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker");

        UserEntity userEntityWithRoles = new UserEntity(
                "user-999",
                "john.doe",
                "john@example.com",
                "$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(Set.of(roleEntity1, roleEntity2)),
                "branch-1",
                UserStatus.ACTIVE,
                createdAt,
                null
        );

        // Map entity to domain (toDomain maps roles via real roleMapper)
        User userWithRoles = userMapper.toDomain(userEntityWithRoles);

        // Act
        UserEntity entity = userMapper.toEntity(userWithRoles);

        // Assert
        assertThat(entity.getRoles()).hasSize(2);
    }

    @Test
    @DisplayName("should_HandleEmptyRoleSet_When_UserHasNoRoles")
    void should_HandleEmptyRoleSet_When_UserHasNoRoles() {
        // Act
        UserEntity entity = userMapper.toEntity(domainUser);

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

    @Test
    @DisplayName("should_HandleNullRolesInEntity_When_MappingToDomain")
    void should_HandleNullRolesInEntity_When_MappingToDomain() {
        // Arrange
        jpaEntity.setRoles(null);

        // Act
        User user = userMapper.toDomain(jpaEntity);

        // Assert
        assertThat(user.roles()).isEmpty();
    }

    @Test
    @DisplayName("should_BidirectionalMapping_When_MappingUserBackAndForth")
    void should_BidirectionalMapping_When_MappingUserBackAndForth() {
        // Act
        UserEntity entity = userMapper.toEntity(domainUser);
        User mappedBackUser = userMapper.toDomain(entity);

        // Assert
        assertThat(mappedBackUser.id().value()).isEqualTo(domainUser.id().value());
        assertThat(mappedBackUser.username()).isEqualTo(domainUser.username());
        assertThat(mappedBackUser.email()).isEqualTo(domainUser.email());
        assertThat(mappedBackUser.passwordHash().value()).isEqualTo(domainUser.passwordHash().value());
        assertThat(mappedBackUser.branchId()).isEqualTo(domainUser.branchId());
        assertThat(mappedBackUser.status()).isEqualTo(domainUser.status());
    }

    @Test
    @DisplayName("should_CreateNewSetForRoles_When_MappingToEntity")
    void should_CreateNewSetForRoles_When_MappingToEntity() {
        // Act
        UserEntity entity = userMapper.toEntity(domainUser);

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

    @Test
    @DisplayName("should_CreateNewSetForRoles_When_MappingToDomain")
    void should_CreateNewSetForRoles_When_MappingToDomain() {
        // Act
        User user = userMapper.toDomain(jpaEntity);

        // Assert
        assertThat(user.roles()).isNotNull();
        assertThat(user.roles()).isInstanceOf(Set.class);
    }

    @Test
    @DisplayName("should_MapAllUserStatuses_When_DifferentStatusesUsed")
    void should_MapAllUserStatuses_When_DifferentStatusesUsed() {
        // Arrange - create users with different statuses via entity -> domain mapping
        User activeUser = userMapper.toDomain(new UserEntity(
                "id-1", "user1", "u1@test.com",
                "$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(), "b1", UserStatus.ACTIVE, createdAt, null));

        User inactiveUser = userMapper.toDomain(new UserEntity(
                "id-2", "user2", "u2@test.com",
                "$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(), "b1", UserStatus.INACTIVE, createdAt, null));

        User lockedUser = userMapper.toDomain(new UserEntity(
                "id-3", "user3", "u3@test.com",
                "$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW",
                new HashSet<>(), "b1", UserStatus.LOCKED, createdAt, null));

        // Act
        UserEntity activeEntity = userMapper.toEntity(activeUser);
        UserEntity inactiveEntity = userMapper.toEntity(inactiveUser);
        UserEntity lockedEntity = userMapper.toEntity(lockedUser);

        // Assert
        assertThat(activeEntity.getStatus()).isEqualTo(UserStatus.ACTIVE);
        assertThat(inactiveEntity.getStatus()).isEqualTo(UserStatus.INACTIVE);
        assertThat(lockedEntity.getStatus()).isEqualTo(UserStatus.LOCKED);
    }
}
