package dev.lions.unionflow.server.api.dto.solidarite.aide;

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.Future;
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.util.UUID;

/**
 * DTO pour la gestion des demandes d'aide et de solidarité Représente les demandes d'assistance
 * mutuelle entre membres
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-10
 */
public class AideDTO extends BaseDTO {

  private static final long serialVersionUID = 1L;

  /** Numéro de référence unique de la demande */
  @NotBlank(message = "Le numéro de référence est obligatoire")
  @Pattern(
      regexp = "^AIDE-\\d{4}-[A-Z0-9]{6}$",
      message = "Format de référence invalide (AIDE-YYYY-XXXXXX)")
  private String numeroReference;

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

  /** Nom complet du membre demandeur */
  private String nomDemandeur;

  /** Numéro de membre du demandeur */
  private String numeroMembreDemandeur;

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

  /** Nom de l'association */
  private String nomAssociation;

  /**
   * Type d'aide demandée FINANCIERE, MATERIELLE, MEDICALE, JURIDIQUE, LOGEMENT, EDUCATION, AUTRE
   */
  @NotBlank(message = "Le type d'aide est obligatoire")
  @Pattern(
      regexp = "^(FINANCIERE|MATERIELLE|MEDICALE|JURIDIQUE|LOGEMENT|EDUCATION|AUTRE)$",
      message =
          "Le type d'aide doit être FINANCIERE, MATERIELLE, MEDICALE, JURIDIQUE, LOGEMENT,"
              + " EDUCATION ou AUTRE")
  private String typeAide;

  /** Titre de la demande d'aide */
  @NotBlank(message = "Le titre est obligatoire")
  @Size(min = 5, max = 200, message = "Le titre doit contenir entre 5 et 200 caractères")
  private String titre;

  /** Description détaillée de la demande */
  @NotBlank(message = "La description est obligatoire")
  @Size(min = 20, max = 2000, message = "La description doit contenir entre 20 et 2000 caractères")
  private String description;

