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

import dev.lions.unionflow.server.entity.ProcesVerbal;
import dev.lions.unionflow.server.repository.ProcesVerbalRepository;
import dev.lions.unionflow.server.security.AuditTrailService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.WebApplicationException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Service de gestion des PV OHADA (AG/CA) : calcul de quorum, signature électronique,
 * archivage immuable via hash SHA-256.
 *
 * <p>Quorum requis par défaut (peut être overridé par les statuts) :
 *
 * <ul>
 *   <li>AG_CONSTITUTIVE : 2/3 (66.67%) des fondateurs
 *   <li>AG_ORDINAIRE : 1/2 (50%) sur 1ère convocation, sans quorum sur 2e
 *   <li>AG_EXTRAORDINAIRE : 2/3 (66.67%)
 *   <li>CA : 1/2 (50%) des administrateurs
 * </ul>
 *
 * @since 2026-04-25 (P1-NEW-2)
 */
@ApplicationScoped
public class ProcesVerbalService {

  private static final Logger LOG = Logger.getLogger(ProcesVerbalService.class);

  @Inject ProcesVerbalRepository repository;
  @Inject AuditTrailService auditTrail;

  /** Quorum requis par défaut selon type de séance. */
  public static BigDecimal quorumRequisDefaut(String typeSeance) {
    return switch (typeSeance) {
      case "AG_CONSTITUTIVE", "AG_EXTRAORDINAIRE" -> new BigDecimal("66.67");
      case "AG_ORDINAIRE" -> new BigDecimal("50.00");
      case "CA", "BUREAU" -> new BigDecimal("50.00");
      default -> new BigDecimal("50.00");
    };
  }

  /**
   * Calcule le quorum réel d'une séance et indique s'il est atteint.
   *
   * <p>Quorum = (présents + représentés) / convoqués × 100.
   */
  public void calculerEtFixerQuorum(ProcesVerbal pv) {
    if (pv == null) throw new IllegalArgumentException("PV null");
    int convoques = pv.getNombreConvoques();
    int participants = pv.getNombrePresents() + pv.getNombreRepresentes();

    BigDecimal pct = convoques == 0
        ? BigDecimal.ZERO
        : BigDecimal.valueOf(participants)
            .multiply(BigDecimal.valueOf(100))
            .divide(BigDecimal.valueOf(convoques), 2, RoundingMode.HALF_UP);

    BigDecimal requis = pv.getQuorumRequisPct() != null
        ? pv.getQuorumRequisPct()
        : quorumRequisDefaut(pv.getTypeSeance());

    pv.setQuorumCalculePct(pct);
    pv.setQuorumRequisPct(requis);
    pv.setQuorumAtteint(pct.compareTo(requis) >= 0);
  }

  /** Crée un PV (statut BROUILLON) avec quorum auto-calculé. */
  @Transactional
  public ProcesVerbal creer(ProcesVerbal pv) {
    if (pv.getStatut() == null) pv.setStatut("BROUILLON");
    calculerEtFixerQuorum(pv);
    repository.persist(pv);
    auditTrail.logSimple("ProcesVerbal", pv.getId(), "CREATE",
        "Création PV " + pv.getTypeSeance() + " — " + pv.getTitre());
    return pv;
  }

  /**
   * Adopte un PV brouillon : marque ADOPTE, fige le contenu, calcule le hash SHA-256
   * (immuabilité). Pré-requis : quorum atteint, ordre du jour et résolutions remplis.
   */
  @Transactional
  public ProcesVerbal adopter(UUID pvId) {
    ProcesVerbal pv = repository.findById(pvId);
    if (pv == null) throw new NotFoundException("PV introuvable : " + pvId);
    if (!"BROUILLON".equals(pv.getStatut())) {
      throw new WebApplicationException(
          "PV doit être en statut BROUILLON pour être adopté (actuel: " + pv.getStatut() + ")",
          400);
    }
    if (!pv.isQuorumAtteint()) {
      throw new WebApplicationException("Quorum non atteint, PV ne peut être adopté", 400);
    }

    pv.setStatut("ADOPTE");
    pv.setHashSha256(calculerHash(pv));
    repository.persist(pv);
    auditTrail.log("ProcesVerbal", pv.getId(), "APPROVE",
        "Adoption PV — quorum=" + pv.getQuorumCalculePct() + "%, hash=" + pv.getHashSha256(),
        null, pv, null, true, null);
    return pv;
  }

