package dev.lions.unionflow.server.service.compliance;

import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.ProcesVerbal;
import dev.lions.unionflow.server.entity.ReferentielComptable;
import dev.lions.unionflow.server.repository.ProcesVerbalRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.Year;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Tableau de bord de conformité par organisation.
 *
 * <p>Agrège les indicateurs critiques pour l'audit AIRMS / BCEAO / ARTCI :
 *
 * <ul>
 *   <li><strong>CMU dirigeants</strong> — au moins président, trésorier, secrétaire enrôlés
 *   <li><strong>AG annuelle</strong> — tenue avant 30 juin chaque année
 *   <li><strong>Rapports AIRMS</strong> — triple rapport déposé année N-1
 *   <li><strong>Compliance Officer</strong> — désigné (Instruction BCEAO 001-03-2025)
 *   <li><strong>FOMUS-CI</strong> — cotisation à jour (mutuelles sociales CI)
 *   <li><strong>Commissariat aux comptes</strong> — flag obligatoire si seuils atteints
 *   <li><strong>KYC à jour</strong> — % membres avec dossier KYC validé non expiré
 *   <li><strong>Formation LBC/FT</strong> — % membres formés dans les 12 derniers mois
 *   <li><strong>Bénéficiaires effectifs (UBO)</strong> — couverture des dossiers KYC entreprise
 * </ul>
 *
 * @since 2026-04-25 (P1-NEW-7)
 */
@ApplicationScoped
public class ComplianceDashboardService {

  @Inject EntityManager em;
  @Inject ProcesVerbalRepository pvRepository;

  /** Construit le snapshot de conformité pour une organisation. */
  public ComplianceSnapshot snapshot(UUID organisationId) {
    Organisation org = em.find(Organisation.class, organisationId);
    if (org == null) {
      throw new IllegalArgumentException("Organisation introuvable : " + organisationId);
    }

    int anneeActuelle = Year.now().getValue();
    int anneeRapport = anneeActuelle - 1; // rapports AIRMS portent sur N-1

    return new ComplianceSnapshot(
        organisationId,
        org.getNom(),
        org.getReferentielComptable(),
        // Compliance officer
        org.getComplianceOfficerId() != null,
        // AG annuelle tenue avant 30 juin année courante
        verifierAgAnnuelle(organisationId, anneeActuelle),
        // Rapport AIRMS déposé
        verifierRapportAirms(organisationId, anneeRapport),
        // CMU dirigeants
        compterDirigeantsAvecCmu(organisationId),
        // KYC
        calculerTauxKycAJour(organisationId),
        // Formation LBC/FT
        calculerTauxFormationLbcFt(organisationId),
        // CAC
        verifierBesoinCommissaireAuxComptes(organisationId, org),
        // FOMUS-CI
        verifierFomusCiAJour(organisationId),
        // UBO
        calculerCouvertureUbo(organisationId),
        // Score global
        calculerScoreGlobal(organisationId, org)
    );
  }

  // ============================================================
  // Vérifications individuelles
  // ============================================================

  private ConformiteIndicateur verifierAgAnnuelle(UUID orgId, int annee) {
    LocalDate deadline = LocalDate.of(annee, 6, 30);
    List<ProcesVerbal> ags = pvRepository.findByOrganisationAndType(orgId, "AG_ORDINAIRE");
    boolean tenue = ags.stream().anyMatch(pv ->
        pv.getDateSeance() != null
            && pv.getDateSeance().getYear() == annee
            && !pv.getDateSeance().toLocalDate().isAfter(deadline)
            && "ARCHIVE".equals(pv.getStatut()));
    return new ConformiteIndicateur(
        tenue ? "OK" : (LocalDate.now().isAfter(deadline) ? "RETARD" : "EN_ATTENTE"),
        tenue
            ? "AG ordinaire " + annee + " tenue et archivée avant " + deadline
            : "AG ordinaire " + annee + " non tenue ou non archivée — deadline " + deadline);
  }

