package fr.titionfire.ffsaf.domain.service;

import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
import fr.titionfire.ffsaf.net2.request.SReqComb;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.rest.from.UserSettingForm;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.PanacheQuery;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.scheduler.Scheduled;
import io.quarkus.vertx.VertxContextSupport;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;


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

    @Inject
    CombRepository repository;
    @Inject
    ClubRepository clubRepository;
    @Inject
    LicenceRepository licenceRepository;

    @Inject
    CompetitionRepository competitionRepository;

    @Inject
    ServerCustom serverCustom;
    @Inject
    KeycloakService keycloakService;

    @ConfigProperty(name = "upload_dir")
    String media;

    @Inject
    RegisterRepository registerRepository;

    @Inject
    LoggerService ls;

    @Inject
    TradService trad;

    public SimpleCombModel find(int licence, String np) throws Throwable {
        return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() ->
                repository.find(
                        "licence = ?1 AND (unaccent(lname) ILIKE unaccent(?2) OR unaccent(fname) ILIKE unaccent(?2))",
                        licence, np).firstResult().map(SimpleCombModel::fromModel)));
    }

    public SimpleCombModel findByIdOptionalComb(long id) throws Throwable {
        return VertxContextSupport.subscribeAndAwait(
                () -> Panache.withTransaction(() -> repository.findById(id).map(SimpleCombModel::fromModel)));
    }

    final static String FIND_NAME_REQUEST = "unaccent(fname) ILIKE unaccent(?1) OR unaccent(lname) ILIKE unaccent(?1) " +
            "OR unaccent(fname || ' ' || lname) ILIKE unaccent(?1) OR unaccent(lname || ' ' || fname) ILIKE unaccent(?1)";

    private Uni<List<LicenceModel>> getLicenceListe(int licenceRequest, int payState) {
        Uni<List<LicenceModel>> baseUni;
        String queryStr = "saison = ?1";
        if (payState == 0)
            queryStr += " AND pay = FALSE";
        if (payState == 1)
            queryStr += " AND pay = TRUE";
        if (licenceRequest == 0 || licenceRequest == 1)
            baseUni = licenceRepository.list(queryStr, Utils.getSaison());
        else if (licenceRequest == 2)
            baseUni = licenceRepository.list(queryStr + " AND validate = FALSE", Utils.getSaison());
        else if (licenceRequest == 5)
            baseUni = licenceRepository.list(queryStr + " AND validate = FALSE AND LENGTH(certificate) >= 3",
                    Utils.getSaison());
        else if (licenceRequest == 6)
            baseUni = licenceRepository.list(queryStr + " AND validate = FALSE AND LENGTH(certificate) <= 2",
                    Utils.getSaison());
        else if (licenceRequest == 3)
            baseUni = licenceRepository.list(queryStr + " AND validate = TRUE", Utils.getSaison());
        else
            baseUni = Uni.createFrom().item(new ArrayList<>());
        return baseUni;
    }

    private Sort getSort(String order) {

        Sort sort;
        if (order == null || order.isBlank()) {
            sort = Sort.ascending("fname", "lname");
        } else {
            sort = Sort.empty();

            for (String e : order.split(",")) {
                String[] split = e.split(" ");
                if (split.length == 2) {
                    sort = sort.and(split[0],
                            split[1].equals("n") ? Sort.Direction.Ascending : Sort.Direction.Descending);
                } else {
                    return null;
                }
            }
        }

        return sort;
    }

    public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
                                                String order, String categorie, Boolean archive, String subject) {
        return repository.find("userId = ?1", subject).firstResult()
                .invoke(Unchecked.consumer(membreModel -> {
                    if (membreModel == null || membreModel.getClub() == null)
                        throw new DForbiddenException();
                }))
                .chain(membreModel ->
                        searchAdmin(limit, page, search, membreModel.getClub(), licenceRequest, payState, order,
                                categorie, archive));
    }

    public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, Object club,
                                                     int licenceRequest, int payState, String order, String categorie,
                                                     Boolean archive) {
        if (search == null)
            search = "";
        search = "%" + search.replaceAll(" ", "% %") + "%";

        String categorieFilter;
        if (categorie == null || categorie.isBlank())
            categorieFilter = " True";
        else
            categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();

        String archiveFilter;
        if (archive == null || archive)
            archiveFilter = "True";
        else
            archiveFilter = String.format(
                    "EXISTS (SELECT 1 FROM LicenceModel l WHERE l.membre.id = m.id AND l.saison >= %s)",
                    Utils.getSaison() - 1);

        String clubFilter = "(TRUE OR ?3 = ?3)";
        if (club != null) {
            if (club instanceof String club_) {
                if (!club_.isBlank()) {
                    if (club_.equals("null"))
                        clubFilter = "club IS NULL AND ?3 = ?3";
                    else
                        clubFilter = "LOWER(club.name) LIKE LOWER(?3)";
                }
            } else if (club instanceof ClubModel)
                clubFilter = "club = ?3";
        }

        String queryStr = String.format(
                "SELECT m FROM MembreModel m WHERE id %s ?2 AND %s AND (%s) AND %s AND %s",
                (licenceRequest == 0 || licenceRequest == 4) ? "NOT IN" : "IN", clubFilter, FIND_NAME_REQUEST,
                categorieFilter,
                archiveFilter);

        Sort sort = getSort(order);
        if (sort == null)
            return Uni.createFrom().failure(new DInternalError(trad.t("erreur.lors.calcul.du.trie")));

        String finalSearch = search;
        return getLicenceListe(licenceRequest, payState)
                .map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
                .chain(ids -> {
                    PanacheQuery<MembreModel> query;
                    query = repository.find(queryStr, sort, finalSearch, ids, club).page(Page.ofSize(limit));
                    return getPageResult(query, limit, page);
                });
    }

    private Uni<PageResult<SimpleMembre>> getPageResult(PanacheQuery<MembreModel> query, int limit, int page) {
        return Uni.createFrom().item(new PageResult<SimpleMembre>())
                .invoke(result -> result.setPage(page))
                .invoke(result -> result.setPage_size(limit))
                .call(result -> query.count().invoke(result::setResult_count))
                .call(result -> query.pageCount()
                        .invoke(Unchecked.consumer(pages -> {
                            if (page > pages) throw new DBadRequestException(trad.t("page.out.of.range"));
                        }))
                        .invoke(result::setPage_count))
                .call(result -> query.page(Page.of(page, limit)).list()
                        .map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList())
                        .invoke(result::setResult));
    }

    public Uni<List<SimpleMembreInOutData>> getAllExport(String subject) {
        return repository.find("userId = ?1", subject).firstResult()
                .chain(membreModel -> repository.list("club = ?1", membreModel.getClub()))
                .chain(membres -> licenceRepository.list("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres)
                        .map(l -> membres.stream().map(m -> SimpleMembreInOutData.fromModel(m, l)).toList()));
    }

    public Uni<String> allImporte(String subject, List<SimpleMembreInOutData> data) {
        if (data == null)
            return Uni.createFrom().nullItem();
        final List<SimpleMembreInOutData> data2 = data.stream()
                .filter(dataIn -> dataIn.getNom() != null && !dataIn.getNom()
                        .isBlank() && dataIn.getPrenom() != null && !dataIn.getPrenom().isBlank()).toList();
        if (data2.isEmpty())
            return Uni.createFrom().nullItem();
        AtomicReference<ClubModel> clubModel = new AtomicReference<>();

        LOGGER.debugf("Membre import (size=%d)", data2.size());
        for (SimpleMembreInOutData simpleMembreInOutData : data2) {
            LOGGER.debugf("-> %s", simpleMembreInOutData.toString());
        }

        return repository.find("userId = ?1", subject).firstResult()
                .chain(membreModel -> {
                    clubModel.set(membreModel.getClub());
                    if (data2.stream().noneMatch(d -> d.getLicence() != null))
                        return Uni.createFrom().item(new ArrayList<MembreModel>());
                    return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3",
                            data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
                            data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(),
                            data2.stream().map(SimpleMembreInOutData::getEmail).filter(o -> o != null && !o.isBlank())
                                    .toList());
                })
                .call(Unchecked.function(membres -> {
                    for (MembreModel membreModel : membres) {
                        if (!Objects.equals(membreModel.getClub(), clubModel.get())) {
                            LOGGER.info("Similar membres found: " + membreModel);
                            throw new DForbiddenException(String.format(trad.t("le.membre.appartient.pas.a.votre.club"),
                                    membreModel.getLicence()));
                        }
                    }
                    Uni<Void> uniResult = Uni.createFrom().voidItem();
                    for (SimpleMembreInOutData dataIn : data2) {
                        MembreModel model = membres.stream()
                                .filter(m -> (dataIn.getLicence() != null && Objects.equals(m.getLicence(),
                                        dataIn.getLicence())) || m.getLname().equals(dataIn.getNom()) && m.getFname()
                                        .equals(dataIn.getPrenom()) || (dataIn.getEmail() != null && !dataIn.getEmail()
                                        .isBlank() && Objects.equals(m.getFname(), dataIn.getEmail()))).findFirst()
                                .orElseGet(() -> {
                                    MembreModel mm = new MembreModel();
                                    mm.setClub(clubModel.get());
                                    mm.setLicences(new ArrayList<>());
                                    mm.setCountry("FR");
                                    return mm;
                                });
                        if (model.getId() != null) {
                            LOGGER.debugf("updating -> %s", dataIn.toString());
                        } else {
                            LOGGER.debugf("creating -> %s", dataIn.toString());
                        }

                        if (model.getEmail() != null && !model.getEmail().isBlank()) {
                            if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
                                LOGGER.info("Similar membres found: " + model);
                                throw new DBadRequestException(
                                        String.format(trad.t("email.deja.utilise.par"), model.getEmail(),
                                                model.getFname(), model.getLname()));
                            }

                            if (StringSimilarity.similarity(model.getLname().toUpperCase(),
                                    dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
                                    model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
                                LOGGER.info("Similar membres found: " + model);
                                throw new DBadRequestException(
                                        String.format(trad.t("email.deja.utilise.par"), model.getEmail(),
                                                model.getFname(), model.getLname()));
                            }
                        }

                        boolean add = model.getId() == null;

                        if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(),
                                dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity(
                                model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
                            LOGGER.info("Similar membres found: " + model);
                            throw new DBadRequestException(
                                    String.format(trad.t("try.edit.licence"), model.getLicence(), model.getFname(),
                                            model.getFname()));
                        }

                        ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model);
                        ls.logChange("Prénom", model.getFname(),
                                dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1), model);

                        model.setLname(dataIn.getNom().toUpperCase());
                        model.setFname(Utils.formatPrenom(dataIn.getPrenom()));

                        if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank()) {
                            ls.logChange("Email", model.getEmail(), dataIn.getEmail(), model);
                            model.setEmail(dataIn.getEmail());
                        }
                        model.setGenre(Genre.fromString(dataIn.getGenre()));
                        if (dataIn.getBirthdate() != null) {
                            if (model.getBirth_date() == null || !Objects.equals(model.getBirth_date().getTime(),
                                    dataIn.getBirthdate().getTime()))
                                ls.logChange("Date de naissance", model.getBirth_date(), dataIn.getBirthdate(), model);
                            model.setBirth_date(dataIn.getBirthdate());
                            model.setCategorie(Utils.getCategoryFormBirthDate(model.getBirth_date(), new Date()));
                        }

                        uniResult = uniResult
                                .call(() -> Panache.withTransaction(() -> repository.persist(model)
                                        .chain(membreModel1 -> dataIn.isLicenceCurrent() ? licenceRepository.find(
                                                        "membre.id = ?1 AND saison = ?2", membreModel1.getId(),
                                                        Utils.getSaison())
                                                .firstResult()
                                                .call(l -> {
                                                    if (l == null) {
                                                        l = new LicenceModel();
                                                        l.setMembre(membreModel1);
                                                        l.setClub_id(clubModel.get().getId());
                                                        l.setValidate(false);
                                                        l.setSaison(Utils.getSaison());
                                                    }
                                                    l.setCertificate(dataIn.getCertif());
                                                    return licenceRepository.persist(l);
                                                }) : licenceRepository.delete(
                                                "membre = ?1 AND saison = ?2 AND validate = false", membreModel1,
                                                Utils.getSaison()))));
                        if (add)
                            uniResult = uniResult.call(() -> ls.logAAdd(model));
                        else
                            uniResult = uniResult.call(() -> ls.append());
                    }
                    return uniResult;
                }))
                .map(__ -> "OK");
    }

    public Uni<MembreModel> getById(long id) {
        return repository.findById(id);
    }

    public Uni<MembreModel> getByIdWithLicence(long id) {
        return repository.findById(id)
                .call(m -> Mutiny.fetch(m.getLicences()));
    }

    public Uni<MembreModel> getByAccountId(String subject) {
        return repository.find("userId = ?1", subject).firstResult();
    }

    public Uni<MembreModel> getByLicence(long licence) {
        return repository.find("licence = ?1", licence).firstResult();
    }

    public Uni<String> update(long id, FullMemberForm membre) {
        return update(repository.findById(id)
                .call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
                        .invoke(Unchecked.consumer(c -> {
                            if (c > 0 && !membre.getEmail().isBlank())
                                throw new DBadRequestException(trad.t("email.deja.utilise"));
                        })))
                .chain(membreModel -> clubRepository.findById(membre.getClub())
                        .map(club -> new Pair<>(membreModel, club)))
                .onItem().transform(pair -> {
                    MembreModel m = pair.getKey();

                    ls.logChange("Rôle", m.getRole(), membre.getRole(), m);
                    m.setRole(membre.getRole());
                    ls.logChange("Club", m.getClub(), pair.getValue(), m);
                    m.setClub(pair.getValue());
                    ls.logChange("Grade d'arbitrage", m.getGrade_arbitrage(), membre.getGrade_arbitrage(), m);
                    m.setGrade_arbitrage(membre.getGrade_arbitrage());
                    return m;
                }), membre, true);
    }

    public Uni<String> update(long id, FullMemberForm membre, SecurityCtx securityCtx) {
        return update(repository.findById(id)
                .call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
                        .invoke(Unchecked.consumer(c -> {
                            if (c > 0 && !membre.getEmail().isBlank())
                                throw new DBadRequestException(trad.t("email.deja.utilise"));
                        })))
                .invoke(Unchecked.consumer(membreModel -> {
                    if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
                        throw new DForbiddenException();
                    if (StringSimilarity.similarity(membreModel.getLname().toUpperCase(),
                            membre.getLname().toUpperCase()) > 3 || StringSimilarity.similarity(
                            membreModel.getFname().toUpperCase(), membre.getFname().toUpperCase()) > 3) {
                        throw new DBadRequestException(
                                trad.t("regiter.new.membre"));
                    }
                }))
                .invoke(Unchecked.consumer(membreModel -> {
                    RoleAsso source = RoleAsso.MEMBRE;
                    if (securityCtx.roleHas("club_president")) source = RoleAsso.PRESIDENT;
                    else if (securityCtx.roleHas("club_secretaire")) source = RoleAsso.SECRETAIRE;
                    else if (securityCtx.roleHas("club_respo_intra")) source = RoleAsso.MEMBREBUREAU;
                    if (!membre.getRole().equals(membreModel.getRole()) && membre.getRole().level >= source.level)
                        throw new DForbiddenException(trad.t("permission.insuffisante"));
                }))
                .onItem().transform(target -> {
                    if (!securityCtx.getSubject().equals(target.getUserId())) {
                        ls.logChange("Rôle", target.getRole(), membre.getRole(), target);
                        target.setRole(membre.getRole());
                    }
                    return target;
                }), membre, false);
    }

    private Uni<String> update(Uni<MembreModel> uni, FullMemberForm membre, boolean admin) {
        return uni.chain(target -> {
                    ls.logChange("Prénom", target.getFname(), membre.getFname(), target);
                    target.setFname(Utils.formatPrenom(membre.getFname()));
                    ls.logChange("Nom", target.getLname(), membre.getLname(), target);
                    target.setLname(membre.getLname().toUpperCase());
                    ls.logChange("Pays", target.getCountry(), membre.getCountry(), target);
                    target.setCountry(membre.getCountry());
                    if (membre.getBirth_date() != null && (target.getBirth_date() == null || !Objects.equals(
                            target.getBirth_date().getTime(), membre.getBirth_date().getTime()))) {
                        ls.logChange("Date de naissance", target.getBirth_date(), membre.getBirth_date(), target);
                        target.setBirth_date(membre.getBirth_date());
                        target.setCategorie(Utils.getCategoryFormBirthDate(membre.getBirth_date(), new Date()));
                    }
                    ls.logChange("Genre", target.getGenre(), membre.getGenre(), target);
                    target.setGenre(membre.getGenre());
                    ls.logChange("Email", target.getEmail(), membre.getEmail(), target);
                    target.setEmail(membre.getEmail());

                    return Panache.withTransaction(() -> repository.persist(target)).call(() -> ls.append());
                })
                .invoke(membreModel -> SReqComb.sendIfNeed(serverCustom.clients,
                        SimpleCombModel.fromModel(membreModel)))
                .call(membreModel -> (admin && membreModel.getUserId() != null) ?
                        ((membreModel.getClub() != null) ?
                                keycloakService.setClubGroupMembre(membreModel, membreModel.getClub()) :
                                keycloakService.clearUser(membreModel.getUserId()))
                        : Uni.createFrom().nullItem())
                .call(membreModel -> (membreModel.getUserId() != null) ?
                        keycloakService.setAutoRoleMembre(membreModel.getUserId(), membreModel.getRole(),
                                membreModel.getGrade_arbitrage()) : Uni.createFrom().nullItem())
                .call(membreModel -> (membreModel.getUserId() != null) ?
                        keycloakService.setEmail(membreModel.getUserId(), membreModel.getEmail()) : Uni.createFrom()
                        .nullItem())
                .call(membreModel -> {
                    Calendar calendar = Calendar.getInstance();
                    calendar.add(Calendar.DAY_OF_YEAR, -7);
                    Date dateLimit = calendar.getTime();

                    return competitionRepository.list("date > ?1", dateLimit)
                            .call(l -> l.isEmpty() ? Uni.createFrom().nullItem() :
                                    Uni.join().all(l.stream().map(competitionModel ->
                                            registerRepository.update(
                                                    "categorie = ?1, club = ?2 where competition = ?3 AND membre = ?4",
                                                    membreModel.getCategorie(), membreModel.getClub(), competitionModel,
                                                    membreModel)
                                    ).toList()).andFailFast());
                })
                .call(membreModel -> licenceRepository.update("club_id = ?1 where membre = ?2 AND saison = ?3",
                        (membreModel.getClub() == null) ? null : membreModel.getClub().getId(), membreModel,
                        Utils.getSaison()))
                .call(membreModel -> (membre.getPhoto_data() != null && membre.getPhoto_data()
                        .size() > 0) ? ls.logAUpdate("Photo",
                        membreModel) : Uni.createFrom().nullItem())
                .map(__ -> "OK");
    }


    public Uni<Long> add(FullMemberForm input) {
        return clubRepository.findById(input.getClub())
                .call(__ -> repository.count("email LIKE ?1", input.getEmail())
                        .invoke(Unchecked.consumer(c -> {
                            if (c > 0 && input.getEmail() != null && !input.getEmail().isBlank())
                                throw new DBadRequestException(trad.t("email.deja.utilise"));
                        })))
                .chain(clubModel -> {
                    MembreModel model = getMembreModel(input, clubModel);
                    return Panache.withTransaction(() -> repository.persist(model));
                })
                .call(membreModel -> ls.logAAdd(membreModel))
                .invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients,
                        SimpleCombModel.fromModel(membreModel)))
                .map(MembreModel::getId);
    }

    public Uni<Long> add(FullMemberForm input, String subject) {
        return repository.find("userId = ?1", subject).firstResult()
                .call(__ -> repository.count("email LIKE ?1", input.getEmail())
                        .invoke(Unchecked.consumer(c -> {
                            if (c > 0) throw new DBadRequestException(trad.t("email.deja.utilise"));
                        })))
                .call(membreModel ->
                        repository.count(
                                        "unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2) AND club = ?3",
                                        input.getLname(), input.getFname(), membreModel.getClub())
                                .invoke(Unchecked.consumer(c -> {
                                    if (c > 0)
                                        throw new DBadRequestException(trad.t("membre.deja.existent"));
                                })))
                .chain(membreModel -> {
                    MembreModel model = getMembreModel(input, membreModel.getClub());
                    model.setRole(RoleAsso.MEMBRE);
                    model.setGrade_arbitrage(GradeArbitrage.NA);
                    return Panache.withTransaction(() -> repository.persist(model));
                })
                .call(membreModel -> ls.logAAdd(membreModel))
                .invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients,
                        SimpleCombModel.fromModel(membreModel)))
                .map(MembreModel::getId);
    }

    public Uni<String> delete(long id) {
        return repository.findById(id)
                .call(membreModel -> (membreModel.getUserId() != null) ?
                        keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
                .call(membreModel -> ls.logADelete(membreModel))
                .call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
                .invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
                .map(__ -> "Ok");
    }

    public Uni<String> delete(long id, SecurityCtx securityCtx) {
        return repository.findById(id)
                .invoke(Unchecked.consumer(membreModel -> {
                    if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
                        throw new DForbiddenException();
                }))
                .invoke(Unchecked.consumer(membreModel -> {
                    if (membreModel.getLicence() != null) {
                        throw new DBadRequestException(
                                trad.t("membre.rm.err1"));
                    }
                }))
                .call(membreModel -> licenceRepository.find("membre = ?1", membreModel).count()
                        .invoke(Unchecked.consumer(l -> {
                            if (l > 0)
                                throw new DBadRequestException(trad.t("membre.rm.err2"));
                        })))
                .call(membreModel -> (membreModel.getUserId() != null) ?
                        keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
                .call(membreModel -> ls.logADelete(membreModel))
                .call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
                .invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
                .call(__ -> Utils.deleteMedia(id, media, "ppMembre"))
                .map(__ -> "Ok");
    }

    public Uni<?> setUserId(Long id, String id1) {
        return repository.findById(id).chain(membreModel -> {
            ls.logChange("KC UUID", membreModel.getUserId(), id1, membreModel);
            membreModel.setUserId(id1);
            return Panache.withTransaction(() -> repository.persist(membreModel))
                    .call(() -> ls.append());
        });
    }

    private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) {
        MembreModel model = new MembreModel();
        model.setFname(Utils.formatPrenom(input.getFname()));
        model.setLname(input.getLname().toUpperCase());
        model.setEmail(input.getEmail());
        model.setLicence(null);
        model.setGenre(input.getGenre());
        model.setCountry(input.getCountry());
        model.setBirth_date(input.getBirth_date());
        model.setCategorie(Utils.getCategoryFormBirthDate(input.getBirth_date(), new Date()));
        model.setClub(clubModel);
        model.setRole(input.getRole());
        model.setGrade_arbitrage(input.getGrade_arbitrage());
        return model;
    }

    public Uni<List<SimpleMembre>> getSimilar(String fname, String lname) {
        return repository.listAll().map(membreModels -> membreModels.stream()
                .filter(m -> StringSimilarity.similarity(m.getFname(), fname) <= 3 &&
                        StringSimilarity.similarity(m.getLname(), lname) <= 3)
                .map(SimpleMembre::fromModel).toList());
    }

    public Uni<MeData> getMembre(String subject) {
        MeData meData = new MeData();
        return repository.find("userId = ?1", subject).firstResult()
                .invoke(m -> meData.setMembre(m, trad))
                .call(membreModel -> Mutiny.fetch(membreModel.getLicences())
                        .map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
                        .invoke(meData::setLicences))
                .call(membreModel -> Mutiny.fetch(membreModel.getSelections())
                        .map(licences -> licences.stream().map(SimpleSelection::fromModel).toList())
                        .invoke(meData::setSelections))
                .map(__ -> meData);
    }

    public Uni<Void> updateSettings(String subject, UserSettingForm settingForm) {
        return repository.find("userId = ?1", subject).firstResult()
                .invoke(Unchecked.consumer(membreModel -> {
                    if (settingForm.getResultPrivacy() != null) {
                        ls.logChange("Confidentialité des résultats", membreModel.getResultPrivacy(),
                                settingForm.getResultPrivacy(), membreModel);
                        membreModel.setResultPrivacy(settingForm.getResultPrivacy());
                    }
                }))
                .call(membreModel -> Panache.withTransaction(() -> repository.persist(membreModel)))
                .call(membreModel -> ls.append())
                .replaceWithVoid();
    }

    @Scheduled(cron = "0 0 1 1 9 ?")
    Uni<Void> everySeason() {
        return repository.list("birth_date IS NOT NULL")
                .chain(l -> Uni.join().all(l.stream().map(m -> {
                    m.setCategorie(Utils.getCategoryFormBirthDate(m.getBirth_date(), new Date()));
                    return Panache.withTransaction(() -> repository.persist(m));
                }).toList()).andCollectFailures())
                .map(__ -> null);
    }
}
