package fr.titionfire.ffsaf.domain.service;

import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.LogModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.data.repository.SequenceRepository;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.SequenceType;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

@WithSession
@ApplicationScoped
public class LicenceService {
    private static final Logger LOGGER = Logger.getLogger(LicenceService.class);

    @Inject
    LicenceRepository repository;

    @Inject
    CombRepository combRepository;

    @Inject
    SequenceRepository sequenceRepository;

    @Inject
    KeycloakService keycloakService;

    @Inject
    LoggerService ls;

    @Inject
    CheckoutService checkoutService;

    @Inject
    TradService trad;

    public Uni<List<LicenceModel>> getLicence(long id, Consumer<MembreModel> checkPerm) {
        return combRepository.findById(id).invoke(checkPerm)
                .chain(combRepository -> Mutiny.fetch(combRepository.getLicences()));
    }

    public Uni<List<LicenceModel>> getCurrentSaisonLicence(SecurityCtx securityCtx) {
        if (securityCtx == null || securityCtx.getSubject() == null)
            return repository.find("saison = ?1", Utils.getSaison()).list();

        return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult().map(MembreModel::getClub)
                .chain(clubModel -> combRepository.find("club = ?1", clubModel).list())
                .chain(membres -> repository.find("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres).list());
    }

    public Uni<?> valideLicences(List<Long> ids) {
        Uni<String> uni = Uni.createFrom().nullItem();

        for (Long id : ids) {
            uni = uni.chain(__ -> repository.find("membre.id = ?1 AND saison = ?2", id, Utils.getSaison()).firstResult()
                            .chain(model -> {
                                if (!model.isValidate())
                                    ls.logUpdate("validation de la licence", model);
                                return validateLicences(model);
                            }))
                    .map(__ -> "OK");
        }
        return uni.call(__ -> ls.append());
    }

    protected Uni<LicenceModel> validateLicences(LicenceModel model) {
        model.setValidate(true);
        return Panache.withTransaction(() -> repository.persist(model)
                .call(m -> Mutiny.fetch(m.getMembre())
                        .call(genLicenceNumberAndAccountIfNeed())
                ));
    }

    public Uni<?> validePaymentLicences(List<Long> ids) {
        Uni<String> uni = Uni.createFrom().nullItem();

        for (Long id : ids) {
            uni = uni.chain(__ -> repository.find("membre.id = ?1 AND saison = ?2", id, Utils.getSaison()).firstResult()
                            .chain(model -> {
                                if (!model.isPay())
                                    ls.logUpdate("payment (admin) de la licence", model);
                                return validatePayLicences(model);
                            }))
                    .map(__ -> "OK");
        }
        return uni.call(__ -> ls.append());
    }

    protected Uni<LicenceModel> validatePayLicences(LicenceModel model) {
        model.setPay(true);
        return Panache.withTransaction(() -> repository.persist(model));
    }

    public Uni<LicenceModel> setLicence(long id, LicenceForm form) {
        if (form.getId() == -1) {
            return combRepository.findById(id).chain(membreModel -> {
                LicenceModel model = new LicenceModel();
                model.setMembre(membreModel);
                model.setClub_id((membreModel.getClub() == null) ? null : membreModel.getClub().getId());
                model.setSaison(form.getSaison());
                model.setCertificate(form.getCertificate());
                model.setValidate(form.isValidate());
                model.setPay(form.isPay());
                return Panache.withTransaction(() -> repository.persist(model)
                                .call(m -> m.isValidate() ? Uni.createFrom().item(membreModel)
                                        .call(genLicenceNumberAndAccountIfNeed())
                                        : Uni.createFrom().nullItem()
                                ))
                        .call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
                                licenceModel));
            });
        } else {
            return repository.findById(form.getId()).chain(model -> {
                ls.logChange("Certificate", model.getCertificate(), form.getCertificate(), model);
                ls.logChange("Validate", model.isValidate(), form.isValidate(), model);
                ls.logChange("Pay", model.isPay(), form.isPay(), model);
                model.setCertificate(form.getCertificate());
                model.setValidate(form.isValidate());
                model.setPay(form.isPay());
                return Panache.withTransaction(() -> repository.persist(model)
                                .call(m -> m.isValidate() ? Mutiny.fetch(m.getMembre())
                                        .call(genLicenceNumberAndAccountIfNeed())
                                        : Uni.createFrom().nullItem()
                                ))
                        .call(__ -> ls.append());
            });
        }
    }

    private Function<MembreModel, Uni<?>> genLicenceNumberAndAccountIfNeed() {
        return membreModel -> ((membreModel.getLicence() == null || membreModel.getLicence() <= 0) ?
                sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
                        .invoke(i -> membreModel.setLicence(Math.toIntExact(i)))
                        .chain(() -> combRepository.persist(membreModel))
                : Uni.createFrom().nullItem())
                .call(__ -> (membreModel.getUserId() == null) ?
                        keycloakService.initCompte(membreModel.getId()).onFailure()
                                .invoke(t -> LOGGER.infof("Failed to init account: %s", t.getMessage())).onFailure()
                                .recoverWithNull()
                        : Uni.createFrom().nullItem());
    }