  private ConformiteIndicateur verifierRapportAirms(UUID orgId, int anneeRapport) {
    // Heuristique : on vérifie l'existence d'au moins un PV AG_ORDINAIRE qui mentionne le rapport
    // (la génération du PDF AIRMS est tracée dans audit_trail_operations EXPORT — vérification simplifiée ici).
    Long count = (Long) em.createQuery(
        "SELECT COUNT(p) FROM ProcesVerbal p "
            + "WHERE p.organisationId = :org AND p.typeSeance = 'AG_ORDINAIRE' "
            + "AND EXTRACT(YEAR FROM p.dateSeance) = :annee + 1")
        .setParameter("org", orgId)
        .setParameter("annee", anneeRapport)
        .getSingleResult();

    boolean ok = count != null && count > 0;
    return new ConformiteIndicateur(ok ? "OK" : "EN_ATTENTE",
        ok ? "Rapport AIRMS " + anneeRapport + " présenté en AG" :
            "Rapport AIRMS " + anneeRapport + " non identifié dans les PV");
  }

  private int compterDirigeantsAvecCmu(UUID orgId) {
    Long count = (Long) em.createQuery(
        "SELECT COUNT(DISTINCT m) FROM Membre m "
            + "JOIN MembreOrganisation mo ON mo.membre = m "
            + "WHERE mo.organisation.id = :org "
            + "AND m.numeroCMU IS NOT NULL "
            + "AND mo.actif = true")
        .setParameter("org", orgId)
        .getSingleResult();
    return count == null ? 0 : count.intValue();
  }

  private BigDecimal calculerTauxKycAJour(UUID orgId) {
    // % de membres de l'org avec un kyc_dossier valide non expiré
    Long total = (Long) em.createQuery(
        "SELECT COUNT(m) FROM MembreOrganisation m "
            + "WHERE m.organisation.id = :org AND m.actif = true")
        .setParameter("org", orgId)
        .getSingleResult();
    if (total == null || total == 0) return BigDecimal.ZERO;

    // Membres de l'org dont le KYC est validé non expiré
    Long ok = (Long) em.createQuery(
        "SELECT COUNT(DISTINCT mo.membre) FROM MembreOrganisation mo, KycDossier k "
            + "WHERE mo.organisation.id = :org AND mo.actif = true "
            + "AND k.membre = mo.membre "
            + "AND k.statut = 'VALIDE' "
            + "AND (k.dateExpirationPiece IS NULL OR k.dateExpirationPiece > CURRENT_DATE)")
        .setParameter("org", orgId)
        .getSingleResult();

    return BigDecimal.valueOf(ok == null ? 0 : ok)
        .multiply(BigDecimal.valueOf(100))
        .divide(BigDecimal.valueOf(total), 2, java.math.RoundingMode.HALF_UP);
  }

  private BigDecimal calculerTauxFormationLbcFt(UUID orgId) {
    // Formation LBC/FT obligatoire annuelle (Instruction BCEAO 001-03-2025).
    // Implémenté dans Sprint 3 — à raffiner quand entité FormationLbcFt sera disponible.
    // Pour l'instant : retourne 0 (à compléter dans P1-NEW-12 ci-dessous).
    return BigDecimal.ZERO;
  }

  private ConformiteIndicateur verifierBesoinCommissaireAuxComptes(UUID orgId, Organisation org) {
    // Seuil OHADA AUDSCGIE : 2/3 (bilan > 125M, CA > 250M, effectif > 50)
    // Pour SFD : article 44 — encours ≥ 2 milliards FCFA
    boolean isSfd = org.getReferentielComptable() == ReferentielComptable.PCSFD_UMOA;
    if (isSfd) {
      return new ConformiteIndicateur("OBLIGATOIRE",
          "SFD article 44 : Commissaire aux comptes obligatoire (encours ≥ 2 Md FCFA)");
    }
    return new ConformiteIndicateur("OPTIONNEL",
        "Vérification à mener : 2/3 seuils AUDSCGIE (bilan 125M, CA 250M, effectif 50)");
  }

