package fr.titionfire.ffsaf.ws.recv;

import fr.titionfire.ffsaf.data.model.CategoryModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.model.TreeModel;
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.data.repository.TreeRepository;
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
import fr.titionfire.ffsaf.domain.entity.TreeEntity;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.TreeNode;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import fr.titionfire.ffsaf.ws.PermLevel;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.Data;
import org.hibernate.reactive.mutiny.Mutiny;

import java.util.ArrayList;
import java.util.List;

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

    @Inject
    CategoryRepository categoryRepository;

    @Inject
    CompetitionRepository competitionRepository;

    @Inject
    MatchRepository matchRepository;

    @Inject
    TreeRepository treeRepository;

    private Uni<CategoryModel> getById(long id, WebSocketConnection connection) {
        return categoryRepository.findById(id)
                .invoke(Unchecked.consumer(o -> {
                    if (o == null)
                        throw new DNotFoundException("Catégorie non trouver");
                    if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
                        throw new DForbiddenException("Permission denied");
                }));
    }

    @WSReceiver(code = "getAllCategory", permission = PermLevel.VIEW)
    public Uni<List<JustCategorie>> getAllCategory(WebSocketConnection connection, Object o) {
        return categoryRepository.list("compet.uuid", connection.pathParam("uuid"))
                .map(category -> category.stream().map(JustCategorie::from).toList());
    }

    @WSReceiver(code = "getFullCategory", permission = PermLevel.VIEW)
    public Uni<FullCategory> getFullCategory(WebSocketConnection connection, Long id) {
        FullCategory fullCategory = new FullCategory();

        return getById(id, connection)
                .invoke(cat -> {
                    fullCategory.setId(cat.getId());
                    fullCategory.setName(cat.getName());
                    fullCategory.setLiceName(cat.getLiceName());
                    fullCategory.setType(cat.getType());
                })
                .call(cat -> Mutiny.fetch(cat.getMatchs())
                        .map(matchModels -> matchModels.stream().filter(o -> o.getCategory_ord() >= 0)
                                .map(MatchEntity::fromModel).toList())
                        .invoke(fullCategory::setMatches))
                .call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
                        .map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
                        .invoke(fullCategory::setTrees))
                .map(__ -> fullCategory);
    }

    @WSReceiver(code = "createCategory", permission = PermLevel.ADMIN)
    public Uni<Long> createCategory(WebSocketConnection connection, JustCategorie categorie) {
        return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
                .chain(cm -> {
                    CategoryModel categoryModel = new CategoryModel();
                    categoryModel.setName(categorie.name);
                    categoryModel.setCompet(cm);
                    categoryModel.setMatchs(new ArrayList<>());
                    categoryModel.setTree(new ArrayList<>());
                    categoryModel.setType(categorie.type);
                    categoryModel.setLiceName(categorie.liceName);

                    return categoryRepository.create(categoryModel);
                })
                .call(cat -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory",
                        JustCategorie.from(cat)))
                .map(CategoryModel::getId);
    }

    @WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN)
    public Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie) {
        return getById(categorie.id, connection)
                .chain(cat -> {
                    cat.setName(categorie.name);
                    cat.setLiceName(categorie.liceName);
                    cat.setType(categorie.type);
                    return Panache.withTransaction(() -> categoryRepository.persist(cat));
                })
                .call(cat -> {
                    Uni<Long> uni = Uni.createFrom().nullItem();
                    if ((categorie.type() & 1) == 0)
                        uni = uni.chain(__ -> matchRepository.delete("category = ?1 AND category_ord >= 0", cat));
                    if ((categorie.type() & 2) == 0) {
                        uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId()))
                                .chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat));
                    }
                    return uni;
                })
                .call(cat -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", JustCategorie.from(cat)))
                .replaceWithVoid();
    }

    private Uni<TreeModel> getOrCreateTreeNode(Long id, Long categorieId, int level) {
        if (id == null) {
            TreeModel treeModel = new TreeModel();
            treeModel.setCategory(categorieId);
            treeModel.setLevel(level);

            return Panache.withTransaction(() -> treeRepository.persistAndFlush(treeModel));
        } else {
            return Panache.withTransaction(
                    () -> treeRepository.find("match.id = ?1", id).firstResult()
                            .invoke(t -> t.setLevel(level))
                            .chain(t -> treeRepository.persist(t)));
        }
    }

    private Uni<Void> updateNode(TreeNode<Long> current, TreeModel currentTreeModel, CategoryModel category) {
        return Uni.createFrom().item(currentTreeModel)
                .chain(t -> {
                    if (current.getData() != null)
                        return Uni.createFrom().item(t);

                    MatchModel matchModel = new MatchModel();
                    matchModel.setCategory(category);
                    matchModel.setCategory_ord(-42);
                    matchModel.setEnd(false);

                    return matchRepository.create(matchModel).onItem()
                            .invoke(t::setMatch)
                            .chain(__ -> treeRepository.persistAndFlush(t));
                })
                .call(t -> {
                    if (current.getLeft() == null)
                        return Uni.createFrom().item(t);

                    return getOrCreateTreeNode(current.getLeft().getData(), category.getId(), 0)
                            .invoke(t::setLeft)
                            .call(treeModel -> updateNode(current.getLeft(), treeModel, category));
                })
                .call(t -> {
                    if (current.getRight() == null)
                        return Uni.createFrom().item(t);

                    return getOrCreateTreeNode(current.getRight().getData(), category.getId(), 0)
                            .invoke(t::setRight)
                            .call(treeModel -> updateNode(current.getRight(), treeModel, category));
                })
                .chain(t -> treeRepository.persist(t))
                .replaceWithVoid();
    }


    @WSReceiver(code = "updateTrees", permission = PermLevel.ADMIN)
    public Uni<Void> updateTrees(WebSocketConnection connection, TreeUpdate data) {
        return getById(data.categoryId, connection)
                .call(cat -> treeRepository.update("level = -1, left = NULL, right = NULL WHERE category = ?1",
                        cat.getId()))
                .call(cat -> {
                    Uni<?> uni = Uni.createFrom().voidItem();

                    for (int i = 0; i < data.trees().size(); i++) {
                        TreeNode<Long> current = data.trees().get(i);

                        int finalI = i;
                        uni = uni.chain(() -> getOrCreateTreeNode(current.getData(), cat.getId(), finalI + 1)
                                .call(treeModel -> updateNode(current, treeModel, cat)));

                    }
                    Uni<?> finalUni = uni;
                    return Panache.withTransaction(() -> finalUni);
                })
                .call(cat -> treeRepository.list("category = ?1 AND level = -1", cat.getId())
                        .map(l -> l.stream().map(o -> o.getMatch().getId()).toList())
                        .call(__ -> treeRepository.delete("category = ?1 AND level = -1", cat.getId()))
                        .call(ids -> matchRepository.delete("id IN ?1", ids)))
                .call(__ -> treeRepository.flush())
                .call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
                        .map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
                        .chain(trees -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", trees)))
                .replaceWithVoid();
    }

    @RegisterForReflection
    public record JustCategorie(long id, String name, int type, String liceName) {
        static JustCategorie from(CategoryModel m) {
            return new JustCategorie(m.getId(), m.getName(), m.getType(), m.getLiceName());
        }
    }

    @RegisterForReflection
    public record TreeUpdate(long categoryId, List<TreeNode<Long>> trees) {
    }

    @Data
    @RegisterForReflection
    public static class FullCategory {
        long id;
        String name;
        int type;
        String liceName;
        List<TreeEntity> trees = null;
        List<MatchEntity> matches;
    }
}
