package dev.lions.user.manager.service.impl;

import dev.lions.user.manager.dto.realm.RealmAssignmentDTO;
import dev.lions.user.manager.enums.audit.TypeActionAudit;
import dev.lions.user.manager.service.AuditService;
import dev.lions.user.manager.service.RealmAuthorizationService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Implémentation du service d'autorisation multi-tenant par realm
 *
 * NOTE: Cette implémentation utilise un stockage en mémoire (ConcurrentHashMap)
 * Pour la production, migrer vers une base de données PostgreSQL
 */
@ApplicationScoped
@Slf4j
public class RealmAuthorizationServiceImpl implements RealmAuthorizationService {

    @Inject
    AuditService auditService;

    // Stockage temporaire en mémoire (à remplacer par BD en production)
    private final Map<String, RealmAssignmentDTO> assignmentsById = new ConcurrentHashMap<>();
    private final Map<String, Set<String>> userToRealms = new ConcurrentHashMap<>();
    private final Map<String, Set<String>> realmToUsers = new ConcurrentHashMap<>();
    private final Set<String> superAdmins = ConcurrentHashMap.newKeySet();

    @Override
    public List<RealmAssignmentDTO> getAllAssignments() {
        log.debug("Récupération de toutes les assignations de realms");
        return new ArrayList<>(assignmentsById.values());
    }

    @Override
    public List<RealmAssignmentDTO> getAssignmentsByUser(@NotBlank String userId) {
        log.debug("Récupération des assignations pour l'utilisateur: {}", userId);

        return assignmentsById.values().stream()
            .filter(assignment -> assignment.getUserId().equals(userId))
            .filter(RealmAssignmentDTO::isActive)
            .filter(assignment -> !assignment.isExpired())
            .collect(Collectors.toList());
    }

    @Override
    public List<RealmAssignmentDTO> getAssignmentsByRealm(@NotBlank String realmName) {
        log.debug("Récupération des assignations pour le realm: {}", realmName);

        return assignmentsById.values().stream()
            .filter(assignment -> assignment.getRealmName().equals(realmName))
            .filter(RealmAssignmentDTO::isActive)
            .filter(assignment -> !assignment.isExpired())
            .collect(Collectors.toList());
    }

    @Override
    public Optional<RealmAssignmentDTO> getAssignmentById(@NotBlank String assignmentId) {
        log.debug("Récupération de l'assignation: {}", assignmentId);
        return Optional.ofNullable(assignmentsById.get(assignmentId));
    }

    @Override
    public boolean canManageRealm(@NotBlank String userId, @NotBlank String realmName) {
        log.debug("Vérification si {} peut gérer le realm {}", userId, realmName);

        // Super admin peut tout gérer
        if (isSuperAdmin(userId)) {
            return true;
        }

        // Vérifier les assignations actives et non expirées
        return assignmentsById.values().stream()
            .anyMatch(assignment ->
                assignment.getUserId().equals(userId) &&
                assignment.getRealmName().equals(realmName) &&
                assignment.isActive() &&
                !assignment.isExpired()
            );
    }

    @Override
    public boolean isSuperAdmin(@NotBlank String userId) {
        return superAdmins.contains(userId);
    }

    @Override
    public List<String> getAuthorizedRealms(@NotBlank String userId) {
        log.debug("Récupération des realms autorisés pour: {}", userId);

        // Super admin retourne liste vide (convention: peut tout gérer)
        if (isSuperAdmin(userId)) {
            return Collections.emptyList();
        }

        // Retourner les realms assignés actifs et non expirés
        return assignmentsById.values().stream()
            .filter(assignment -> assignment.getUserId().equals(userId))
            .filter(RealmAssignmentDTO::isActive)
            .filter(assignment -> !assignment.isExpired())
            .map(RealmAssignmentDTO::getRealmName)
            .distinct()
            .collect(Collectors.toList());
    }