  private ConformiteIndicateur verifierFomusCiAJour(UUID orgId) {
    // FOMUS-CI obligatoire pour mutuelles sociales CI (Ordonnance 2024-1043)
    // Modalités exactes non publiées au 2026-04-25 → veille
    return new ConformiteIndicateur("EN_VEILLE",
        "FOMUS-CI : modalités cotisation à confirmer (arrêté ministériel attendu)");
  }

  private BigDecimal calculerCouvertureUbo(UUID orgId) {
    // % de dossiers KYC de l'org ayant au moins un UBO renseigné
    Long totalKyc = (Long) em.createQuery(
        "SELECT COUNT(DISTINCT k) FROM KycDossier k, MembreOrganisation mo "
            + "WHERE mo.organisation.id = :org AND k.membre = mo.membre AND mo.actif = true")
        .setParameter("org", orgId)
        .getSingleResult();
    if (totalKyc == null || totalKyc == 0) return BigDecimal.ZERO;

    Long avecUbo = (Long) em.createQuery(
        "SELECT COUNT(DISTINCT u.kycDossier) FROM BeneficiaireEffectif u, MembreOrganisation mo "
            + "WHERE mo.organisation.id = :org AND u.kycDossier.membre = mo.membre AND mo.actif = true")
        .setParameter("org", orgId)
        .getSingleResult();

    return BigDecimal.valueOf(avecUbo == null ? 0 : avecUbo)
        .multiply(BigDecimal.valueOf(100))
        .divide(BigDecimal.valueOf(totalKyc), 2, java.math.RoundingMode.HALF_UP);
  }

  /** Score global = moyenne pondérée des indicateurs (0-100). */
  private int calculerScoreGlobal(UUID orgId, Organisation org) {
    int score = 0;
    int max = 0;

    // Compliance officer (poids 20)
    max += 20;
    if (org.getComplianceOfficerId() != null) score += 20;

    // AG (poids 25)
    max += 25;
    ConformiteIndicateur ag = verifierAgAnnuelle(orgId, Year.now().getValue());
    if ("OK".equals(ag.statut())) score += 25;

    // KYC (poids 25)
    max += 25;
    BigDecimal tauxKyc = calculerTauxKycAJour(orgId);
    score += tauxKyc.multiply(BigDecimal.valueOf(25)).divide(BigDecimal.valueOf(100), 0, java.math.RoundingMode.HALF_UP).intValue();

    // Référentiel comptable correctement appliqué (poids 15)
    max += 15;
    if (org.getReferentielComptable() != null) score += 15;

    // CMU dirigeants (poids 15)
    max += 15;
    if (compterDirigeantsAvecCmu(orgId) >= 3) score += 15;  // ≥ 3 dirigeants enrôlés

    return max == 0 ? 0 : (score * 100) / max;
  }

  // ============================================================
  // DTOs
  // ============================================================

  public record ComplianceSnapshot(
      UUID organisationId,
      String organisationNom,
      ReferentielComptable referentielComptable,
      boolean complianceOfficerDesigne,
      ConformiteIndicateur agAnnuelle,
      ConformiteIndicateur rapportAirms,
      int dirigeantsAvecCmu,
      BigDecimal tauxKycAJourPct,
      BigDecimal tauxFormationLbcFtPct,
      ConformiteIndicateur commissaireAuxComptes,
      ConformiteIndicateur fomusCi,
      BigDecimal couvertureUboPct,
      int scoreGlobal
  ) {}

  public record ConformiteIndicateur(
      String statut,        // OK, EN_ATTENTE, RETARD, OPTIONNEL, OBLIGATOIRE, EN_VEILLE
      String message
  ) {}
}
