package dev.lions.unionflow.server.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * Entité Cotisation avec UUID Représente une cotisation d'un membre à son
 * organisation
 *
 * @author UnionFlow Team
 * @version 2.0
 * @since 2025-01-16
 */
@Entity
@Table(name = "cotisations", indexes = {
    @Index(name = "idx_cotisation_membre", columnList = "membre_id"),
    @Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
    @Index(name = "idx_cotisation_statut", columnList = "statut"),
    @Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
    @Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
    @Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Cotisation extends BaseEntity {

  @NotBlank
  @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
  private String numeroReference;

  @NotNull
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "membre_id", nullable = false)
  private Membre membre;

  /** Organisation pour laquelle la cotisation est due */
  @NotNull
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "organisation_id", nullable = false)
  private Organisation organisation;

  /** Intention de paiement Wave associée (null si cotisation en attente) */
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "intention_paiement_id")
  private IntentionPaiement intentionPaiement;

  @NotBlank
  @Column(name = "type_cotisation", nullable = false, length = 50)
  private String typeCotisation;

  @NotBlank
  @Column(name = "libelle", nullable = false, length = 100)
  private String libelle;

  @NotNull
  @DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
  @Digits(integer = 10, fraction = 2)
  @Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
  private BigDecimal montantDu;

  @Builder.Default
  @DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
  @Digits(integer = 10, fraction = 2)
  @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
  private BigDecimal montantPaye = BigDecimal.ZERO;

  @NotBlank
  @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
  @Column(name = "code_devise", nullable = false, length = 3)
  private String codeDevise;

  @NotBlank
  @Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
  @Column(name = "statut", nullable = false, length = 30)
  private String statut;

  @NotNull
  @Column(name = "date_echeance", nullable = false)
  private LocalDate dateEcheance;

  @Column(name = "date_paiement")
  private LocalDateTime datePaiement;

  @Size(max = 500)
  @Column(name = "description", length = 500)
  private String description;

  @Size(max = 20)
  @Column(name = "periode", length = 20)
  private String periode;

  @NotNull
  @Min(value = 2020, message = "L'année doit être supérieure à 2020")
  @Max(value = 2100, message = "L'année doit être inférieure à 2100")
  @Column(name = "annee", nullable = false)
  private Integer annee;

  @Min(value = 1, message = "Le mois doit être entre 1 et 12")
  @Max(value = 12, message = "Le mois doit être entre 1 et 12")
  @Column(name = "mois")
  private Integer mois;

  @Size(max = 1000)
  @Column(name = "observations", length = 1000)
  private String observations;

  @Builder.Default
  @Column(name = "recurrente", nullable = false)
  private Boolean recurrente = false;

  @Builder.Default
  @Min(value = 0, message = "Le nombre de rappels doit être positif")
  @Column(name = "nombre_rappels", nullable = false)
  private Integer nombreRappels = 0;

  @Column(name = "date_dernier_rappel")
  private LocalDateTime dateDernierRappel;

  @Column(name = "valide_par_id")
  private UUID valideParId;

  @Size(max = 100)
  @Column(name = "nom_validateur", length = 100)
  private String nomValidateur;

  @Column(name = "date_validation")
  private LocalDateTime dateValidation;

  /** Méthode métier pour calculer le montant restant à payer */
  public BigDecimal getMontantRestant() {
    if (montantDu == null || montantPaye == null) {
      return BigDecimal.ZERO;
    }
    return montantDu.subtract(montantPaye);
  }

  /** Méthode métier pour vérifier si la cotisation est entièrement payée */
  public boolean isEntierementPayee() {
    return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
  }

  /** Méthode métier pour vérifier si la cotisation est en retard */
  public boolean isEnRetard() {
    return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
  }

  private static final AtomicLong REFERENCE_COUNTER =
      new AtomicLong(System.currentTimeMillis() % 100000000L);

  /** Méthode métier pour générer un numéro de référence unique */
  public static String genererNumeroReference() {
    return "COT-"
        + LocalDate.now().getYear()
        + "-"
        + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
  }

  /** Callback JPA avant la persistance */
  @PrePersist
  protected void onCreate() {
    super.onCreate(); // Appelle le onCreate de BaseEntity
    if (numeroReference == null || numeroReference.isEmpty()) {
      numeroReference = genererNumeroReference();
    }
    if (codeDevise == null) {
      codeDevise = "XOF";
    }
    if (statut == null) {
      statut = "EN_ATTENTE";
    }
    if (montantPaye == null) {
      montantPaye = BigDecimal.ZERO;
    }
    if (nombreRappels == null) {
      nombreRappels = 0;
    }
    if (recurrente == null) {
      recurrente = false;
    }
  }
}