    @Override
    public RealmAssignmentDTO assignRealmToUser(@Valid @NotNull RealmAssignmentDTO assignment) {
        log.info("Assignation du realm {} à l'utilisateur {}",
            assignment.getRealmName(), assignment.getUserId());

        // Validation
        if (assignment.getUserId() == null || assignment.getUserId().isBlank()) {
            throw new IllegalArgumentException("L'ID utilisateur est obligatoire");
        }
        if (assignment.getRealmName() == null || assignment.getRealmName().isBlank()) {
            throw new IllegalArgumentException("Le nom du realm est obligatoire");
        }

        // Vérifier si l'assignation existe déjà
        if (assignmentExists(assignment.getUserId(), assignment.getRealmName())) {
            throw new IllegalArgumentException(
                String.format("L'utilisateur %s a déjà accès au realm %s",
                    assignment.getUserId(), assignment.getRealmName())
            );
        }

        // Générer ID si absent
        if (assignment.getId() == null) {
            assignment.setId(UUID.randomUUID().toString());
        }

        // Compléter les métadonnées
        assignment.setAssignedAt(LocalDateTime.now());
        assignment.setActive(true);
        assignment.setDateCreation(LocalDateTime.now());

        // Stocker l'assignation
        assignmentsById.put(assignment.getId(), assignment);

        // Mettre à jour les index
        userToRealms.computeIfAbsent(assignment.getUserId(), k -> ConcurrentHashMap.newKeySet())
            .add(assignment.getRealmName());
        realmToUsers.computeIfAbsent(assignment.getRealmName(), k -> ConcurrentHashMap.newKeySet())
            .add(assignment.getUserId());

        // Audit
        auditService.logSuccess(
            TypeActionAudit.REALM_ASSIGN,
            "REALM_ASSIGNMENT",
            assignment.getId(),
            assignment.getUsername(),
            assignment.getRealmName(),
            assignment.getAssignedBy() != null ? assignment.getAssignedBy() : "system",
            String.format("Assignation du realm %s à %s", assignment.getRealmName(), assignment.getUsername())
        );

        log.info("Realm {} assigné avec succès à {}", assignment.getRealmName(), assignment.getUserId());
        return assignment;
    }

    @Override
    public void revokeRealmFromUser(@NotBlank String userId, @NotBlank String realmName) {
        log.info("Révocation du realm {} pour l'utilisateur {}", realmName, userId);

        // Trouver et supprimer l'assignation
        Optional<RealmAssignmentDTO> assignment = assignmentsById.values().stream()
            .filter(a -> a.getUserId().equals(userId) && a.getRealmName().equals(realmName))
            .findFirst();

        if (assignment.isEmpty()) {
            log.warn("Aucune assignation trouvée pour {} / {}", userId, realmName);
            return;
        }

        RealmAssignmentDTO assignmentToRemove = assignment.get();
        assignmentsById.remove(assignmentToRemove.getId());

        // Mettre à jour les index
        Set<String> realms = userToRealms.get(userId);
        if (realms != null) {
            realms.remove(realmName);
            if (realms.isEmpty()) {
                userToRealms.remove(userId);
            }
        }

        Set<String> users = realmToUsers.get(realmName);
        if (users != null) {
            users.remove(userId);
            if (users.isEmpty()) {
                realmToUsers.remove(realmName);
            }
        }

        // Audit
        auditService.logSuccess(
            TypeActionAudit.REALM_REVOKE,
            "REALM_ASSIGNMENT",
            assignmentToRemove.getId(),
            assignmentToRemove.getUsername(),
            realmName,
            "system",
            String.format("Révocation du realm %s pour %s", realmName, assignmentToRemove.getUsername())
        );

        log.info("Realm {} révoqué avec succès pour {}", realmName, userId);
    }

    @Override
    public void revokeAllRealmsFromUser(@NotBlank String userId) {
        log.info("Révocation de tous les realms pour l'utilisateur {}", userId);

        List<RealmAssignmentDTO> userAssignments = getAssignmentsByUser(userId);
        userAssignments.forEach(assignment ->
            revokeRealmFromUser(userId, assignment.getRealmName())
        );

        log.info("{} realm(s) révoqué(s) pour {}", userAssignments.size(), userId);
    }

