package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.notification.request.CreateNotificationRequest;
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Service de cycle de vie des membres au sein d'une organisation.
 *
 * <p>Gère les transitions de statut automatiques et manuelles :
 * <ul>
 *   <li>Invitation → EN_ATTENTE_VALIDATION (après acceptation)</li>
 *   <li>EN_ATTENTE_VALIDATION → ACTIF (après validation admin)</li>
 *   <li>ACTIF → SUSPENDU (suspension manuelle ou automatique)</li>
 *   <li>INVITE → expiré (si la date d'expiration est dépassée)</li>
 *   <li>ACTIF → ARCHIVE (archivage)</li>
 * </ul>
 *
 * <p>Les tâches automatiques sont déclenchées par {@link MemberLifecycleScheduler}.
 */
@ApplicationScoped
public class MemberLifecycleService {

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

    /** Durée de validité par défaut d'une invitation (7 jours). */
    public static final int INVITATION_EXPIRY_DAYS = 7;

    /** Délai de rappel avant expiration (24h avant). */
    public static final int INVITATION_REMINDER_HOURS = 24;

    @Inject
    MembreOrganisationRepository membreOrgRepository;

    @Inject
    NotificationService notificationService;

    // =========================================================================
    //  Transitions manuelles
    // =========================================================================

    /**
     * Active un membre dans une organisation (transition → ACTIF).
     *
     * @param membreOrgId UUID du lien membre-organisation
     * @param adminId     UUID de l'administrateur qui valide
     * @param motif       Motif optionnel de l'activation
     */
    @Transactional
    public MembreOrganisation activerMembre(UUID membreOrgId, UUID adminId, String motif) {
        MembreOrganisation lien = charger(membreOrgId);
        verifierTransitionAutorisee(lien.getStatutMembre(),
                StatutMembre.EN_ATTENTE_VALIDATION, StatutMembre.INVITE, StatutMembre.SUSPENDU);

        lien.setStatutMembre(StatutMembre.ACTIF);
        lien.setDateChangementStatut(LocalDate.now());
        lien.setMotifStatut(motif != null ? motif : "Validation par l'administrateur");
        membreOrgRepository.persist(lien);

        // Mettre à jour le compteur de membres de l'organisation
        lien.getOrganisation().ajouterMembre();

        LOG.infof("Membre %s activé dans organisation %s par admin %s",
                lien.getMembre().getId(), lien.getOrganisation().getId(), adminId);

        // Notifier le membre
        envoyerNotification(lien.getMembre(), "Adhésion activée",
                "Votre adhésion à " + lien.getOrganisation().getNom() + " est maintenant active.");

        return lien;
    }

    /**
     * Suspend un membre dans une organisation (ACTIF → SUSPENDU).
     */
    @Transactional
    public MembreOrganisation suspendreMembre(UUID membreOrgId, UUID adminId, String motif) {
        MembreOrganisation lien = charger(membreOrgId);
        verifierTransitionAutorisee(lien.getStatutMembre(), StatutMembre.ACTIF);

        lien.setStatutMembre(StatutMembre.SUSPENDU);
        lien.setDateChangementStatut(LocalDate.now());
        lien.setMotifStatut(motif != null ? motif : "Suspension par l'administrateur");
        membreOrgRepository.persist(lien);

        LOG.infof("Membre %s suspendu dans organisation %s", lien.getMembre().getId(),
                lien.getOrganisation().getId());

        envoyerNotification(lien.getMembre(), "Adhésion suspendue",
                "Votre adhésion à " + lien.getOrganisation().getNom() + " a été suspendue. Motif : " + motif);

        return lien;
    }

    /**
     * Radie un membre d'une organisation (→ RADIE).
     */
    @Transactional
    public MembreOrganisation radierMembre(UUID membreOrgId, UUID adminId, String motif) {
        MembreOrganisation lien = charger(membreOrgId);

        lien.setStatutMembre(StatutMembre.RADIE);
        lien.setDateChangementStatut(LocalDate.now());
        lien.setMotifStatut(motif != null ? motif : "Radiation par l'administrateur");
        membreOrgRepository.persist(lien);

        // Mettre à jour le compteur de membres de l'organisation
        lien.getOrganisation().retirerMembre();

        LOG.infof("Membre %s radié de l'organisation %s. Motif: %s",
                lien.getMembre().getId(), lien.getOrganisation().getId(), motif);

        envoyerNotification(lien.getMembre(), "Adhésion radiée",
                "Votre adhésion à " + lien.getOrganisation().getNom() + " a été radiée.");

        return lien;
    }

    /**
     * Archive un membre (→ ARCHIVE) sans supprimer l'historique.
     */
    @Transactional
    public MembreOrganisation archiverMembre(UUID membreOrgId, String motif) {
        MembreOrganisation lien = charger(membreOrgId);

        lien.setStatutMembre(StatutMembre.ARCHIVE);
        lien.setDateChangementStatut(LocalDate.now());
        lien.setMotifArchivage(motif);
        membreOrgRepository.persist(lien);

        // Mettre à jour le compteur de membres de l'organisation
        lien.getOrganisation().retirerMembre();

        LOG.infof("Membre %s archivé dans l'organisation %s", lien.getMembre().getId(),
                lien.getOrganisation().getId());

        return lien;
    }

    /**
     * Crée une invitation (statut INVITE) pour un nouveau membre.
     *
     * @param membre        Le membre à inviter (doit exister en base)
     * @param organisation  L'organisation cible
     * @param adminId       L'administrateur qui invite
     * @param roleOrg       Rôle proposé dans l'organisation (optionnel)
     * @return le lien MembreOrganisation créé avec le token d'invitation
     */
    @Transactional
    public MembreOrganisation inviterMembre(
            dev.lions.unionflow.server.entity.Membre membre,
            dev.lions.unionflow.server.entity.Organisation organisation,
            UUID adminId,
            String roleOrg) {

        // Vérifier s'il n'existe pas déjà un lien actif
        membreOrgRepository.findByMembreIdAndOrganisationId(membre.getId(), organisation.getId())
                .ifPresent(existing -> {
                    throw new IllegalStateException("Le membre est déjà lié à cette organisation.");
                });

        String token = UUID.randomUUID().toString().replace("-", "");
        LocalDateTime now = LocalDateTime.now();

        MembreOrganisation lien = MembreOrganisation.builder()
                .membre(membre)
                .organisation(organisation)
                .statutMembre(StatutMembre.INVITE)
                .dateInvitation(now)
                .dateExpirationInvitation(now.plusDays(INVITATION_EXPIRY_DAYS))
                .tokenInvitation(token)
                .invitePar(adminId)
                .roleOrg(roleOrg)
                .build();

        membreOrgRepository.persist(lien);

        LOG.infof("Invitation créée pour membre %s dans organisation %s (expire: %s)",
                membre.getId(), organisation.getId(), lien.getDateExpirationInvitation());

        // Notifier le membre
        envoyerNotification(membre, "Invitation à rejoindre " + organisation.getNom(),
                "Vous avez été invité à rejoindre " + organisation.getNom() +
                ". Votre invitation expire dans " + INVITATION_EXPIRY_DAYS + " jours.");

        return lien;
    }

    /**
     * Accepte une invitation via son token (INVITE → EN_ATTENTE_VALIDATION).
     */
    @Transactional
    public MembreOrganisation accepterInvitation(String token) {
        MembreOrganisation lien = membreOrgRepository
                .find("tokenInvitation = ?1 and statutMembre = ?2", token, StatutMembre.INVITE)
                .firstResultOptional()
                .orElseThrow(() -> new IllegalArgumentException("Invitation introuvable ou déjà utilisée."));

        if (lien.getDateExpirationInvitation() != null &&
                lien.getDateExpirationInvitation().isBefore(LocalDateTime.now())) {
            throw new IllegalStateException("Cette invitation a expiré.");
        }

        lien.setStatutMembre(StatutMembre.EN_ATTENTE_VALIDATION);
        lien.setDateChangementStatut(LocalDate.now());
        lien.setTokenInvitation(null); // Invalider le token après usage
        membreOrgRepository.persist(lien);

        LOG.infof("Invitation acceptée par membre %s dans organisation %s",
                lien.getMembre().getId(), lien.getOrganisation().getId());

        return lien;
    }

    // =========================================================================
    //  Traitements automatiques (appelés par MemberLifecycleScheduler)
    // =========================================================================

    /**
     * Expire les invitations dont la date limite est dépassée.
     * Les membres INVITE passent au statut RADIE.
     *
     * @return nombre d'invitations expirées
     */
    @Transactional
    public int expirerInvitations() {
        LocalDateTime now = LocalDateTime.now();
        List<MembreOrganisation> expiredInvitations = membreOrgRepository.findInvitationsExpirees(now);

        int count = 0;
        for (MembreOrganisation lien : expiredInvitations) {
            lien.setStatutMembre(StatutMembre.RADIE);
            lien.setDateChangementStatut(LocalDate.now());
            lien.setMotifStatut("Invitation expirée sans réponse");
            membreOrgRepository.persist(lien);
            count++;

            LOG.infof("Invitation expirée : membre %s dans org %s",
                    lien.getMembre().getId(), lien.getOrganisation().getId());
        }

        if (count > 0) {
            LOG.infof("Expiration des invitations : %d invitation(s) expirée(s)", count);
        }
        return count;
    }

    /**
     * Envoie des rappels pour les invitations qui expirent dans les prochaines 24h.
     *
     * @return nombre de rappels envoyés
     */
    @Transactional
    public int envoyerRappelsInvitation() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime dans24h = now.plusHours(INVITATION_REMINDER_HOURS);
        List<MembreOrganisation> aRappeler =
                membreOrgRepository.findInvitationsExpirantBientot(now, dans24h);

        int count = 0;
        for (MembreOrganisation lien : aRappeler) {
            envoyerNotification(lien.getMembre(),
                    "Rappel : votre invitation expire bientôt",
                    "Votre invitation à rejoindre " + lien.getOrganisation().getNom() +
                    " expire dans moins de 24h. Acceptez-la maintenant.");
            count++;
        }

        if (count > 0) {
            LOG.infof("Rappels d'invitation : %d rappel(s) envoyé(s)", count);
        }
        return count;
    }

    // =========================================================================
    //  Helpers privés
    // =========================================================================

    private MembreOrganisation charger(UUID membreOrgId) {
        return membreOrgRepository.findByIdOptional(membreOrgId)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Lien membre-organisation introuvable : " + membreOrgId));
    }

    private void verifierTransitionAutorisee(StatutMembre current, StatutMembre... autorises) {
        for (StatutMembre autorise : autorises) {
            if (autorise.equals(current)) return;
        }
        throw new IllegalStateException(
                "Transition non autorisée depuis le statut " + current);
    }

    private void envoyerNotification(Membre membre, String titre, String corps) {
        try {
            if (notificationService != null) {
                CreateNotificationRequest req = CreateNotificationRequest.builder()
                        .typeNotification("SYSTEME")
                        .priorite("NORMALE")
                        .sujet(titre)
                        .corps(corps)
                        .membreId(membre.getId())
                        .build();
                notificationService.creerNotification(req);
            }
        } catch (Exception e) {
            LOG.warnf("Impossible d'envoyer la notification à %s : %s", membre.getId(), e.getMessage());
        }
    }
}
