package dev.lions.unionflow.server.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * Entité Approbation de Transaction
 *
 * Représente une approbation dans le workflow financier multi-niveaux.
 * Chaque transaction financière au-dessus d'un certain seuil nécessite une ou plusieurs approbations.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-13
 */
@Entity
@Table(name = "transaction_approvals", indexes = {
    @Index(name = "idx_approval_transaction", columnList = "transaction_id"),
    @Index(name = "idx_approval_status", columnList = "status"),
    @Index(name = "idx_approval_requester", columnList = "requester_id"),
    @Index(name = "idx_approval_organisation", columnList = "organisation_id"),
    @Index(name = "idx_approval_created", columnList = "created_at"),
    @Index(name = "idx_approval_level", columnList = "required_level")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class TransactionApproval extends BaseEntity {

    /** ID de la transaction financière à approuver */
    @NotNull
    @Column(name = "transaction_id", nullable = false)
    private UUID transactionId;

    /** Type de transaction (CONTRIBUTION, DEPOSIT, WITHDRAWAL, TRANSFER, SOLIDARITY, EVENT, OTHER) */
    @NotBlank
    @Pattern(regexp = "^(CONTRIBUTION|DEPOSIT|WITHDRAWAL|TRANSFER|SOLIDARITY|EVENT|OTHER)$")
    @Column(name = "transaction_type", nullable = false, length = 20)
    private String transactionType;

    /** Montant de la transaction */
    @NotNull
    @DecimalMin(value = "0.0", message = "Le montant doit être positif")
    @Digits(integer = 12, fraction = 2)
    @Column(name = "amount", nullable = false, precision = 14, scale = 2)
    private BigDecimal amount;

    /** Code devise ISO 3 lettres */
    @NotBlank
    @Pattern(regexp = "^[A-Z]{3}$")
    @Builder.Default
    @Column(name = "currency", nullable = false, length = 3)
    private String currency = "XOF";

    /** ID du membre demandeur */
    @NotNull
    @Column(name = "requester_id", nullable = false)
    private UUID requesterId;

    /** Nom complet du demandeur (cache pour performance) */
    @NotBlank
    @Column(name = "requester_name", nullable = false, length = 200)
    private String requesterName;

    /** Organisation concernée (peut être null pour transactions globales) */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "organisation_id")
    private Organisation organisation;

    /** Niveau d'approbation requis (NONE, LEVEL1, LEVEL2, LEVEL3) */
    @NotBlank
    @Pattern(regexp = "^(NONE|LEVEL1|LEVEL2|LEVEL3)$")
    @Column(name = "required_level", nullable = false, length = 10)
    private String requiredLevel;

    /** Statut de l'approbation (PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED) */
    @NotBlank
    @Pattern(regexp = "^(PENDING|APPROVED|VALIDATED|REJECTED|EXPIRED|CANCELLED)$")
    @Builder.Default
    @Column(name = "status", nullable = false, length = 20)
    private String status = "PENDING";

    /** Liste des actions d'approbateurs */
    @OneToMany(mappedBy = "approval", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @Builder.Default
    private List<ApproverAction> approvers = new ArrayList<>();

    /** Raison du rejet (si status = REJECTED) */
    @Size(max = 1000)
    @Column(name = "rejection_reason", length = 1000)
    private String rejectionReason;

    /** Date de création de la demande d'approbation */
    @NotNull
    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    /** Date d'expiration (timeout) */
    @Column(name = "expires_at")
    private LocalDateTime expiresAt;

    /** Date de completion (approbation finale ou rejet) */
    @Column(name = "completed_at")
    private LocalDateTime completedAt;

    /** Métadonnées additionnelles (JSON) */
    @Column(name = "metadata", columnDefinition = "TEXT")
    private String metadata;

    @PrePersist
    protected void onCreate() {
        super.onCreate();
        if (createdAt == null) {
            createdAt = LocalDateTime.now();
        }
        if (currency == null) {
            currency = "XOF";
        }
        if (status == null) {
            status = "PENDING";
        }
        // Expiration par défaut: 7 jours
        if (expiresAt == null) {
            expiresAt = createdAt.plusDays(7);
        }
    }

    /** Méthode métier pour ajouter une action d'approbateur */
    public void addApproverAction(ApproverAction action) {
        approvers.add(action);
        action.setApproval(this);
    }

    /** Méthode métier pour compter les approbations */
    public long countApprovals() {
        return approvers.stream()
            .filter(a -> "APPROVED".equals(a.getDecision()))
            .count();
    }

    /** Méthode métier pour obtenir le nombre d'approbations requises */
    public int getRequiredApprovals() {
        return switch (requiredLevel) {
            case "NONE" -> 0;
            case "LEVEL1" -> 1;
            case "LEVEL2" -> 2;
            case "LEVEL3" -> 3;
            default -> 0;
        };
    }

    /** Méthode métier pour vérifier si toutes les approbations sont reçues */
    public boolean hasAllApprovals() {
        return countApprovals() >= getRequiredApprovals();
    }

    /** Méthode métier pour vérifier si l'approbation est expirée */
    public boolean isExpired() {
        return expiresAt != null && LocalDateTime.now().isAfter(expiresAt);
    }

    /** Méthode métier pour vérifier si l'approbation est en attente */
    public boolean isPending() {
        return "PENDING".equals(status);
    }

    /** Méthode métier pour vérifier si l'approbation est complétée */
    public boolean isCompleted() {
        return "VALIDATED".equals(status) || "REJECTED".equals(status) || "CANCELLED".equals(status);
    }
}