  /**
   * Signe un PV adopté (par le président + secrétaire). Marque SIGNE puis ARCHIVE.
   *
   * @param signaturePresident base64 d'une signature électronique (ex: image/SVG ou hash de
   *     certif)
   */
  @Transactional
  public ProcesVerbal signer(UUID pvId, String signaturePresident, String signatureSecretaire) {
    ProcesVerbal pv = repository.findById(pvId);
    if (pv == null) throw new NotFoundException("PV introuvable : " + pvId);
    if (!"ADOPTE".equals(pv.getStatut())) {
      throw new WebApplicationException(
          "PV doit être ADOPTE avant signature (actuel: " + pv.getStatut() + ")", 400);
    }

    // Vérifie que le hash n'a pas changé depuis adoption (immutabilité)
    String hashRecalcule = calculerHash(pv);
    if (!hashRecalcule.equals(pv.getHashSha256())) {
      throw new WebApplicationException(
          "PV modifié après adoption (hash divergent) — signature refusée", 409);
    }

    pv.setSignaturePresident(signaturePresident);
    pv.setSignatureSecretaire(signatureSecretaire);
    pv.setDateSignature(LocalDateTime.now());
    pv.setStatut("SIGNE");
    repository.persist(pv);
    auditTrail.log("ProcesVerbal", pv.getId(), "APPROVE", "Signature PV", null, pv, null, true, null);
    return pv;
  }

  /** Archive un PV signé (figé pour la postérité — conservation OHADA 10 ans minimum). */
  @Transactional
  public ProcesVerbal archiver(UUID pvId) {
    ProcesVerbal pv = repository.findById(pvId);
    if (pv == null) throw new NotFoundException("PV introuvable : " + pvId);
    if (!"SIGNE".equals(pv.getStatut())) {
      throw new WebApplicationException(
          "PV doit être SIGNE avant archivage (actuel: " + pv.getStatut() + ")", 400);
    }
    pv.setStatut("ARCHIVE");
    repository.persist(pv);
    auditTrail.logSimple("ProcesVerbal", pv.getId(), "EXPORT", "Archivage PV (immuable)");
    return pv;
  }

  /**
   * Calcule le hash SHA-256 du contenu signé d'un PV. Inclut tous les champs immuables (date,
   * lieu, quorum, ordre du jour, résolutions) — exclut signature et statut.
   */
  public String calculerHash(ProcesVerbal pv) {
    try {
      String content = String.join("|",
          String.valueOf(pv.getId()),
          String.valueOf(pv.getOrganisationId()),
          nullSafe(pv.getTypeSeance()),
          nullSafe(pv.getTitre()),
          String.valueOf(pv.getDateSeance()),
          nullSafe(pv.getLieu()),
          String.valueOf(pv.getNombreConvoques()),
          String.valueOf(pv.getNombrePresents()),
          String.valueOf(pv.getNombreRepresentes()),
          String.valueOf(pv.getQuorumCalculePct()),
          nullSafe(pv.getOrdreDuJour()),
          nullSafe(pv.getResolutions()),
          nullSafe(pv.getDeliberations())
      );
      MessageDigest md = MessageDigest.getInstance("SHA-256");
      byte[] hash = md.digest(content.getBytes(StandardCharsets.UTF_8));
      StringBuilder hex = new StringBuilder();
      for (byte b : hash) {
        hex.append(String.format("%02x", b));
      }
      return hex.toString();
    } catch (Exception e) {
      throw new RuntimeException("Erreur calcul hash PV : " + e.getMessage(), e);
    }
  }

  private static String nullSafe(String s) {
    return s == null ? "" : s;
  }
}
