package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.ApproverAction;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.TransactionApproval;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.TransactionApprovalRepository;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.ApproveTransactionRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.RejectTransactionRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.response.ApproverActionResponse;
import dev.lions.unionflow.server.api.dto.finance_workflow.response.TransactionApprovalResponse;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.ForbiddenException;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.logging.Logger;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Service métier pour la gestion des approbations de transactions
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-13
 */
@ApplicationScoped
public class ApprovalService {

    private static final Logger LOG = Logger.getLogger(ApprovalService.class);

    @Inject
    TransactionApprovalRepository approvalRepository;

    @Inject
    MembreRepository membreRepository;

    @Inject
    OrganisationRepository organisationRepository;

    @Inject
    JsonWebToken jwt;

    /**
     * Demande une approbation pour une transaction
     */
    @Transactional
    public TransactionApprovalResponse requestApproval(
            UUID transactionId,
            String transactionType,
            Double amount,
            UUID organizationId) {
        LOG.infof("Demande d'approbation pour transaction %s (type: %s, montant: %.2f, org: %s)",
            transactionId, transactionType, amount, organizationId);

        // Récupérer l'utilisateur courant
        String userEmail = jwt.getClaim("email");
        UUID userId = UUID.fromString(jwt.getClaim("sub"));
        Membre membre = membreRepository.findByEmail(userEmail)
            .orElseThrow(() -> new ForbiddenException("Utilisateur non trouvé"));

        // Récupérer l'organisation si fournie
        Organisation organisation = null;
        if (organizationId != null) {
            organisation = organisationRepository.findByIdOptional(organizationId)
                .orElseThrow(() -> new NotFoundException("Organisation non trouvée: " + organizationId));
        }

        // Déterminer le niveau d'approbation requis selon le montant
        String requiredLevel = determineRequiredLevel(amount);

        // Créer la demande d'approbation
        TransactionApproval approval = TransactionApproval.builder()
            .transactionId(transactionId)
            .transactionType(transactionType)
            .amount(java.math.BigDecimal.valueOf(amount))
            .currency("XOF")
            .requesterId(userId)
            .requesterName(membre.getNom() + " " + membre.getPrenom())
            .organisation(organisation)
            .requiredLevel(requiredLevel)
            .status("PENDING")
            .createdAt(LocalDateTime.now())
            .expiresAt(LocalDateTime.now().plusDays(7)) // 7 jours par défaut
            .build();

        approvalRepository.persist(approval);

        LOG.infof("Demande d'approbation créée avec ID: %s (niveau: %s, %d approbations requises)",
            approval.getId(), requiredLevel, approval.getRequiredApprovals());

        return toResponse(approval);
    }

    /**
     * Détermine le niveau d'approbation requis selon le montant
     * Utilise les niveaux standard de l'entité: NONE, LEVEL1, LEVEL2, LEVEL3
     */
    private String determineRequiredLevel(Double amount) {
        if (amount >= 5_000_000) { // 5M XOF
            return "LEVEL3"; // 3 approbations (Board)
        } else if (amount >= 1_000_000) { // 1M XOF
            return "LEVEL2"; // 2 approbations (Director)
        } else if (amount >= 100_000) { // 100K XOF
            return "LEVEL1"; // 1 approbation (Manager)
        } else {
            return "NONE"; // Pas d'approbation requise
        }
    }

    /**
     * Récupère toutes les approbations en attente
     */
    public List<TransactionApprovalResponse> getPendingApprovals(UUID organizationId) {
        LOG.infof("Récupération des approbations en attente pour l'organisation %s", organizationId);

        List<TransactionApproval> approvals = organizationId != null
            ? approvalRepository.findPendingByOrganisation(organizationId)
            : approvalRepository.findPending();

        return approvals.stream()
            .map(this::toResponse)
            .collect(Collectors.toList());
    }

    /**
     * Récupère une approbation par ID
     */
    public TransactionApprovalResponse getApprovalById(UUID approvalId) {
        LOG.infof("Récupération de l'approbation %s", approvalId);

        TransactionApproval approval = approvalRepository.findByIdOptional(approvalId)
            .orElseThrow(() -> new NotFoundException("Approbation non trouvée: " + approvalId));

        return toResponse(approval);
    }

    /**
     * Approuve une transaction
     */
    @Transactional
    public TransactionApprovalResponse approveTransaction(UUID approvalId, ApproveTransactionRequest request) {
        LOG.infof("Approbation de la transaction %s", approvalId);

        // Récupérer l'approbation
        TransactionApproval approval = approvalRepository.findByIdOptional(approvalId)
            .orElseThrow(() -> new NotFoundException("Approbation non trouvée: " + approvalId));

        // Vérifier que l'approbation est en attente
        if (!"PENDING".equals(approval.getStatus())) {
            throw new ForbiddenException("Cette approbation n'est plus en attente");
        }

        // Vérifier que l'approbation n'est pas expirée
        if (approval.isExpired()) {
            approval.setStatus("EXPIRED");
            approvalRepository.persist(approval);
            throw new ForbiddenException("Cette approbation est expirée");
        }

        // Récupérer l'utilisateur courant
        String userEmail = jwt.getClaim("email");
        UUID userId = UUID.fromString(jwt.getClaim("sub"));
        Membre membre = membreRepository.findByEmail(userEmail)
            .orElseThrow(() -> new ForbiddenException("Utilisateur non trouvé"));

        // Vérifier que l'utilisateur n'est pas le demandeur
        if (approval.getRequesterId().equals(userId)) {
            throw new ForbiddenException("Vous ne pouvez pas approuver votre propre demande");
        }

        // Créer l'action d'approbation
        ApproverAction action = ApproverAction.builder()
            .approval(approval)
            .approverId(userId)
            .approverName(membre.getNom() + " " + membre.getPrenom())
            .approverRole(jwt.getClaim("role")) // Récupérer le rôle depuis JWT
            .decision("APPROVED")
            .comment(request.getComment())
            .decidedAt(LocalDateTime.now())
            .build();

        approval.addApproverAction(action);

        // Vérifier si toutes les approbations requises sont reçues
        if (approval.hasAllApprovals()) {
            approval.setStatus("VALIDATED");
            approval.setCompletedAt(LocalDateTime.now());
            LOG.infof("Transaction %s validée avec toutes les approbations", approval.getTransactionId());
        } else {
            approval.setStatus("APPROVED");
            LOG.infof("Transaction %s approuvée (%d/%d)",
                approval.getTransactionId(),
                approval.countApprovals(),
                approval.getRequiredApprovals());
        }

        approvalRepository.persist(approval);

        return toResponse(approval);
    }