    @Override
    public void revokeAllUsersFromRealm(@NotBlank String realmName) {
        log.info("Révocation de tous les utilisateurs du realm {}", realmName);

        List<RealmAssignmentDTO> realmAssignments = getAssignmentsByRealm(realmName);
        realmAssignments.forEach(assignment ->
            revokeRealmFromUser(assignment.getUserId(), realmName)
        );

        log.info("{} utilisateur(s) révoqué(s) du realm {}", realmAssignments.size(), realmName);
    }

    @Override
    public void setSuperAdmin(@NotBlank String userId, boolean superAdmin) {
        log.info("Définition de {} comme super admin: {}", userId, superAdmin);

        if (superAdmin) {
            superAdmins.add(userId);
            auditService.logSuccess(
                TypeActionAudit.REALM_SET_SUPER_ADMIN,
                "USER",
                userId,
                userId,
                "lions-user-manager",
                "system",
                String.format("Utilisateur %s défini comme super admin", userId)
            );
        } else {
            superAdmins.remove(userId);
            auditService.logSuccess(
                TypeActionAudit.REALM_SET_SUPER_ADMIN,
                "USER",
                userId,
                userId,
                "lions-user-manager",
                "system",
                String.format("Privilèges super admin retirés pour %s", userId)
            );
        }
    }

    @Override
    public void deactivateAssignment(@NotBlank String assignmentId) {
        log.info("Désactivation de l'assignation {}", assignmentId);

        RealmAssignmentDTO assignment = assignmentsById.get(assignmentId);
        if (assignment == null) {
            throw new IllegalArgumentException("Assignation non trouvée: " + assignmentId);
        }

        assignment.setActive(false);
        assignment.setDateModification(LocalDateTime.now());

        auditService.logSuccess(
            TypeActionAudit.REALM_DEACTIVATE,
            "REALM_ASSIGNMENT",
            assignment.getId(),
            assignment.getUsername(),
            assignment.getRealmName(),
            "system",
            String.format("Désactivation de l'assignation %s", assignmentId)
        );
    }

    @Override
    public void activateAssignment(@NotBlank String assignmentId) {
        log.info("Activation de l'assignation {}", assignmentId);

        RealmAssignmentDTO assignment = assignmentsById.get(assignmentId);
        if (assignment == null) {
            throw new IllegalArgumentException("Assignation non trouvée: " + assignmentId);
        }

        assignment.setActive(true);
        assignment.setDateModification(LocalDateTime.now());

        auditService.logSuccess(
            TypeActionAudit.REALM_ACTIVATE,
            "REALM_ASSIGNMENT",
            assignment.getId(),
            assignment.getUsername(),
            assignment.getRealmName(),
            "system",
            String.format("Activation de l'assignation %s", assignmentId)
        );
    }

    @Override
    public long countAssignmentsByUser(@NotBlank String userId) {
        return assignmentsById.values().stream()
            .filter(assignment -> assignment.getUserId().equals(userId))
            .filter(RealmAssignmentDTO::isActive)
            .filter(assignment -> !assignment.isExpired())
            .count();
    }

    @Override
    public long countUsersByRealm(@NotBlank String realmName) {
        return assignmentsById.values().stream()
            .filter(assignment -> assignment.getRealmName().equals(realmName))
            .filter(RealmAssignmentDTO::isActive)
            .filter(assignment -> !assignment.isExpired())
            .map(RealmAssignmentDTO::getUserId)
            .distinct()
            .count();
    }

    @Override
    public boolean assignmentExists(@NotBlank String userId, @NotBlank String realmName) {
        return assignmentsById.values().stream()
            .anyMatch(assignment ->
                assignment.getUserId().equals(userId) &&
                assignment.getRealmName().equals(realmName) &&
                assignment.isActive()
            );
    }
}