  /** Montant demandé (pour les aides financières) */
  @DecimalMin(value = "0.0", inclusive = false, message = "Le montant demandé doit être positif")
  @Digits(
      integer = 10,
      fraction = 2,
      message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
  private BigDecimal montantDemande;

  /** Devise du montant */
  @Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
  private String devise = "XOF";

  /**
   * Statut de la demande EN_ATTENTE, EN_COURS_EVALUATION, APPROUVEE, REJETEE, EN_COURS_AIDE,
   * TERMINEE, ANNULEE
   */
  @NotBlank(message = "Le statut est obligatoire")
  @Pattern(
      regexp =
          "^(EN_ATTENTE|EN_COURS_EVALUATION|APPROUVEE|REJETEE|EN_COURS_AIDE|TERMINEE|ANNULEE)$",
      message = "Statut invalide")
  private String statut;

  /** Priorité de la demande BASSE, NORMALE, HAUTE, URGENTE */
  @Pattern(
      regexp = "^(BASSE|NORMALE|HAUTE|URGENTE)$",
      message = "La priorité doit être BASSE, NORMALE, HAUTE ou URGENTE")
  private String priorite;

  /** Date limite pour l'aide */
  @Future(message = "La date limite doit être dans le futur")
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateLimite;

  /** Justificatifs fournis */
  private Boolean justificatifsFournis;

  /** Liste des documents joints (noms de fichiers) */
  @Size(max = 1000, message = "La liste des documents ne peut pas dépasser 1000 caractères")
  private String documentsJoints;

  /** Identifiant du membre évaluateur */
  private UUID membreEvaluateurId;

  /** Nom de l'évaluateur */
  private String nomEvaluateur;

  /** Date d'évaluation */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateEvaluation;

  /** Commentaires de l'évaluateur */
  @Size(max = 1000, message = "Les commentaires ne peuvent pas dépasser 1000 caractères")
  private String commentairesEvaluateur;

  /** Montant approuvé (peut être différent du montant demandé) */
  @DecimalMin(value = "0.0", message = "Le montant approuvé doit être positif")
  @Digits(
      integer = 10,
      fraction = 2,
      message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
  private BigDecimal montantApprouve;

  /** Date d'approbation */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateApprobation;

  /** Identifiant du membre qui fournit l'aide */
  private UUID membreAidantId;

  /** Nom du membre aidant */
  private String nomAidant;

  /** Date de début de l'aide */
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateDebutAide;

  /** Date de fin de l'aide */
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateFinAide;

  /** Montant effectivement versé */
  @DecimalMin(value = "0.0", message = "Le montant versé doit être positif")
  @Digits(
      integer = 10,
      fraction = 2,
      message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
  private BigDecimal montantVerse;

  /** Mode de versement */
  @Pattern(
      regexp = "^(WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|VIREMENT|CHEQUE|ESPECES|NATURE)$",
      message = "Mode de versement invalide")
  private String modeVersement;

  /** Numéro de transaction (pour les paiements mobiles) */
  @Size(max = 50, message = "Le numéro de transaction ne peut pas dépasser 50 caractères")
  private String numeroTransaction;

  /** Date de versement */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateVersement;

  /** Commentaires du bénéficiaire */
  @Size(max = 1000, message = "Les commentaires ne peuvent pas dépasser 1000 caractères")
  private String commentairesBeneficiaire;

  /** Note de satisfaction (1-5) */
  private Integer noteSatisfaction;

  /** Aide publique (visible par tous les membres) */
  private Boolean aidePublique;

  /** Aide anonyme (demandeur anonyme) */
  private Boolean aideAnonyme;

  /** Nombre de vues de la demande */
  private Integer nombreVues;

  /** Raison du rejet (si applicable) */
  @Size(max = 500, message = "La raison du rejet ne peut pas dépasser 500 caractères")
  private String raisonRejet;

  /** Date de rejet */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateRejet;

  /** Identifiant de celui qui a rejeté */
  private UUID rejeteParId;

  /** Nom de celui qui a rejeté */
  private String rejetePar;

  // Constructeurs
  public AideDTO() {
    super();
    this.statut = "EN_ATTENTE";
    this.priorite = "NORMALE";
    this.devise = "XOF";
    this.justificatifsFournis = false;
    this.aidePublique = true;
    this.aideAnonyme = false;
    this.nombreVues = 0;
    this.numeroReference = genererNumeroReference();
  }

  public AideDTO(UUID membreDemandeurId, UUID associationId, String typeAide, String titre) {
    this();
    this.membreDemandeurId = membreDemandeurId;
    this.associationId = associationId;
    this.typeAide = typeAide;
    this.titre = titre;
  }

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

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

  public UUID getMembreDemandeurId() {
    return membreDemandeurId;
  }

  public void setMembreDemandeurId(UUID membreDemandeurId) {
    this.membreDemandeurId = membreDemandeurId;
  }

  public String getNomDemandeur() {
    return nomDemandeur;
  }

  public void setNomDemandeur(String nomDemandeur) {
    this.nomDemandeur = nomDemandeur;
  }

  public String getNumeroMembreDemandeur() {
    return numeroMembreDemandeur;
  }

  public void setNumeroMembreDemandeur(String numeroMembreDemandeur) {
    this.numeroMembreDemandeur = numeroMembreDemandeur;
  }

  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 getTypeAide() {
    return typeAide;
  }

  public void setTypeAide(String typeAide) {
    this.typeAide = typeAide;
  }

  public String getTitre() {
    return titre;
  }

  public void setTitre(String titre) {
    this.titre = titre;
  }

  public String getDescription() {
    return description;
  }

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

  public BigDecimal getMontantDemande() {
    return montantDemande;
  }

  public void setMontantDemande(BigDecimal montantDemande) {
    this.montantDemande = montantDemande;
  }

  public String getDevise() {
    return devise;
  }

  public void setDevise(String devise) {
    this.devise = devise;
  }

  public String getStatut() {
    return statut;
  }

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

  public String getPriorite() {
    return priorite;
  }

  public void setPriorite(String priorite) {
    this.priorite = priorite;
  }

  public LocalDate getDateLimite() {
    return dateLimite;
  }

  public void setDateLimite(LocalDate dateLimite) {
    this.dateLimite = dateLimite;
  }

  public Boolean getJustificatifsFournis() {
    return justificatifsFournis;
  }

  public void setJustificatifsFournis(Boolean justificatifsFournis) {
    this.justificatifsFournis = justificatifsFournis;
  }

  public String getDocumentsJoints() {
    return documentsJoints;
  }

  public void setDocumentsJoints(String documentsJoints) {
    this.documentsJoints = documentsJoints;
  }

  // Getters et setters restants (suite)
  public UUID getMembreEvaluateurId() {
    return membreEvaluateurId;
  }

  public void setMembreEvaluateurId(UUID membreEvaluateurId) {
    this.membreEvaluateurId = membreEvaluateurId;
  }

  public String getNomEvaluateur() {
    return nomEvaluateur;
  }

  public void setNomEvaluateur(String nomEvaluateur) {
    this.nomEvaluateur = nomEvaluateur;
  }

  public LocalDateTime getDateEvaluation() {
    return dateEvaluation;
  }

  public void setDateEvaluation(LocalDateTime dateEvaluation) {
    this.dateEvaluation = dateEvaluation;
  }

  public String getCommentairesEvaluateur() {
    return commentairesEvaluateur;
  }

  public void setCommentairesEvaluateur(String commentairesEvaluateur) {
    this.commentairesEvaluateur = commentairesEvaluateur;
  }

  public BigDecimal getMontantApprouve() {
    return montantApprouve;
  }

  public void setMontantApprouve(BigDecimal montantApprouve) {
    this.montantApprouve = montantApprouve;
  }

  public LocalDateTime getDateApprobation() {
    return dateApprobation;
  }

  public void setDateApprobation(LocalDateTime dateApprobation) {
    this.dateApprobation = dateApprobation;
  }

  public UUID getMembreAidantId() {
    return membreAidantId;
  }

  public void setMembreAidantId(UUID membreAidantId) {
    this.membreAidantId = membreAidantId;
  }

  public String getNomAidant() {
    return nomAidant;
  }

  public void setNomAidant(String nomAidant) {
    this.nomAidant = nomAidant;
  }

  public LocalDate getDateDebutAide() {
    return dateDebutAide;
  }

  public void setDateDebutAide(LocalDate dateDebutAide) {
    this.dateDebutAide = dateDebutAide;
  }

  public LocalDate getDateFinAide() {
    return dateFinAide;
  }

  public void setDateFinAide(LocalDate dateFinAide) {
    this.dateFinAide = dateFinAide;
  }

  public BigDecimal getMontantVerse() {
    return montantVerse;
  }

  public void setMontantVerse(BigDecimal montantVerse) {
    this.montantVerse = montantVerse;
  }

  public String getModeVersement() {
    return modeVersement;
  }

  public void setModeVersement(String modeVersement) {
    this.modeVersement = modeVersement;
  }

  public String getNumeroTransaction() {
    return numeroTransaction;
  }

  public void setNumeroTransaction(String numeroTransaction) {
    this.numeroTransaction = numeroTransaction;
  }

  public LocalDateTime getDateVersement() {
    return dateVersement;
  }

  public void setDateVersement(LocalDateTime dateVersement) {
    this.dateVersement = dateVersement;
  }

  public String getCommentairesBeneficiaire() {
    return commentairesBeneficiaire;
  }

  public void setCommentairesBeneficiaire(String commentairesBeneficiaire) {
    this.commentairesBeneficiaire = commentairesBeneficiaire;
  }

  public Integer getNoteSatisfaction() {
    return noteSatisfaction;
  }

  public void setNoteSatisfaction(Integer noteSatisfaction) {
    this.noteSatisfaction = noteSatisfaction;
  }

  public Boolean getAidePublique() {
    return aidePublique;
  }

  public void setAidePublique(Boolean aidePublique) {
    this.aidePublique = aidePublique;
  }

  public Boolean getAideAnonyme() {
    return aideAnonyme;
  }

  public void setAideAnonyme(Boolean aideAnonyme) {
    this.aideAnonyme = aideAnonyme;
  }

  public Integer getNombreVues() {
    return nombreVues;
  }

  public void setNombreVues(Integer nombreVues) {
    this.nombreVues = nombreVues;
  }

  public String getRaisonRejet() {
    return raisonRejet;
  }

  public void setRaisonRejet(String raisonRejet) {
    this.raisonRejet = raisonRejet;
  }

  public LocalDateTime getDateRejet() {
    return dateRejet;
  }

  public void setDateRejet(LocalDateTime dateRejet) {
    this.dateRejet = dateRejet;
  }

  public UUID getRejeteParId() {
    return rejeteParId;
  }

  public void setRejeteParId(UUID rejeteParId) {
    this.rejeteParId = rejeteParId;
  }

  public String getRejetePar() {
    return rejetePar;
  }

  public void setRejetePar(String rejetePar) {
    this.rejetePar = rejetePar;
  }

  // Méthodes utilitaires

  /**
   * Vérifie si la demande est en attente
   *
   * @return true si la demande est en attente
   */
  public boolean isEnAttente() {
    return "EN_ATTENTE".equals(statut);
  }

  /**
   * Vérifie si la demande est en cours d'évaluation
   *
   * @return true si la demande est en cours d'évaluation
   */
  public boolean isEnCoursEvaluation() {
    return "EN_COURS_EVALUATION".equals(statut);
  }

  /**
   * Vérifie si la demande est approuvée
   *
   * @return true si la demande est approuvée
   */
  public boolean isApprouvee() {
    return "APPROUVEE".equals(statut);
  }

  /**
   * Vérifie si la demande est rejetée
   *
   * @return true si la demande est rejetée
   */
  public boolean isRejetee() {
    return "REJETEE".equals(statut);
  }

  /**
   * Vérifie si l'aide est en cours
   *
   * @return true si l'aide est en cours
   */
  public boolean isEnCoursAide() {
    return "EN_COURS_AIDE".equals(statut);
  }

  /**
   * Vérifie si l'aide est terminée
   *
   * @return true si l'aide est terminée
   */
  public boolean isTerminee() {
    return "TERMINEE".equals(statut);
  }

  /**
   * Vérifie si la demande est annulée
   *
   * @return true si la demande est annulée
   */
  public boolean isAnnulee() {
    return "ANNULEE".equals(statut);
  }

  /**
   * Vérifie si la demande est urgente
   *
   * @return true si la priorité est urgente
   */
  public boolean isUrgente() {
    return "URGENTE".equals(priorite);
  }

  /**
   * Vérifie si la date limite est dépassée
   *
   * @return true si la date limite est dépassée
   */
  public boolean isDateLimiteDepassee() {
    return dateLimite != null && LocalDate.now().isAfter(dateLimite);
  }

  /**
   * Calcule le nombre de jours restants avant la date limite
   *
   * @return Le nombre de jours restants, ou 0 si dépassé
   */
  public long getJoursRestants() {
    if (dateLimite == null) return 0;
    LocalDate aujourd = LocalDate.now();
    return aujourd.isBefore(dateLimite) ? aujourd.until(dateLimite).getDays() : 0;
  }

  /**
   * Vérifie si l'aide concerne un montant financier
   *
   * @return true si c'est une aide financière
   */
  public boolean isAideFinanciere() {
    return "FINANCIERE".equals(typeAide) && montantDemande != null;
  }

  /**
   * Calcule l'écart entre le montant demandé et approuvé
   *
   * @return La différence (positif = réduction, négatif = augmentation)
   */
  public BigDecimal getEcartMontant() {
    if (montantDemande == null || montantApprouve == null) {
      return BigDecimal.ZERO;
    }
    return montantDemande.subtract(montantApprouve);
  }

  /**
   * Calcule le pourcentage d'approbation du montant
   *
   * @return Le pourcentage du montant approuvé par rapport au demandé
   */
  public int getPourcentageApprobation() {
    if (montantDemande == null
        || montantApprouve == null
        || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
      return 0;
    }
    return montantApprouve
        .multiply(BigDecimal.valueOf(100))
        .divide(montantDemande, 0, java.math.RoundingMode.HALF_UP)
        .intValue();
  }

  /**
   * Retourne le libellé du type d'aide
   *
   * @return Le libellé du type d'aide
   */
  public String getTypeAideLibelle() {
    if (typeAide == null) return "Non défini";

    return switch (typeAide) {
      case "FINANCIERE" -> "Aide Financière";
      case "MATERIELLE" -> "Aide Matérielle";
      case "MEDICALE" -> "Aide Médicale";
      case "JURIDIQUE" -> "Aide Juridique";
      case "LOGEMENT" -> "Aide au Logement";
      case "EDUCATION" -> "Aide à l'Éducation";
      case "AUTRE" -> "Autre";
      default -> typeAide;
    };
  }

  /**
   * 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 "EN_COURS_EVALUATION" -> "En Cours d'Évaluation";
      case "APPROUVEE" -> "Approuvée";
      case "REJETEE" -> "Rejetée";
      case "EN_COURS_AIDE" -> "En Cours d'Aide";
      case "TERMINEE" -> "Terminée";
      case "ANNULEE" -> "Annulée";
      default -> statut;
    };
  }

  /**
   * Retourne le libellé de la priorité
   *
   * @return Le libellé de la priorité
   */
  public String getPrioriteLibelle() {
    if (priorite == null) return "Normale";

    return switch (priorite) {
      case "BASSE" -> "Basse";
      case "NORMALE" -> "Normale";
      case "HAUTE" -> "Haute";
      case "URGENTE" -> "Urgente";
      default -> priorite;
    };
  }

  /**
   * Approuve la demande d'aide
   *
   * @param evaluateurId ID de l'évaluateur
   * @param nomEvaluateur Nom de l'évaluateur
   * @param montantApprouve Montant approuvé
   * @param commentaires Commentaires de l'évaluateur
   */
  public void approuver(
      UUID evaluateurId, String nomEvaluateur, BigDecimal montantApprouve, String commentaires) {
    this.statut = "APPROUVEE";
    this.membreEvaluateurId = evaluateurId;
    this.nomEvaluateur = nomEvaluateur;
    this.montantApprouve = montantApprouve;
    this.commentairesEvaluateur = commentaires;
    this.dateEvaluation = LocalDateTime.now();
    this.dateApprobation = LocalDateTime.now();
    marquerCommeModifie(nomEvaluateur);
  }

  /**
   * Rejette la demande d'aide
   *
   * @param evaluateurId ID de l'évaluateur
   * @param nomEvaluateur Nom de l'évaluateur
   * @param raison Raison du rejet
   */
  public void rejeter(UUID evaluateurId, String nomEvaluateur, String raison) {
    this.statut = "REJETEE";
    this.rejeteParId = evaluateurId;
    this.rejetePar = nomEvaluateur;
    this.raisonRejet = raison;
    this.dateRejet = LocalDateTime.now();
    this.dateEvaluation = LocalDateTime.now();
    marquerCommeModifie(nomEvaluateur);
  }

  /**
   * Démarre l'aide
   *
   * @param aidantId ID du membre aidant
   * @param nomAidant Nom du membre aidant
   */
  public void demarrerAide(UUID aidantId, String nomAidant) {
    this.statut = "EN_COURS_AIDE";
    this.membreAidantId = aidantId;
    this.nomAidant = nomAidant;
    this.dateDebutAide = LocalDate.now();
    marquerCommeModifie(nomAidant);
  }

  /**
   * Termine l'aide avec versement
   *
   * @param montantVerse Montant effectivement versé
   * @param modeVersement Mode de versement
   * @param numeroTransaction Numéro de transaction
   */
  public void terminerAvecVersement(
      BigDecimal montantVerse, String modeVersement, String numeroTransaction) {
    this.statut = "TERMINEE";
    this.montantVerse = montantVerse;
    this.modeVersement = modeVersement;
    this.numeroTransaction = numeroTransaction;
    this.dateVersement = LocalDateTime.now();
    this.dateFinAide = LocalDate.now();
    marquerCommeModifie("SYSTEM");
  }

  /** Incrémente le nombre de vues */
  public void incrementerVues() {
    if (nombreVues == null) {
      nombreVues = 1;
    } else {
      nombreVues++;
    }
  }

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

  @Override
  public String toString() {
    return "AideDTO{"
        + "numeroReference='"
        + numeroReference
        + '\''
        + ", typeAide='"
        + typeAide
        + '\''
        + ", titre='"
        + titre
        + '\''
        + ", statut='"
        + statut
        + '\''
        + ", priorite='"
        + priorite
        + '\''
        + ", montantDemande="
        + montantDemande
        + ", montantApprouve="
        + montantApprouve
        + ", devise='"
        + devise
        + '\''
        + "} "
        + super.toString();
  }
}
