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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lowagie.text.Document;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import dev.lions.unionflow.server.entity.RapportTrimestrielControleurInterne;
import dev.lions.unionflow.server.repository.AuditTrailOperationRepository;
import dev.lions.unionflow.server.repository.RapportTrimestrielControleurInterneRepository;
import dev.lions.unionflow.server.security.AuditTrailService;
import dev.lions.unionflow.server.service.compliance.ComplianceDashboardService;
import dev.lions.unionflow.server.service.compliance.ComplianceDashboardService.ComplianceSnapshot;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Service de génération automatisée des rapports trimestriels du Contrôleur Interne.
 *
 * <p>Pour chaque {@code (organisation, année, trimestre)} :
 * <ol>
 *   <li>Agrège les données : score conformité, DOS CENTIF, formations LBC/FT, KYC, audit trail
 *   <li>Sérialise en JSON (colonne {@code contenu_jsonb})
 *   <li>Génère un PDF OpenPDF (colonne {@code pdf_bytes})
 *   <li>Persiste — ou met à jour si rapport DRAFT déjà existant
 * </ol>
 *
 * <p>Cycle de vie : DRAFT → SIGNE (hash SHA-256 figé) → ARCHIVE.
 *
 * @since 2026-04-25 (P2-NEW-3)
 */
@ApplicationScoped
public class RapportTrimestrielService {

  private static final Logger LOG = Logger.getLogger(RapportTrimestrielService.class);
  private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
  private static final ObjectMapper MAPPER = new ObjectMapper();

  @Inject EntityManager em;
  @Inject ComplianceDashboardService complianceService;
  @Inject AuditTrailOperationRepository auditTrailRepo;
  @Inject RapportTrimestrielControleurInterneRepository repository;
  @Inject AuditTrailService auditTrail;

  /**
   * Génère (ou régénère) le rapport trimestriel pour {@code (orgId, annee, trimestre)}.
   *
   * @return l'entité persistée avec PDF + JSON snapshot
   * @throws IllegalStateException si le rapport est déjà SIGNE ou ARCHIVE (pas de réécriture)
   */
  @Transactional
  public RapportTrimestrielControleurInterne genererRapport(UUID orgId, int annee, int trimestre) {
    if (trimestre < 1 || trimestre > 4) {
      throw new IllegalArgumentException("Trimestre doit être entre 1 et 4 (reçu: " + trimestre + ")");
    }

    var existant = repository.trouverParOrgAnneeTrimestre(orgId, annee, trimestre);
    if (existant.isPresent() && !"DRAFT".equals(existant.get().getStatut())) {
      throw new IllegalStateException(
          "Rapport " + annee + "/T" + trimestre + " déjà " + existant.get().getStatut()
              + " — régénération interdite (immuabilité signature)");
    }

    DonneesAgregees donnees = agreger(orgId, annee, trimestre);
    JsonNode contenuJsonb = construireJson(donnees);
    byte[] pdfBytes = genererPdf(donnees);

    RapportTrimestrielControleurInterne rapport = existant.orElseGet(
        RapportTrimestrielControleurInterne::new);
    rapport.setOrganisationId(orgId);
    rapport.setAnnee(annee);
    rapport.setTrimestre(trimestre);
    rapport.setDateGeneration(LocalDateTime.now());
    rapport.setStatut("DRAFT");
    rapport.setScoreConformite(donnees.snapshot.scoreGlobal());
    rapport.setContenuJsonb(contenuJsonb);
    rapport.setPdfBytes(pdfBytes);
    rapport.setActif(true);

    repository.persist(rapport);

    auditTrail.logSimple("RapportTrimestrielControleurInterne", rapport.getId(), "EXPORT",
        "Génération rapport trimestriel " + annee + "/T" + trimestre + " org=" + orgId);
    LOG.infof("Rapport trimestriel %s/T%s généré pour org=%s (score=%s)",
        annee, trimestre, orgId, donnees.snapshot.scoreGlobal());

    return rapport;
  }

