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.Max;
import jakarta.validation.constraints.Min;
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.temporal.ChronoUnit;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;

/**
 * DTO pour la gestion des cotisations dans l'API UnionFlow Représente une cotisation d'un membre à
 * son organisation
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-10
 */
@Getter
@Setter
public class CotisationDTO extends BaseDTO {

  private static final long serialVersionUID = 1L;

  /** Numéro de référence unique de la cotisation */
  @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;

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

  /** Nom de l'association (lecture seule) */
  private String nomAssociation;

  /** Type de cotisation */
  @NotNull(message = "Le type de cotisation est obligatoire")
  @Pattern(
      regexp = "^(MENSUELLE|TRIMESTRIELLE|SEMESTRIELLE|ANNUELLE|EXCEPTIONNELLE|ADHESION)$",
      message = "Type de cotisation invalide")
  private String typeCotisation;

  /** Libellé de la cotisation */
  @NotBlank(message = "Le libellé est obligatoire")
  @Size(max = 100, message = "Le libellé ne peut pas dépasser 100 caractères")
  private String libelle;

  /** Description détaillée */
  @Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
  private String description;

  /** Montant dû */
  @NotNull(message = "Le montant dû est obligatoire")
  @DecimalMin(value = "0.0", inclusive = false, message = "Le montant dû doit être positif")
  @Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
  private BigDecimal montantDu;

  /** 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 de la devise */
  @NotBlank(message = "Le code devise est obligatoire")
  @Size(min = 3, max = 3, message = "Le code devise doit faire exactement 3 caractères")
  private String codeDevise;

  /** Statut de la cotisation */
  @NotNull(message = "Le statut est obligatoire")
  @Pattern(
      regexp = "^(EN_ATTENTE|PAYEE|PARTIELLEMENT_PAYEE|EN_RETARD|ANNULEE|REMBOURSEE)$",
      message = "Statut invalide")
  private String statut;

  /** Date d'échéance */
  @JsonFormat(pattern = "yyyy-MM-dd")
  @NotNull(message = "La date d'échéance est obligatoire")
  private LocalDate dateEcheance;

