package fr.titionfire.ffsaf.domain.service;

import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.data.CategoryData;
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
import fr.titionfire.ffsaf.rest.data.TreeData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

@WithSession
@ApplicationScoped
public class CategoryService {

    @Inject
    CategoryRepository repository;

    @Inject
    CompetitionRepository competRepository;

    @Inject
    MatchRepository matchRepository;

    @Inject
    TreeRepository treeRepository;

    @Inject
    CombRepository combRepository;

    @Inject
    CompetPermService permService;

    @Inject
    CompetitionGuestRepository competitionGuestRepository;

    @Inject
    TradService trad;

    public Uni<CategoryData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
        return repository.find("systemId = ?1 AND system = ?2", id, system)
                .firstResult()
                .onItem().ifNull().failWith(() -> new RuntimeException(trad.t("categorie.non.trouver")))
                .call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
                .map(CategoryData::fromModel);
    }

    public Uni<List<CategoryData>> getAllAdmin(SecurityCtx securityCtx, CompetitionSystem system) {
        return permService.getAllHaveAdminAccess(securityCtx)
                .chain(ids -> repository.list("system = ?1 AND compet.id IN ?2", system, ids))
                .map(pouleModels -> pouleModels.stream().map(CategoryData::fromModel).toList());
    }

    public Uni<CategoryData> addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, CategoryData data) {
        return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult()
                .chain(o -> {
                    if (o == null) {
                        return competRepository.findById(data.getCompet())
                                .onItem().ifNull().failWith(() -> new RuntimeException(trad.t("competition.not.found")))
                                .call(o2 -> permService.hasEditPerm(securityCtx, o2))
                                .chain(competitionModel -> {
                                    CategoryModel model = new CategoryModel();

                                    model.setId(null);
                                    model.setSystem(system);
                                    model.setSystemId(data.getId());
                                    model.setCompet(competitionModel);
                                    model.setName(data.getName());
                                    model.setMatchs(new ArrayList<>());
                                    model.setTree(new ArrayList<>());
                                    model.setType(data.getType());

                                    return Panache.withTransaction(() -> repository.persist(model));
                                });
                    } else {
                        o.setName(data.getName());
                        o.setType(data.getType());
                        return Panache.withTransaction(() -> repository.persist(o));
                    }
                }).map(CategoryData::fromModel);
    }

    private MatchModel findMatch(List<MatchModel> matchModelList, Long id) {
        return matchModelList.stream().filter(m -> m.getSystemId().equals(id))
                .findFirst().orElse(null);
    }

    private TreeModel findNode(List<TreeModel> node, Long match_id) {
        return node.stream().filter(m -> m.getMatch().getSystemId().equals(match_id))
                .findFirst().orElse(null);
    }

    private void flatTreeChild(TreeModel current, ArrayList<TreeModel> node) {
        if (current != null) {
            node.add(current);
            flatTreeChild(current.getLeft(), node);
            flatTreeChild(current.getRight(), node);
        }
    }

    private void flatTreeChild(TreeData current, ArrayList<TreeData> node) {
        if (current != null) {
            node.add(current);
            flatTreeChild(current.getLeft(), node);
            flatTreeChild(current.getRight(), node);
        }
    }

    private Uni<TreeModel> persisteTree(TreeData data, List<TreeModel> node, CategoryModel poule,
                                        List<MatchModel> matchModelList) {
        TreeModel mm = findNode(node, data.getMatch());
        if (mm == null) {
            mm = new TreeModel();
            mm.setId(null);
        }
        mm.setLevel(data.getLevel());
        mm.setCategory(poule.getId());
        mm.setMatch(findMatch(matchModelList, data.getMatch()));

        return Uni.createFrom().item(mm)
                .call(o -> (data.getLeft() == null ? Uni.createFrom().nullItem().invoke(o1 -> o.setLeft(null)) :
                        persisteTree(data.getLeft(), node, poule, matchModelList).invoke(o::setLeft)))
                .call(o -> (data.getRight() == null ? Uni.createFrom().nullItem().invoke(o1 -> o.setRight(null)) :
                        persisteTree(data.getRight(), node, poule, matchModelList).invoke(o::setRight)))
                .chain(o -> Panache.withTransaction(() -> treeRepository.persist(o)));
    }

    public Uni<?> syncCategory(SecurityCtx securityCtx, CompetitionSystem system, CategoryFullData data) {
        return repository.find("systemId = ?1 AND system = ?2", data.getId(), system)
                .firstResult()
                .onItem().ifNotNull().call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet()))
                .onItem().ifNull().switchTo(
                        () -> competRepository.findById(data.getCompet())
                                .onItem().ifNull().failWith(() -> new RuntimeException(trad.t("competition.not.found")))
                                .call(o -> permService.hasEditPerm(securityCtx, o))
                                .map(o -> {
                                    CategoryModel model = new CategoryModel();
                                    model.setId(null);
                                    model.setSystem(system);
                                    model.setSystemId(data.getId());
                                    model.setMatchs(new ArrayList<>());
                                    model.setTree(new ArrayList<>());
                                    model.setCompet(o);
                                    return model;
                                }))
                .call(o -> Mutiny.fetch(o.getMatchs()))
                .call(o -> Mutiny.fetch(o.getTree()))
                .map(o -> {
                    o.setName(data.getName());
                    o.setType(data.getType());

                    WorkData workData = new WorkData();
                    workData.category = o;
                    return workData;
                })
                .call(o -> Panache.withTransaction(() -> repository.persist(o.category)))
                .call(o -> (data.getMatches() == null || data.getMatches().isEmpty()) ? Uni.createFrom().nullItem() :
                        Uni.createFrom()
                                .item(data.getMatches().stream().flatMap(m -> Stream.of(m.getC1_id(), m.getC2_id())
                                        .filter(Objects::nonNull)).distinct().toList())
                                .chain(ids -> ids.isEmpty() ? Uni.createFrom().nullItem()
                                        : combRepository.list("id IN ?1", ids)
                                        .invoke(o2 -> o2.forEach(m -> o.membres.put(m.getId(), m)))
                                )
                )
                .call(o -> Mutiny.fetch(o.category.getCompet().getGuests())
                        .invoke(o2 -> o2.forEach(m -> o.guest.put(m.getFname() + " " + m.getLname(), m)))
                        .map(o2 -> data.getMatches().stream().flatMap(m -> Stream.of(m.getC1_str(), m.getC2_str())
                                .filter(Objects::nonNull)).distinct().filter(s -> !o.guest.containsKey(s)).map(
                                CompetitionGuestModel::new).toList())
                        .call(o3 -> o3.isEmpty() ? Uni.createFrom().nullItem() :
                                Uni.join().all(o3.stream().map(o4 -> competitionGuestRepository.persist(o4)).toList())
                                        .andFailFast())
                        .invoke(o2 -> o2.forEach(m -> o.guest.put(m.getFname() + " " + m.getLname(), m))))
                .invoke(in -> {
                    ArrayList<TreeModel> node = new ArrayList<>();
                    for (TreeModel treeModel : in.category.getTree())
                        flatTreeChild(treeModel, node);

                    ArrayList<TreeData> new_node = new ArrayList<>();
                    for (TreeData treeModel : data.getTrees())
                        flatTreeChild(treeModel, new_node);

                    in.toRmNode = node.stream().filter(m -> new_node.stream()
                                    .noneMatch(m2 -> m2.getMatch().equals(m.getMatch().getSystemId())))
                            .map(TreeModel::getId).toList();

                    in.unlinkNode = node;
                    in.unlinkNode.forEach(n -> {
                        n.setRight(null);
                        n.setLeft(null);
                    });

                    in.toRmMatch = in.category.getMatchs().stream()
                            .filter(m -> data.getMatches().stream().noneMatch(m2 -> m2.getId().equals(m.getSystemId())))
                            .map(MatchModel::getId).toList();
                })
                .call(in -> in.unlinkNode.isEmpty() ? Uni.createFrom().nullItem() :
                        Panache.withTransaction(() -> treeRepository.persist(in.unlinkNode)))
                .call(in -> in.toRmNode.isEmpty() ? Uni.createFrom().nullItem() :
                        Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in.toRmNode)))
                .call(in -> in.toRmMatch.isEmpty() ? Uni.createFrom().nullItem() :
                        Panache.withTransaction(() -> Uni.join().all(
                                        in.toRmMatch.stream().map(l -> matchRepository.deleteById(l)).toList())
                                .andCollectFailures()))
                .call(in -> data.getMatches().isEmpty() ? Uni.createFrom().nullItem() :
                        Uni.join().all(
                                        data.getMatches().stream().map(m -> {
                                            MatchModel mm = findMatch(in.category.getMatchs(), m.getId());
                                            if (mm == null) {
                                                mm = new MatchModel();
                                                mm.setId(null);
                                                mm.setSystem(system);
                                                mm.setSystemId(m.getId());
                                            }
                                            mm.setCategory(in.category);
                                            mm.setCategory_ord(m.getCategory_ord());
                                            mm.setC1_guest(in.guest.getOrDefault(m.getC1_str(), null));
                                            mm.setC2_guest(in.guest.getOrDefault(m.getC2_str(), null));
                                            mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null));
                                            mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null));
                                            mm.setEnd(m.isEnd());
                                            mm.setPoule(m.getPoule());
                                            mm.getScores().clear();
                                            mm.getScores().addAll(m.getScores());

                                            MatchModel finalMm = mm;
                                            return Panache.withTransaction(() -> matchRepository.persist(finalMm)
                                                    .invoke(o -> in.match.add(o)));
                                        }).toList())
                                .andCollectFailures())
                .call(in -> data.getTrees().isEmpty() ? Uni.createFrom().nullItem() :
                        Uni.join().all(data.getTrees().stream()
                                        .map(m -> persisteTree(m, in.category.getTree(), in.category, in.match)).toList())
                                .andCollectFailures())
                .map(__ -> "OK");
    }

    private static class WorkData {
        CategoryModel category;
        HashMap<Long, MembreModel> membres = new HashMap<>();
        HashMap<String, CompetitionGuestModel> guest = new HashMap<>();
        List<MatchModel> match = new ArrayList<>();
        List<Long> toRmMatch;
        List<TreeModel> unlinkNode;
        List<Long> toRmNode;
    }

    public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
        return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
                .onItem().ifNull().failWith(() -> new RuntimeException(trad.t("categorie.non.trouver")))
                .call(o -> permService.hasEditPerm(securityCtx, o.getCompet()))
                .call(o -> Mutiny.fetch(o.getTree())
                        .call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :
                                Uni.createFrom().item(o2.stream().peek(m -> {
                                            m.setRight(null);
                                            m.setLeft(null);
                                        }).toList())
                                        .call(in -> Panache.withTransaction(() -> treeRepository.persist(in)))
                                        .map(in -> in.stream().map(TreeModel::getId).toList())
                                        .call(in -> in.isEmpty() ? Uni.createFrom().nullItem() :
                                                Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in)))
                        )
                )
                .call(o -> matchRepository.delete("category.id = ?1", o.getId()))
                .chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())));
    }
}