  /**
   * Signe le rapport — calcule hash SHA-256 du contenu JSON, fige l'état.
   *
   * @throws IllegalStateException si déjà SIGNE/ARCHIVE
   */
  @Transactional
  public RapportTrimestrielControleurInterne signer(UUID rapportId, UUID signataireId) {
    RapportTrimestrielControleurInterne rapport = repository.findById(rapportId);
    if (rapport == null) {
      throw new IllegalArgumentException("Rapport introuvable : " + rapportId);
    }
    if (!"DRAFT".equals(rapport.getStatut())) {
      throw new IllegalStateException(
          "Rapport déjà " + rapport.getStatut() + " — signature impossible");
    }

    String hash = calculerHash(rapport.getContenuJsonb());
    rapport.setStatut("SIGNE");
    rapport.setSignataireId(signataireId);
    rapport.setDateSignature(LocalDateTime.now());
    rapport.setHashSha256(hash);

    auditTrail.logSimple("RapportTrimestrielControleurInterne", rapportId, "VALIDATE",
        "Signature contrôleur interne — hash=" + hash.substring(0, 16) + "...");
    return rapport;
  }

  /** Archive un rapport SIGNE. */
  @Transactional
  public RapportTrimestrielControleurInterne archiver(UUID rapportId) {
    RapportTrimestrielControleurInterne rapport = repository.findById(rapportId);
    if (rapport == null) {
      throw new IllegalArgumentException("Rapport introuvable : " + rapportId);
    }
    if (!"SIGNE".equals(rapport.getStatut())) {
      throw new IllegalStateException(
          "Rapport en statut " + rapport.getStatut() + " — archivage interdit (signer d'abord)");
    }
    rapport.setStatut("ARCHIVE");
    auditTrail.logSimple("RapportTrimestrielControleurInterne", rapportId, "VALIDATE",
        "Archivage rapport trimestriel");
    return rapport;
  }

  // ============================================================
  // Agrégation
  // ============================================================

  DonneesAgregees agreger(UUID orgId, int annee, int trimestre) {
    LocalDate debut = debutTrimestre(annee, trimestre);
    LocalDate fin = finTrimestre(annee, trimestre);
    LocalDateTime debutDt = debut.atStartOfDay();
    LocalDateTime finDt = fin.plusDays(1).atStartOfDay();

    ComplianceSnapshot snapshot = complianceService.snapshot(orgId);

    long dosCount = compterDosCentif(orgId, debutDt, finDt);
    long formationsCount = compterFormationsLbcFt(orgId, debut, fin);
    long anomaliesAuditCount = auditTrailRepo
        .find("organisationActiveId = ?1 AND operationAt BETWEEN ?2 AND ?3 AND sodCheckPassed = false",
            orgId, debutDt, finDt)
        .count();
    long demandesAideTraitees = compterDemandesAide(orgId, debutDt, finDt);

    return new DonneesAgregees(orgId, annee, trimestre, debut, fin,
        snapshot, dosCount, formationsCount, anomaliesAuditCount, demandesAideTraitees);
  }

  private long compterDosCentif(UUID orgId, LocalDateTime debut, LocalDateTime fin) {
    try {
      Long c = (Long) em.createQuery(
          "SELECT COUNT(d) FROM DosCentif d "
              + "WHERE d.organisationId = :org AND d.dateDeclaration BETWEEN :debut AND :fin")
          .setParameter("org", orgId)
          .setParameter("debut", debut)
          .setParameter("fin", fin)
          .getSingleResult();
      return c == null ? 0L : c;
    } catch (Exception e) {
      LOG.debugf("Entité DosCentif absente, count=0 (msg=%s)", e.getMessage());
      return 0L;
    }
  }

  private long compterFormationsLbcFt(UUID orgId, LocalDate debut, LocalDate fin) {
    try {
      Long c = (Long) em.createQuery(
          "SELECT COUNT(f) FROM FormationLbcFt f "
              + "WHERE f.organisationId = :org "
              + "AND f.dateSession BETWEEN :debut AND :fin")
          .setParameter("org", orgId)
          .setParameter("debut", debut.atStartOfDay())
          .setParameter("fin", fin.plusDays(1).atStartOfDay())
          .getSingleResult();
      return c == null ? 0L : c;
    } catch (Exception e) {
      return 0L;
    }
  }

  private long compterDemandesAide(UUID orgId, LocalDateTime debut, LocalDateTime fin) {
    try {
      Long c = (Long) em.createQuery(
          "SELECT COUNT(d) FROM DemandeAide d "
              + "WHERE d.organisationId = :org AND d.dateCreation BETWEEN :debut AND :fin")
          .setParameter("org", orgId)
          .setParameter("debut", debut)
          .setParameter("fin", fin)
          .getSingleResult();
      return c == null ? 0L : c;
    } catch (Exception e) {
      return 0L;
    }
  }

  // ============================================================
  // Sérialisation JSON
  // ============================================================