  /** Date de paiement */
  @JsonFormat(pattern = "yyyy-MM-dd 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 du paiement (numéro de transaction, chèque, etc.) */
  @Size(max = 100, message = "La référence de paiement ne peut pas dépasser 100 caractères")
  private String referencePaiement;

  /** Période concernée (ex: "Janvier 2025", "Q1 2025") */
  @Size(max = 50, message = "La période ne peut pas dépasser 50 caractères")
  private String periode;

  /** Année de la cotisation */
  @Min(value = 2020, message = "L'année doit être supérieure à 2020")
  @Max(value = 2050, message = "L'année doit être inférieure à 2050")
  private Integer annee;

  /** Mois de la cotisation (1-12) */
  @Min(value = 1, message = "Le mois doit être entre 1 et 12")
  @Max(value = 12, message = "Le mois doit être entre 1 et 12")
  private Integer mois;

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

  /** Indique si la cotisation est récurrente */
  private Boolean recurrente = false;

  /** Nombre de rappels envoyés */
  @Min(value = 0, message = "Le nombre de rappels ne peut pas être négatif")
  private Integer nombreRappels = 0;

  /** Date du dernier rappel */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateDernierRappel;

  /** Identifiant de l'utilisateur qui a validé le paiement */
  private UUID validePar;

  /** Nom de l'utilisateur qui a validé le paiement */
  private String nomValidateur;

  // Constructeurs
  public CotisationDTO() {
    super();
    this.montantPaye = BigDecimal.ZERO;
    this.codeDevise = "XOF"; // Franc CFA par défaut
    this.statut = "EN_ATTENTE";
    this.recurrente = false;
    this.nombreRappels = 0;
    this.annee = LocalDate.now().getYear();
  }

  public CotisationDTO(
      UUID membreId, String typeCotisation, BigDecimal montantDu, LocalDate dateEcheance) {
    this();
    this.membreId = membreId;
    this.typeCotisation = typeCotisation;
    this.montantDu = montantDu;
    this.dateEcheance = dateEcheance;
    this.numeroReference = genererNumeroReference();
  }

  // Getters et Setters
  public String getNumeroReference() {
    return numeroReference;
  }

  public void setNumeroReference(String numeroReference) {
    this.numeroReference = numeroReference;
  }

  public UUID getMembreId() {
    return membreId;
  }

  public void setMembreId(UUID membreId) {
    this.membreId = membreId;
  }

  public String getNumeroMembre() {
    return numeroMembre;
  }

  public void setNumeroMembre(String numeroMembre) {
    this.numeroMembre = numeroMembre;
  }

  public String getNomMembre() {
    return nomMembre;
  }

  public void setNomMembre(String nomMembre) {
    this.nomMembre = nomMembre;
  }

  public UUID getAssociationId() {
    return associationId;
  }

  public void setAssociationId(UUID associationId) {
    this.associationId = associationId;
  }

  public String getNomAssociation() {
    return nomAssociation;
  }

  public void setNomAssociation(String nomAssociation) {
    this.nomAssociation = nomAssociation;
  }

  public String getTypeCotisation() {
    return typeCotisation;
  }

  public void setTypeCotisation(String typeCotisation) {
    this.typeCotisation = typeCotisation;
  }

  public String getLibelle() {
    return libelle;
  }

  public void setLibelle(String libelle) {
    this.libelle = libelle;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public BigDecimal getMontantDu() {
    return montantDu;
  }

  public void setMontantDu(BigDecimal montantDu) {
    this.montantDu = montantDu;
  }

  public BigDecimal getMontantPaye() {
    return montantPaye;
  }

  public void setMontantPaye(BigDecimal montantPaye) {
    this.montantPaye = montantPaye;
  }

  public String getCodeDevise() {
    return codeDevise;
  }

  public void setCodeDevise(String codeDevise) {
    this.codeDevise = codeDevise;
  }

  public String getStatut() {
    return statut;
  }

  public void setStatut(String statut) {
    this.statut = statut;
  }

  public LocalDate getDateEcheance() {
    return dateEcheance;
  }

  public void setDateEcheance(LocalDate dateEcheance) {
    this.dateEcheance = dateEcheance;
  }

  public LocalDateTime getDatePaiement() {
    return datePaiement;
  }

  public void setDatePaiement(LocalDateTime datePaiement) {
    this.datePaiement = datePaiement;
  }

  public String getMethodePaiement() {
    return methodePaiement;
  }

  public void setMethodePaiement(String methodePaiement) {
    this.methodePaiement = methodePaiement;
  }

  public String getReferencePaiement() {
    return referencePaiement;
  }

  public void setReferencePaiement(String referencePaiement) {
    this.referencePaiement = referencePaiement;
  }

  public String getPeriode() {
    return periode;
  }

  public void setPeriode(String periode) {
    this.periode = periode;
  }

  public Integer getAnnee() {
    return annee;
  }

  public void setAnnee(Integer annee) {
    this.annee = annee;
  }

  public Integer getMois() {
    return mois;
  }

  public void setMois(Integer mois) {
    this.mois = mois;
  }

  public String getObservations() {
    return observations;
  }

  public void setObservations(String observations) {
    this.observations = observations;
  }

  public Boolean getRecurrente() {
    return recurrente;
  }

  public void setRecurrente(Boolean recurrente) {
    this.recurrente = recurrente;
  }

  public Integer getNombreRappels() {
    return nombreRappels;
  }

  public void setNombreRappels(Integer nombreRappels) {
    this.nombreRappels = nombreRappels;
  }

  public LocalDateTime getDateDernierRappel() {
    return dateDernierRappel;
  }

  public void setDateDernierRappel(LocalDateTime dateDernierRappel) {
    this.dateDernierRappel = dateDernierRappel;
  }

  public UUID getValidePar() {
    return validePar;
  }

  public void setValidePar(UUID validePar) {
    this.validePar = validePar;
  }

  public String getNomValidateur() {
    return nomValidateur;
  }

  public void setNomValidateur(String nomValidateur) {
    this.nomValidateur = nomValidateur;
  }

  // Méthodes utilitaires

  /**
   * Génère un numéro de référence unique pour la cotisation
   *
   * @return Le numéro de référence généré
   */
  private String genererNumeroReference() {
    return "COT-"
        + LocalDate.now().getYear()
        + "-"
        + String.format("%06d", System.currentTimeMillis() % 1000000);
  }

  /**
   * Vérifie si la cotisation est payée intégralement
   *
   * @return true si le montant payé égale le montant dû
   */
  public boolean isPayeeIntegralement() {
    return montantPaye != null && montantDu != null && montantPaye.compareTo(montantDu) >= 0;
  }

  /**
   * Vérifie si la cotisation est en retard
   *
   * @return true si la date d'échéance est dépassée et non payée
   */
  public boolean isEnRetard() {
    return dateEcheance != null && LocalDate.now().isAfter(dateEcheance) && !isPayeeIntegralement();
  }

  /**
   * Calcule le montant restant à payer
   *
   * @return Le montant restant
   */
  public BigDecimal getMontantRestant() {
    if (montantDu == null) return BigDecimal.ZERO;
    if (montantPaye == null) return montantDu;

    BigDecimal restant = montantDu.subtract(montantPaye);
    return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
  }

  /**
   * Calcule le pourcentage de paiement
   *
   * @return Le pourcentage payé (0-100)
   */
  public int getPourcentagePaiement() {
    if (montantDu == null || montantDu.compareTo(BigDecimal.ZERO) == 0) {
      return 0;
    }
    if (montantPaye == null) {
      return 0;
    }

    return montantPaye
        .multiply(BigDecimal.valueOf(100))
        .divide(montantDu, 0, java.math.RoundingMode.HALF_UP)
        .intValue();
  }

  /**
   * Calcule le nombre de jours de retard
   *
   * @return Le nombre de jours de retard, 0 si pas en retard
   */
  public long getJoursRetard() {
    if (dateEcheance == null || !isEnRetard()) {
      return 0;
    }
    return ChronoUnit.DAYS.between(dateEcheance, LocalDate.now());
  }

  /**
   * Retourne le libellé du type de cotisation
   *
   * @return Le libellé du type
   */
  public String getTypeCotisationLibelle() {
    if (typeCotisation == null) return "Non défini";

    return switch (typeCotisation) {
      case "MENSUELLE" -> "Mensuelle";
      case "TRIMESTRIELLE" -> "Trimestrielle";
      case "SEMESTRIELLE" -> "Semestrielle";
      case "ANNUELLE" -> "Annuelle";
      case "EXCEPTIONNELLE" -> "Exceptionnelle";
      case "ADHESION" -> "Adhésion";
      default -> typeCotisation;
    };
  }

  /**
   * Retourne le libellé du statut
   *
   * @return Le libellé du statut
   */
  public String getStatutLibelle() {
    if (statut == null) return "Non défini";

    return switch (statut) {
      case "EN_ATTENTE" -> "En attente";
      case "PAYEE" -> "Payée";
      case "PARTIELLEMENT_PAYEE" -> "Partiellement payée";
      case "EN_RETARD" -> "En retard";
      case "ANNULEE" -> "Annulée";
      case "REMBOURSEE" -> "Remboursée";
      default -> statut;
    };
  }

  /**
   * Retourne le libellé de la méthode de paiement
   *
   * @return Le libellé de la méthode
   */
  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;
    };
  }

