package org.raddatz.familienarchiv.audit;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.user.AdminUpdateUserRequest;
import org.raddatz.familienarchiv.user.CreateUserRequest;
import org.raddatz.familienarchiv.user.GroupDTO;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.user.UserGroup;
import org.raddatz.familienarchiv.user.AppUserRepository;
import org.raddatz.familienarchiv.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.transaction.support.TransactionTemplate;
import software.amazon.awssdk.services.s3.S3Client;

import java.util.List;
import java.util.Set;
import java.util.UUID;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles("test")
@Import(PostgresContainerConfig.class)
class UserManagementAuditIntegrationTest {

    @MockitoBean S3Client s3Client;
    @Autowired UserService userService;
    @Autowired AppUserRepository userRepository;
    @Autowired AuditLogRepository auditLogRepository;
    @Autowired AuditLogQueryService auditLogQueryService;
    @Autowired TransactionTemplate transactionTemplate;

    @BeforeEach
    void clearAuditLog() {
        transactionTemplate.execute(status -> { auditLogRepository.deleteAll(); return null; });
    }

    @Test
    void createAndDeleteUser_producesOrderedAuditEntries() {
        // Bootstrap actor with no audit event — clean slate guaranteed by @BeforeEach
        CreateUserRequest adminReq = new CreateUserRequest();
        adminReq.setEmail("admin@test.example.com");
        adminReq.setInitialPassword("admin-secret");
        AppUser actor = transactionTemplate.execute(status -> userService.createUserForBootstrap(adminReq));
        UUID actorId = actor.getId();

        // Create the target user — should emit USER_CREATED
        CreateUserRequest req = new CreateUserRequest();
        req.setEmail("audit-test@example.com");
        req.setInitialPassword("secret");
        transactionTemplate.execute(status -> {
            userService.createUserOrUpdate(actorId, req);
            return null;
        });
        await().atMost(10, SECONDS).until(() -> auditLogRepository.existsByKind(AuditKind.USER_CREATED));

        // Delete the target user — should emit USER_DELETED
        AppUser created = userRepository.findByEmail("audit-test@example.com").orElseThrow();
        transactionTemplate.execute(status -> {
            userService.deleteUser(actorId, created.getId());
            return null;
        });
        await().atMost(10, SECONDS).until(() -> auditLogRepository.existsByKind(AuditKind.USER_DELETED));

        List<AuditLog> events = auditLogQueryService.findRecentUserManagementEvents(10);
        assertThat(events).hasSize(2);
        assertThat(events.get(0).getKind()).isEqualTo(AuditKind.USER_DELETED);
        assertThat(events.get(1).getKind()).isEqualTo(AuditKind.USER_CREATED);
    }

    @Test
    void updateUserGroups_producesGroupMembershipChangedEvent() {
        GroupDTO groupADto = new GroupDTO(); groupADto.setName("Viewers"); groupADto.setPermissions(Set.of("READ_ALL"));
        GroupDTO groupBDto = new GroupDTO(); groupBDto.setName("Editors"); groupBDto.setPermissions(Set.of("WRITE_ALL"));
        UserGroup gA = transactionTemplate.execute(status -> userService.createGroup(groupADto));
        UserGroup gB = transactionTemplate.execute(status -> userService.createGroup(groupBDto));

        // Bootstrap actor with no audit event — clean slate guaranteed by @BeforeEach
        CreateUserRequest actorReq = new CreateUserRequest();
        actorReq.setEmail("actor-group-test@test.example.com");
        actorReq.setInitialPassword("secret");
        AppUser actor = transactionTemplate.execute(status -> userService.createUserForBootstrap(actorReq));

        // Create target user pre-assigned to gA — emits USER_CREATED
        CreateUserRequest targetReq = new CreateUserRequest();
        targetReq.setEmail("target-group-test@test.example.com");
        targetReq.setInitialPassword("secret");
        targetReq.setGroupIds(List.of(gA.getId()));
        transactionTemplate.execute(status -> userService.createUserOrUpdate(actor.getId(), targetReq));
        await().atMost(10, SECONDS).until(() -> auditLogRepository.existsByKind(AuditKind.USER_CREATED));
        transactionTemplate.execute(status -> { auditLogRepository.deleteAll(); return null; });

        AppUser target = userRepository.findByEmail("target-group-test@test.example.com").orElseThrow();

        // Change groups: Viewers → Editors
        AdminUpdateUserRequest dto = new AdminUpdateUserRequest();
        dto.setGroupIds(List.of(gB.getId()));
        transactionTemplate.execute(status -> userService.adminUpdateUser(actor.getId(), target.getId(), dto));

        await().atMost(10, SECONDS).until(() -> auditLogRepository.existsByKind(AuditKind.GROUP_MEMBERSHIP_CHANGED));

        List<AuditLog> events = auditLogQueryService.findRecentUserManagementEvents(10);
        assertThat(events).hasSize(1);
        AuditLog event = events.get(0);
        assertThat(event.getKind()).isEqualTo(AuditKind.GROUP_MEMBERSHIP_CHANGED);
        assertThat(event.getPayload()).containsEntry("email", "target-group-test@test.example.com");
        @SuppressWarnings("unchecked")
        List<String> added = (List<String>) event.getPayload().get("addedGroups");
        @SuppressWarnings("unchecked")
        List<String> removed = (List<String>) event.getPayload().get("removedGroups");
        assertThat(added).containsExactlyInAnyOrder("Editors");
        assertThat(removed).containsExactlyInAnyOrder("Viewers");
    }
}
