package dev.lions.unionflow.server.service.mutuelle.epargne;

import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneResponse;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.mapper.mutuelle.epargne.CompteEpargneMapper;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.service.OrganisationService;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;

import io.quarkus.security.identity.SecurityIdentity;

import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Service métier pour la gestion des comptes d'épargne.
 */
@ApplicationScoped
public class CompteEpargneService {

    @Inject
    CompteEpargneRepository compteEpargneRepository;

    @Inject
    MembreRepository membreRepository;

    @Inject
    OrganisationRepository organisationRepository;

    @Inject
    CompteEpargneMapper compteEpargneMapper;

    @Inject
    SecurityIdentity securityIdentity;

    @Inject
    OrganisationService organisationService;

    /**
     * Crée un nouveau compte d'épargne.
     *
     * @param request Le DTO contenant les informations du compte.
     * @return Le DTO du compte créé.
     */
    @Transactional
    public CompteEpargneResponse creerCompte(CompteEpargneRequest request) {
        Membre membre = membreRepository.findByIdOptional(UUID.fromString(request.getMembreId()))
                .orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + request.getMembreId()));

        Organisation organisation = organisationRepository
                .findByIdOptional(UUID.fromString(request.getOrganisationId()))
                .orElseThrow(() -> new NotFoundException(
                        "Organisation non trouvée avec l'ID: " + request.getOrganisationId()));

        CompteEpargne compte = compteEpargneMapper.toEntity(request);
        compte.setMembre(membre);
        compte.setOrganisation(organisation);

        // Par défaut, le compte est actif (dateOuverture déjà initialisée dans l'entité)
        compte.setStatut(StatutCompteEpargne.ACTIF);

        // Générer un numéro de compte s'il n'est pas fourni (il n'est pas dans le DTO
        // de requête actuel)
        compte.setNumeroCompte(genererNumeroCompte(organisation.getNom()));

        compteEpargneRepository.persist(compte);
        return compteEpargneMapper.toDto(compte);
    }

    private String genererNumeroCompte(String nomOrga) {
        String prefix = nomOrga.length() >= 3 ? nomOrga.substring(0, 3).toUpperCase() : "ORG";
        return prefix + "-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }

    /**
     * Récupère un compte d'épargne par son ID.
     *
     * @param id L'UUID du compte.
     * @return Le DTO du compte.
     */
    public CompteEpargneResponse getCompteById(UUID id) {
        CompteEpargne compte = compteEpargneRepository.findByIdOptional(id)
                .orElseThrow(() -> new NotFoundException("Compte non trouvé avec l'ID: " + id));
        return compteEpargneMapper.toDto(compte);
    }

    /**
     * Liste les comptes d'épargne selon le rôle :
     * - ADMIN / ADMIN_ORGANISATION : tous les comptes des organisations dont l'utilisateur est admin.
     * - Membre : les comptes du membre connecté (email depuis SecurityIdentity).
     *
     * @return La liste des comptes visibles pour l'utilisateur connecté.
     */
    public List<CompteEpargneResponse> getMesComptes() {
        java.security.Principal principal = securityIdentity.getPrincipal();
        String email = principal != null ? principal.getName() : null;
        if (email == null || email.isBlank()) {
            return Collections.emptyList();
        }
        Set<String> roles = securityIdentity.getRoles();
        if (roles != null && (roles.contains("ADMIN") || roles.contains("ADMIN_ORGANISATION"))) {
            List<Organisation> orgs = organisationService.listerOrganisationsPourUtilisateur(email);
            if (orgs == null || orgs.isEmpty()) {
                return Collections.emptyList();
            }
            return orgs.stream()
                    .flatMap(org -> getComptesByOrganisation(org.getId()).stream())
                    .distinct()
                    .collect(Collectors.toList());
        }
        return membreRepository.findByEmail(email)
                .map(m -> getComptesByMembre(m.getId()))
                .orElse(Collections.emptyList());
    }

    /**
     * Liste tous les comptes d'épargne d'un membre.
     *
     * @param membreId L'UUID du membre.
     * @return La liste des comptes.
     */
    public List<CompteEpargneResponse> getComptesByMembre(UUID membreId) {
        return compteEpargneRepository.find("membre.id = ?1 and actif = true", membreId)
                .stream()
                .map(compteEpargneMapper::toDto)
                .collect(Collectors.toList());
    }

    /**
     * Liste tous les comptes d'épargne d'une organisation.
     *
     * @param organisationId L'UUID de l'organisation.
     * @return La liste des comptes.
     */
    public List<CompteEpargneResponse> getComptesByOrganisation(UUID organisationId) {
        return compteEpargneRepository.find("organisation.id = ?1 and actif = true", organisationId)
                .stream()
                .map(compteEpargneMapper::toDto)
                .collect(Collectors.toList());
    }

    /**
     * Met à jour le statut d'une compte.
     *
     * @param id     L'UUID du compte.
     * @param statut Le nouveau statut.
     * @return Le DTO mis à jour.
     */
    @Transactional
    public CompteEpargneResponse changerStatut(UUID id, StatutCompteEpargne statut) {
        CompteEpargne compte = compteEpargneRepository.findByIdOptional(id)
                .orElseThrow(() -> new NotFoundException("Compte non trouvé avec l'ID: " + id));

        compte.setStatut(statut);
        return compteEpargneMapper.toDto(compte);
    }
}