  JsonNode construireJson(DonneesAgregees d) {
    ObjectNode root = MAPPER.createObjectNode();
    root.put("organisationId", d.orgId.toString());
    root.put("annee", d.annee);
    root.put("trimestre", d.trimestre);
    root.put("periode_debut", d.debut.toString());
    root.put("periode_fin", d.fin.toString());
    root.put("date_generation", LocalDateTime.now().toString());

    ObjectNode conformite = root.putObject("conformite");
    conformite.put("score_global", d.snapshot.scoreGlobal());
    conformite.put("compliance_officer_designe", d.snapshot.complianceOfficerDesigne());
    conformite.put("ag_annuelle_statut", d.snapshot.agAnnuelle().statut());
    conformite.put("rapport_airms_statut", d.snapshot.rapportAirms().statut());
    conformite.put("dirigeants_avec_cmu", d.snapshot.dirigeantsAvecCmu());
    conformite.put("taux_kyc_pct", d.snapshot.tauxKycAJourPct());
    conformite.put("taux_formation_lbcft_pct", d.snapshot.tauxFormationLbcFtPct());
    conformite.put("couverture_ubo_pct", d.snapshot.couvertureUboPct());

    ObjectNode activite = root.putObject("activite_trimestrielle");
    activite.put("dos_centif_emises", d.dosCount);
    activite.put("sessions_formation_lbcft", d.formationsCount);
    activite.put("anomalies_audit_trail_sod", d.anomaliesAuditCount);
    activite.put("demandes_aide_creees", d.demandesAideTraitees);

    ArrayNode alertes = root.putArray("alertes");
    if (!d.snapshot.complianceOfficerDesigne()) {
      alertes.add("CRITIQUE — Compliance Officer non désigné (Instr. BCEAO 001-03-2025)");
    }
    if ("RETARD".equals(d.snapshot.agAnnuelle().statut())) {
      alertes.add("CRITIQUE — AG annuelle en retard (échéance 30/06)");
    }
    if (d.anomaliesAuditCount > 0) {
      alertes.add("ATTENTION — " + d.anomaliesAuditCount + " violation(s) SoD détectée(s)");
    }
    if (d.snapshot.scoreGlobal() < 60) {
      alertes.add("ATTENTION — Score conformité <60% (" + d.snapshot.scoreGlobal() + ")");
    }

    return root;
  }

  // ============================================================
  // PDF OpenPDF
  // ============================================================

  byte[] genererPdf(DonneesAgregees d) {
    try (Document doc = new Document(PageSize.A4);
         ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      PdfWriter.getInstance(doc, out);
      doc.open();

      Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16, Color.BLACK);
      Font sectionFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12, new Color(0, 51, 102));
      Font normalFont = FontFactory.getFont(FontFactory.HELVETICA, 10, Color.BLACK);

      Paragraph title = new Paragraph(
          "RAPPORT TRIMESTRIEL — CONTRÔLEUR INTERNE\n" + d.snapshot.organisationNom(), titleFont);
      title.setAlignment(Element.ALIGN_CENTER);
      title.setSpacingAfter(20);
      doc.add(title);

      Paragraph periode = new Paragraph(
          String.format("Trimestre %d / %d  (%s — %s)",
              d.trimestre, d.annee, d.debut, d.fin), normalFont);
      periode.setAlignment(Element.ALIGN_CENTER);
      periode.setSpacingAfter(15);
      doc.add(periode);

      Paragraph genere = new Paragraph(
          "Généré le " + LocalDateTime.now().format(DATE_FMT), normalFont);
      genere.setAlignment(Element.ALIGN_CENTER);
      genere.setSpacingAfter(25);
      doc.add(genere);

      // Section conformité
      doc.add(new Paragraph("1. INDICATEURS DE CONFORMITÉ", sectionFont));
      PdfPTable tConf = new PdfPTable(2);
      tConf.setWidthPercentage(100);
      tConf.setSpacingBefore(5);
      tConf.setSpacingAfter(15);
      addRow(tConf, "Score global", d.snapshot.scoreGlobal() + " / 100", normalFont);
      addRow(tConf, "Compliance Officer", d.snapshot.complianceOfficerDesigne() ? "Désigné" : "ABSENT",
          normalFont);
      addRow(tConf, "AG annuelle", d.snapshot.agAnnuelle().statut(), normalFont);
      addRow(tConf, "Rapport AIRMS", d.snapshot.rapportAirms().statut(), normalFont);
      addRow(tConf, "Dirigeants avec CMU", String.valueOf(d.snapshot.dirigeantsAvecCmu()), normalFont);
      addRow(tConf, "Taux KYC à jour", d.snapshot.tauxKycAJourPct() + " %", normalFont);
      addRow(tConf, "Taux formation LBC/FT", d.snapshot.tauxFormationLbcFtPct() + " %", normalFont);
      addRow(tConf, "Couverture UBO", d.snapshot.couvertureUboPct() + " %", normalFont);
      doc.add(tConf);

