package com.iqser.red.service.persistence.management.v1.processor.service.users;

import static com.iqser.red.service.persistence.management.v1.processor.configuration.UserMessagingConfiguration.PERSISTENCE_SERVICE_USER_CREATED_QUEUE;
import static com.iqser.red.service.persistence.management.v1.processor.configuration.UserMessagingConfiguration.PERSISTENCE_SERVICE_USER_DELETED_QUEUE;
import static com.iqser.red.service.persistence.management.v1.processor.configuration.UserMessagingConfiguration.PERSISTENCE_SERVICE_USER_OWN_PROFILE_UPDATED_QUEUE;
import static com.iqser.red.service.persistence.management.v1.processor.configuration.UserMessagingConfiguration.PERSISTENCE_SERVICE_USER_ROLES_UPDATED_QUEUE;
import static com.iqser.red.service.persistence.management.v1.processor.configuration.UserMessagingConfiguration.PERSISTENCE_SERVICE_USER_STATUS_CHANGED_QUEUE;
import static com.iqser.red.service.persistence.management.v1.processor.configuration.UserMessagingConfiguration.PERSISTENCE_SERVICE_USER_UPDATED_QUEUE;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles.RED_MANAGER_ROLE;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles.RED_USER_ROLE;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.service.CustomPermissionService;
import com.iqser.red.service.persistence.management.v1.processor.client.tenantusermanagementservice.UsersClient;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.events.UserCreatedEvent;
import com.iqser.red.service.persistence.management.v1.processor.service.users.events.UserRemovedEvent;
import com.iqser.red.service.persistence.management.v1.processor.service.users.events.UserRolesUpdatedEvent;
import com.iqser.red.service.persistence.management.v1.processor.service.users.events.UserStatusToggleEvent;
import com.iqser.red.service.persistence.management.v1.processor.service.users.events.UserUpdatedEvent;
import com.iqser.red.service.persistence.management.v1.processor.service.users.events.UserUpdatedOwnProfileEvent;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AddNotificationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationType;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final DossierManagementService dossierManagementService;
    private final FileStatusManagementService fileStatusManagementService;
    private final AuditPersistenceService auditPersistenceService;
    private final NotificationPersistenceService notificationPersistenceService;
    private final DossierACLService dossierACLService;
    private final CustomPermissionService customPermissionService;
    private final UsersClient usersClient;



    @RabbitListener(queues = PERSISTENCE_SERVICE_USER_CREATED_QUEUE)
    public void userCreated(UserCreatedEvent user) {

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(user.getCreatingUserId())
                .objectId(user.getUser().getUserId())
                .category(AuditCategory.USER.name())
                .message("User created")
                .build());

        customPermissionService.syncAllCustomPermissions();

    }

    @RabbitListener(queues = PERSISTENCE_SERVICE_USER_ROLES_UPDATED_QUEUE)
    public void rolesUpdated(UserRolesUpdatedEvent userRolesUpdatedEvent) {

        var newRoles = userRolesUpdatedEvent.getNewRoles();

        var isUser = userRolesUpdatedEvent.getOldRoles().contains(RED_USER_ROLE); // || currentRoles.contains(RED_MANAGER_ROLE);
        var isManager = userRolesUpdatedEvent.getOldRoles().contains(RED_MANAGER_ROLE);

        if (!newRoles.contains(RED_MANAGER_ROLE) && isManager) {
            removeUserFromDossiers(userRolesUpdatedEvent.getUser().getUserId(), UserRemovalModel.REMOVE_MANAGER);
        }

        if (!newRoles.contains(RED_USER_ROLE) && !newRoles.contains(RED_MANAGER_ROLE) && isUser) {
            removeUserFromDossiers(userRolesUpdatedEvent.getUser().getUserId(), UserRemovalModel.REMOVE_USER);
        }

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(userRolesUpdatedEvent.getModifyingUserId())
                .objectId(userRolesUpdatedEvent.getUser().getUserId())
                .category(AuditCategory.USER.name())
                .message("Roles updated for user")
                .details(Map.of("CurrentRoles", newRoles))
                .build());
    }


    private void removeUserFromDossiers(String userId, UserRemovalModel mode) {

        dossierManagementService.getAllDossiers(true, true).forEach(dossier -> {

            dossierACLService.enhanceDossierWithACLData(dossier);

            updateDossierUsers(userId, mode, dossier);
        });

    }


    public void updateDossierUsers(String userId, UserRemovalModel mode, Dossier dossier) {

        if (mode == UserRemovalModel.REMOVE_USER || mode == UserRemovalModel.PERMANENT) {
            // remove from members in case we removed the user role or we permanently removed this user
            dossier.getMemberIds().remove(userId);
            dossier.getApproverIds().remove(userId);
        }
        fileStatusManagementService.getAllDossierStatus(dossier.getId()).forEach(fileStatus -> {
            if (userId.equals(fileStatus.getAssignee()) && (mode == UserRemovalModel.REMOVE_USER || mode == UserRemovalModel.PERMANENT)) {
                fileStatusManagementService.setCurrentFileAssignee(dossier.getId(), fileStatus.getId(), null);
            }
        });

        if (userId.equals(dossier.getOwnerId()) && (mode == UserRemovalModel.REMOVE_MANAGER || mode == UserRemovalModel.PERMANENT)) {
            dossier.setOwnerId(null);

            dossier.getMemberIds()
                    .forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
                            .userId(member)
                            .issuerId(KeycloakSecurity.getUserId())
                            .notificationType(NotificationType.DOSSIER_OWNER_DELETED.name())
                            .target(Map.of("dossierId", dossier.getId()))
                            .build()));
        }

        dossierManagementService.updateDossier(CreateOrUpdateDossierRequest.builder()
                .dossierTemplateId(dossier.getDossierTemplateId())
                .dossierName(dossier.getDossierName())
                .description(dossier.getDescription())
                .dossierTemplateId(dossier.getDossierTemplateId())
                .downloadFileTypes(dossier.getDownloadFileTypes())
                .dueDate(dossier.getDueDate())
                .reportTemplateIds(Lists.newArrayList(dossier.getReportTemplateIds()))
                .watermarkId(dossier.getWatermarkId())
                .previewWatermarkId(dossier.getPreviewWatermarkId())
                .dossierStatusId(dossier.getDossierStatusId())
                .build(), dossier.getId());

        dossierACLService.updateDossierACL(dossier.getMemberIds(), dossier.getApproverIds(), dossier.getOwnerId(), dossier.getId());
    }


    @RabbitListener(queues = PERSISTENCE_SERVICE_USER_OWN_PROFILE_UPDATED_QUEUE)
    public void myProfileUpdated(UserUpdatedOwnProfileEvent userUpdatedOwnProfileEvent) {

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(userUpdatedOwnProfileEvent.getUser().getUserId())
                .objectId(userUpdatedOwnProfileEvent.getUser().getUserId())
                .category(AuditCategory.USER.name())
                .message("Profile updated for user")
                .details(Map.of("Profile", userUpdatedOwnProfileEvent.getUser()))
                .build());
    }

    @RabbitListener(queues = PERSISTENCE_SERVICE_USER_DELETED_QUEUE)
    public void userRemoved(UserRemovedEvent userRemovedEvent) {

        removeUserFromDossiers(userRemovedEvent.getUser().getUserId(), UserRemovalModel.PERMANENT);

        customPermissionService.syncAllCustomPermissions();

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(userRemovedEvent.getDeletingUserId())
                .objectId(userRemovedEvent.getUser().getUserId())
                .category(AuditCategory.USER.name())
                .message("User removed")
                .build());

    }


    /*
     * If a user is deleted via Keycloak, the user ist still in the RedactManager database, thus we have to clean this
     */
    public Set<String> removeDeletedUsers(@NotNull Set<String> userIds) {

        var users = usersClient.getAllUsers(true);
        Set<String> deletedUsers = new HashSet<>();

        for (String userId : userIds) {
            if (users.stream().filter(u -> u.getUserId().equalsIgnoreCase(userId)).findAny().isEmpty()) {
                log.info("Will delete {} user", userId);

                removeUserFromDossiers(userId, UserRemovalModel.PERMANENT);

                auditPersistenceService.audit(AuditRequest.builder().objectId(userId).category(AuditCategory.USER.name()).message("User removed automatically").build());
                deletedUsers.add(userId);
            }
        }

        customPermissionService.syncAllCustomPermissions();
        return deletedUsers;
    }


    @RabbitListener(queues = PERSISTENCE_SERVICE_USER_UPDATED_QUEUE)
    public void userUpdated(UserUpdatedEvent userUpdatedEvent) {

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(userUpdatedEvent.getModifyingUserId())
                .objectId(userUpdatedEvent.getUser().getUserId())
                .category(AuditCategory.USER.name())
                .message("Profile updated for user")
                .details(Map.of("Profile", userUpdatedEvent.getUser()))
                .build());
    }


    @RabbitListener(queues = PERSISTENCE_SERVICE_USER_STATUS_CHANGED_QUEUE)
    public void userStatusToggled(UserStatusToggleEvent userStatusToggleEvent) {

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(userStatusToggleEvent.getModifyingUserId())
                .objectId(userStatusToggleEvent.getUser().getUserId())
                .category(AuditCategory.USER.name())
                .message("Profile activated/deactivated for user")
                .details(Map.of("Profile activated", userStatusToggleEvent.getUser().isActive()))
                .build());

    }


    public Optional<User> getUserById(String userId) {

        return usersClient.getAllUsers(false).stream().filter(u -> u.getUserId().equalsIgnoreCase(userId)).findAny();
    }


    public List<User> getUsersByIds(Set<String> actualMemberIds) {
        return usersClient.getAllUsers(false).stream().filter(u -> actualMemberIds.contains(u.getUserId())).collect(Collectors.toList());
    }


    public enum UserRemovalModel {
        PERMANENT,
        REMOVE_MANAGER,
        REMOVE_USER
    }

}