    /**
     * Rejette une transaction
     */
    @Transactional
    public TransactionApprovalResponse rejectTransaction(UUID approvalId, RejectTransactionRequest request) {
        LOG.infof("Rejet de la transaction %s", approvalId);

        // Récupérer l'approbation
        TransactionApproval approval = approvalRepository.findByIdOptional(approvalId)
            .orElseThrow(() -> new NotFoundException("Approbation non trouvée: " + approvalId));

        // Vérifier que l'approbation est en attente
        if (!"PENDING".equals(approval.getStatus()) && !"APPROVED".equals(approval.getStatus())) {
            throw new ForbiddenException("Cette approbation ne peut plus être rejetée");
        }

        // Récupérer l'utilisateur courant
        String userEmail = jwt.getClaim("email");
        UUID userId = UUID.fromString(jwt.getClaim("sub"));
        Membre membre = membreRepository.findByEmail(userEmail)
            .orElseThrow(() -> new ForbiddenException("Utilisateur non trouvé"));

        // Créer l'action de rejet
        ApproverAction action = ApproverAction.builder()
            .approval(approval)
            .approverId(userId)
            .approverName(membre.getNom() + " " + membre.getPrenom())
            .approverRole(jwt.getClaim("role"))
            .decision("REJECTED")
            .comment(request.getReason())
            .decidedAt(LocalDateTime.now())
            .build();

        approval.addApproverAction(action);
        approval.setStatus("REJECTED");
        approval.setRejectionReason(request.getReason());
        approval.setCompletedAt(LocalDateTime.now());

        approvalRepository.persist(approval);

        LOG.infof("Transaction %s rejetée: %s", approval.getTransactionId(), request.getReason());

        return toResponse(approval);
    }

    /**
     * Récupère l'historique des approbations
     */
    public List<TransactionApprovalResponse> getApprovalsHistory(
            UUID organizationId,
            LocalDateTime startDate,
            LocalDateTime endDate,
            String status) {
        LOG.infof("Récupération de l'historique des approbations pour l'organisation %s", organizationId);

        if (organizationId == null) {
            throw new IllegalArgumentException("L'ID de l'organisation est requis");
        }

        List<TransactionApproval> approvals = approvalRepository.findHistory(
            organizationId, startDate, endDate, status);

        return approvals.stream()
            .map(this::toResponse)
            .collect(Collectors.toList());
    }

    /**
     * Compte les approbations en attente
     */
    public long countPendingApprovals(UUID organizationId) {
        if (organizationId == null) {
            return approvalRepository.count("status", "PENDING");
        }
        return approvalRepository.countPendingByOrganisation(organizationId);
    }

    /**
     * Convertit une entité en DTO de réponse
     */
    private TransactionApprovalResponse toResponse(TransactionApproval approval) {
        List<ApproverActionResponse> approversResponse = approval.getApprovers().stream()
            .map(action -> ApproverActionResponse.builder()
                .id(action.getId())
                .approverId(action.getApproverId())
                .approverName(action.getApproverName())
                .approverRole(action.getApproverRole())
                .decision(action.getDecision())
                .comment(action.getComment())
                .decidedAt(action.getDecidedAt())
                .build())
            .collect(Collectors.toList());

        return TransactionApprovalResponse.builder()
            .id(approval.getId())
            .transactionId(approval.getTransactionId())
            .transactionType(approval.getTransactionType())
            .amount(approval.getAmount())
            .currency(approval.getCurrency())
            .requesterId(approval.getRequesterId())
            .requesterName(approval.getRequesterName())
            .organizationId(approval.getOrganisation() != null ? approval.getOrganisation().getId() : null)
            .requiredLevel(approval.getRequiredLevel())
            .status(approval.getStatus())
            .approvers(approversResponse)
            .rejectionReason(approval.getRejectionReason())
            .createdAt(approval.getCreatedAt())
            .expiresAt(approval.getExpiresAt())
            .completedAt(approval.getCompletedAt())
            .metadata(approval.getMetadata())
            // Champs calculés
            .approvalCount((int) approval.countApprovals())
            .requiredApprovals(approval.getRequiredApprovals())
            .hasAllApprovals(approval.hasAllApprovals())
            .isExpired(approval.isExpired())
            .isPending(approval.isPending())
            .isCompleted(approval.isCompleted())
            .build();
    }
}