      // Section activité
      doc.add(new Paragraph("2. ACTIVITÉ DU TRIMESTRE", sectionFont));
      PdfPTable tAct = new PdfPTable(2);
      tAct.setWidthPercentage(100);
      tAct.setSpacingBefore(5);
      tAct.setSpacingAfter(15);
      addRow(tAct, "DOS CENTIF émises", String.valueOf(d.dosCount), normalFont);
      addRow(tAct, "Sessions formation LBC/FT", String.valueOf(d.formationsCount), normalFont);
      addRow(tAct, "Anomalies audit trail (SoD)", String.valueOf(d.anomaliesAuditCount), normalFont);
      addRow(tAct, "Demandes d'aide créées", String.valueOf(d.demandesAideTraitees), normalFont);
      doc.add(tAct);

      // Section alertes
      doc.add(new Paragraph("3. ALERTES & POINTS D'ATTENTION", sectionFont));
      Paragraph alertes = new Paragraph();
      alertes.setSpacingBefore(5);
      boolean any = false;
      if (!d.snapshot.complianceOfficerDesigne()) {
        alertes.add(new Phrase(
            "• CRITIQUE — Compliance Officer non désigné (Instr. BCEAO 001-03-2025)\n", normalFont));
        any = true;
      }
      if ("RETARD".equals(d.snapshot.agAnnuelle().statut())) {
        alertes.add(new Phrase("• CRITIQUE — AG annuelle en retard (échéance 30/06)\n", normalFont));
        any = true;
      }
      if (d.anomaliesAuditCount > 0) {
        alertes.add(new Phrase(
            "• ATTENTION — " + d.anomaliesAuditCount + " violation(s) SoD détectée(s)\n",
            normalFont));
        any = true;
      }
      if (d.snapshot.scoreGlobal() < 60) {
        alertes.add(new Phrase(
            "• ATTENTION — Score conformité <60% (" + d.snapshot.scoreGlobal() + ")\n", normalFont));
        any = true;
      }
      if (!any) {
        alertes.add(new Phrase("Aucune alerte critique sur ce trimestre.", normalFont));
      }
      doc.add(alertes);

      doc.add(new Paragraph("\n\nSignature du Contrôleur Interne : ____________________________",
          normalFont));
      doc.add(new Paragraph("Date : ____ / ____ / ________", normalFont));

      doc.close();
      return out.toByteArray();
    } catch (IOException e) {
      throw new IllegalStateException("Erreur génération PDF rapport trimestriel", e);
    }
  }

  private void addRow(PdfPTable t, String label, String value, Font f) {
    PdfPCell c1 = new PdfPCell(new Phrase(label, f));
    PdfPCell c2 = new PdfPCell(new Phrase(value, f));
    c1.setPadding(4f);
    c2.setPadding(4f);
    t.addCell(c1);
    t.addCell(c2);
  }

  // ============================================================
  // Hash SHA-256
  // ============================================================

  static String calculerHash(JsonNode contenu) {
    try {
      String canonical = contenu == null ? "" : contenu.toString();
      byte[] digest = MessageDigest.getInstance("SHA-256")
          .digest(canonical.getBytes(StandardCharsets.UTF_8));
      return HexFormat.of().formatHex(digest);
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException("SHA-256 indisponible", e);
    }
  }

  // ============================================================
  // Trimestre
  // ============================================================

  static LocalDate debutTrimestre(int annee, int trimestre) {
    int mois = switch (trimestre) {
      case 1 -> 1;
      case 2 -> 4;
      case 3 -> 7;
      case 4 -> 10;
      default -> throw new IllegalArgumentException("Trimestre invalide: " + trimestre);
    };
    return LocalDate.of(annee, mois, 1);
  }

  static LocalDate finTrimestre(int annee, int trimestre) {
    return debutTrimestre(annee, trimestre).plusMonths(3).minusDays(1);
  }

  // ============================================================
  // DTO interne
  // ============================================================

  record DonneesAgregees(
      UUID orgId,
      int annee,
      int trimestre,
      LocalDate debut,
      LocalDate fin,
      ComplianceSnapshot snapshot,
      long dosCount,
      long formationsCount,
      long anomaliesAuditCount,
      long demandesAideTraitees
  ) {}
}