    public Uni<String> payLicences(List<Long> ids, Consumer<MembreModel> checkPerm, SecurityCtx securityCtx) {
        return repository.list("membre.id IN ?1 AND saison = ?2 AND pay = FALSE", ids, Utils.getSaison())
                .invoke(Unchecked.consumer(models -> {
                    if (models.size() != ids.size())
                        throw new DBadRequestException(trad.t("erreur.lors.de.la.selection.des.membres"));
                }))
                .call(models -> {
                    Uni<?> uni = Uni.createFrom().nullItem();
                    for (LicenceModel model : models)
                        uni = uni.chain(__ -> Mutiny.fetch(model.getMembre()).invoke(checkPerm));
                    return uni;
                })
                .chain(models -> checkoutService.create(models.stream().map(LicenceModel::getId).toList(),
                        securityCtx));
    }

    public Uni<?> deleteLicence(long id) {
        return repository.findById(id)
                .call(__ -> checkoutService.canDeleteLicence(id)
                        .invoke(Unchecked.consumer(b -> {
                            if (!b) throw new DBadRequestException(
                                    trad.t("licence.rm.err1"));
                        })))
                .call(model -> ls.logADelete(model))
                .chain(model -> Panache.withTransaction(() -> repository.delete(model)));
    }

    public Uni<LicenceModel> askLicence(long id, LicenceForm form, Consumer<MembreModel> checkPerm) {
        return combRepository.findById(id).invoke(checkPerm).chain(membreModel -> {
            if (form.getId() == -1) {
                return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count()
                        .invoke(Unchecked.consumer(count -> {
                            if (count > 0)
                                throw new DBadRequestException(trad.t("licence.deja.demandee"));
                        })).chain(__ -> combRepository.findById(id).chain(membreModel2 -> {
                            LicenceModel model = new LicenceModel();
                            model.setClub_id((membreModel2.getClub() == null) ? null : membreModel2.getClub().getId());
                            model.setMembre(membreModel2);
                            model.setSaison(Utils.getSaison());
                            model.setCertificate(form.getCertificate());
                            model.setValidate(false);
                            return Panache.withTransaction(() -> repository.persist(model));
                        }))
                        .call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
                                licenceModel));
            } else {
                return repository.findById(form.getId()).chain(model -> {
                    ls.logChange("Certificate", model.getCertificate(), form.getCertificate(), model);
                    model.setCertificate(form.getCertificate());
                    return Panache.withTransaction(() -> repository.persist(model))
                            .call(__ -> ls.append());
                });
            }
        });
    }

    public Uni<?> deleteAskLicence(long id, Consumer<MembreModel> checkPerm) {
        return repository.findById(id)
                .call(licenceModel -> Mutiny.fetch(licenceModel.getMembre()).invoke(checkPerm))
                .call(__ -> checkoutService.canDeleteLicence(id)
                        .invoke(Unchecked.consumer(b -> {
                            if (!b) throw new DBadRequestException(
                                    trad.t("licence.rm.err1"));
                        })))
                .invoke(Unchecked.consumer(licenceModel -> {
                    if (licenceModel.isValidate())
                        throw new DBadRequestException(trad.t("impossible.de.supprimer.une.licence.deja.validee"));
                    if (licenceModel.isPay())
                        throw new DBadRequestException(trad.t("impossible.de.supprimer.une.licence.deja.payee"));
                }))
                .call(model -> ls.logADelete(model))
                .chain(__ -> Panache.withTransaction(() -> repository.deleteById(id)));
    }

    public Uni<?> setImport(int licence, int saison, boolean valid) {
        return combRepository.find("licence = ?1", licence).firstResult()
                .chain(membreModel ->
                        repository.find("saison = ?1 AND membre = ?2", saison, membreModel).firstResult()
                                .chain(licenceModel -> {
                                    if (licenceModel != null) {
                                        if (licenceModel.getClub_id() == null)
                                            licenceModel.setClub_id(
                                                    (membreModel.getClub() == null) ? null : membreModel.getClub()
                                                            .getId());
                                        licenceModel.setValidate(valid);
                                        return Panache.withTransaction(() -> repository.persist(licenceModel));
                                    } else {
                                        LicenceModel model = new LicenceModel();
                                        model.setClub_id(
                                                (membreModel.getClub() == null) ? null : membreModel.getClub().getId());
                                        model.setMembre(membreModel);
                                        model.setSaison(saison);
                                        model.setCertificate("¤");
                                        model.setValidate(valid);
                                        return Panache.withTransaction(() -> repository.persist(model));
                                    }
                                }))
                .map(__ -> "OK");
    }
}
