package dev.lions.unionflow.server.api.dto.finance;

import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;

/**
 * DTO pour la gestion des adhésions dans l'API UnionFlow
 * Représente une demande d'adhésion d'un membre à une organisation
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-17
 */
@Getter
@Setter
public class AdhesionDTO extends BaseDTO {

  private static final long serialVersionUID = 1L;

  /** Numéro de référence unique de l'adhésion */
  @NotBlank(message = "Le numéro de référence est obligatoire")
  @Size(max = 50, message = "Le numéro de référence ne peut pas dépasser 50 caractères")
  private String numeroReference;

  /** Identifiant du membre */
  @NotNull(message = "L'identifiant du membre est obligatoire")
  private UUID membreId;

  /** Numéro du membre (lecture seule) */
  private String numeroMembre;

  /** Nom complet du membre (lecture seule) */
  private String nomMembre;

  /** Email du membre (lecture seule) */
  private String emailMembre;

  /** Identifiant de l'organisation */
  @NotNull(message = "L'identifiant de l'organisation est obligatoire")
  private UUID organisationId;

  /** Nom de l'organisation (lecture seule) */
  private String nomOrganisation;

  /** Date de la demande d'adhésion */
  @NotNull(message = "La date de demande est obligatoire")
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateDemande;

  /** Frais d'adhésion */
  @NotNull(message = "Les frais d'adhésion sont obligatoires")
  @DecimalMin(value = "0.0", inclusive = false, message = "Les frais d'adhésion doivent être positifs")
  @Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
  private BigDecimal fraisAdhesion;

  /** Montant payé */
  @DecimalMin(value = "0.0", message = "Le montant payé ne peut pas être négatif")
  @Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
  private BigDecimal montantPaye;

  /** Code devise (ISO 4217) */
  @NotBlank(message = "Le code devise est obligatoire")
  @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
  private String codeDevise;

  /** Statut de l'adhésion */
  @NotBlank(message = "Le statut est obligatoire")
  @Pattern(
      regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$",
      message = "Statut invalide")
  private String statut;

  /** Date d'approbation */
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateApprobation;

  /** Date de paiement */
  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
  private LocalDateTime datePaiement;

  /** Méthode de paiement */
  @Pattern(
      regexp = "^(ESPECES|VIREMENT|CHEQUE|WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|CARTE_BANCAIRE)$",
      message = "Méthode de paiement invalide")
  private String methodePaiement;

  /** Référence de paiement */
  @Size(max = 100, message = "La référence de paiement ne peut pas dépasser 100 caractères")
  private String referencePaiement;

  /** Motif de rejet */
  @Size(max = 1000, message = "Le motif de rejet ne peut pas dépasser 1000 caractères")
  private String motifRejet;

  /** Observations */
  @Size(max = 1000, message = "Les observations ne peuvent pas dépasser 1000 caractères")
  private String observations;

  /** Utilisateur ayant approuvé l'adhésion */
  @Size(max = 255, message = "Le nom de l'approbateur ne peut pas dépasser 255 caractères")
  private String approuvePar;

  /** Date de validation */
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateValidation;

  // Méthodes utilitaires pour l'affichage

  /** Vérifie si l'adhésion est payée intégralement */
  public boolean isPayeeIntegralement() {
    return montantPaye != null
        && fraisAdhesion != null
        && montantPaye.compareTo(fraisAdhesion) >= 0;
  }

  /** Vérifie si l'adhésion est en attente de paiement */
  public boolean isEnAttentePaiement() {
    return "APPROUVEE".equals(statut) && !isPayeeIntegralement();
  }

  /** Calcule le montant restant à payer */
  public BigDecimal getMontantRestant() {
    if (fraisAdhesion == null) return BigDecimal.ZERO;
    if (montantPaye == null) return fraisAdhesion;
    BigDecimal restant = fraisAdhesion.subtract(montantPaye);
    return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
  }

  /** Calcule le pourcentage de paiement */
  public int getPourcentagePaiement() {
    if (fraisAdhesion == null || fraisAdhesion.compareTo(BigDecimal.ZERO) == 0) return 0;
    if (montantPaye == null) return 0;
    return montantPaye
        .multiply(BigDecimal.valueOf(100))
        .divide(fraisAdhesion, 0, java.math.RoundingMode.HALF_UP)
        .intValue();
  }

  /** Calcule le nombre de jours depuis la demande */
  public long getJoursDepuisDemande() {
    if (dateDemande == null) return 0;
    return ChronoUnit.DAYS.between(dateDemande, LocalDate.now());
  }

  /** Retourne le libellé du statut */
  public String getStatutLibelle() {
    if (statut == null) return "Non défini";
    return switch (statut) {
      case "EN_ATTENTE" -> "En attente";
      case "APPROUVEE" -> "Approuvée";
      case "REJETEE" -> "Rejetée";
      case "ANNULEE" -> "Annulée";
      case "EN_PAIEMENT" -> "En paiement";
      case "PAYEE" -> "Payée";
      default -> statut;
    };
  }

  /** Retourne la sévérité du statut pour PrimeFaces */
  public String getStatutSeverity() {
    if (statut == null) return "secondary";
    return switch (statut) {
      case "APPROUVEE", "PAYEE" -> "success";
      case "EN_ATTENTE", "EN_PAIEMENT" -> "warning";
      case "REJETEE" -> "danger";
      case "ANNULEE" -> "secondary";
      default -> "secondary";
    };
  }

  /** Retourne l'icône du statut pour PrimeFaces */
  public String getStatutIcon() {
    if (statut == null) return "pi-circle";
    return switch (statut) {
      case "APPROUVEE", "PAYEE" -> "pi-check";
      case "EN_ATTENTE" -> "pi-clock";
      case "EN_PAIEMENT" -> "pi-credit-card";
      case "REJETEE" -> "pi-times";
      case "ANNULEE" -> "pi-ban";
      default -> "pi-circle";
    };
  }

  /** Retourne le libellé de la méthode de paiement */
  public String getMethodePaiementLibelle() {
    if (methodePaiement == null) return "Non défini";
    return switch (methodePaiement) {
      case "ESPECES" -> "Espèces";
      case "VIREMENT" -> "Virement bancaire";
      case "CHEQUE" -> "Chèque";
      case "WAVE_MONEY" -> "Wave Money";
      case "ORANGE_MONEY" -> "Orange Money";
      case "FREE_MONEY" -> "Free Money";
      case "CARTE_BANCAIRE" -> "Carte bancaire";
      default -> methodePaiement;
    };
  }

  /** Formate la date de demande */
  public String getDateDemandeFormatee() {
    if (dateDemande == null) return "";
    return dateDemande.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
  }

  /** Formate la date d'approbation */
  public String getDateApprobationFormatee() {
    if (dateApprobation == null) return "";
    return dateApprobation.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
  }

  /** Formate la date de paiement */
  public String getDatePaiementFormatee() {
    if (datePaiement == null) return "";
    return datePaiement.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"));
  }

  /** Formate les frais d'adhésion */
  public String getFraisAdhesionFormatte() {
    if (fraisAdhesion == null) return "0 FCFA";
    return String.format("%,.0f FCFA", fraisAdhesion.doubleValue());
  }

  /** Formate le montant payé */
  public String getMontantPayeFormatte() {
    if (montantPaye == null) return "0 FCFA";
    return String.format("%,.0f FCFA", montantPaye.doubleValue());
  }

  /** Formate le montant restant */
  public String getMontantRestantFormatte() {
    return String.format("%,.0f FCFA", getMontantRestant().doubleValue());
  }
}