  /** Met à jour le statut en fonction du montant payé */
  public void mettreAJourStatut() {
    if (montantPaye == null || montantPaye.compareTo(BigDecimal.ZERO) == 0) {
      if (isEnRetard()) {
        this.statut = "EN_RETARD";
      } else {
        this.statut = "EN_ATTENTE";
      }
    } else if (isPayeeIntegralement()) {
      this.statut = "PAYEE";
      if (this.datePaiement == null) {
        this.datePaiement = LocalDateTime.now();
      }
    } else {
      this.statut = "PARTIELLEMENT_PAYEE";
    }
  }

  /**
   * Marque la cotisation comme payée
   *
   * @param montant Le montant payé
   * @param methode La méthode de paiement
   * @param reference La référence du paiement
   */
  public void marquerCommePaye(BigDecimal montant, String methode, String reference) {
    this.montantPaye = montant;
    this.methodePaiement = methode;
    this.referencePaiement = reference;
    this.datePaiement = LocalDateTime.now();
    mettreAJourStatut();
  }

  @Override
  public String toString() {
    return "CotisationDTO{"
        + "numeroReference='"
        + numeroReference
        + '\''
        + ", nomMembre='"
        + nomMembre
        + '\''
        + ", typeCotisation='"
        + typeCotisation
        + '\''
        + ", montantDu="
        + montantDu
        + ", montantPaye="
        + montantPaye
        + ", statut='"
        + statut
        + '\''
        + ", dateEcheance="
        + dateEcheance
        + ", periode='"
        + periode
        + '\''
        + "} "
        + super.toString();
  }
}
