package fr.titionfire.ffsaf.domain.service;

import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.RegisterRepository;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.RegisterMode;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@ApplicationScoped
public class CompetPermService {

    @Inject
    ServerCustom serverCustom;

    @Inject
    CompetitionRepository competitionRepository;

    @Inject
    @CacheName("safca-config")
    Cache cache;

    @Inject
    @CacheName("safca-have-access")
    Cache cacheAccess;

    @Inject
    @CacheName("have-access")
    Cache cacheNoneAccess;
    @Inject
    RegisterRepository registerRepository;


    public Uni<SimpleCompet> getSafcaConfig(long id) {
        return cache.get(id, k -> {
            CompletableFuture<SimpleCompet> f = new CompletableFuture<>();
            SReqCompet.getConfig(serverCustom.clients, id, f);
            try {
                return f.get(500, TimeUnit.MILLISECONDS);
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                throw new RuntimeException(e);
            }
        });
    }

    public Uni<List<Long>> getAllHaveAdminAccess(SecurityCtx securityCtx) {
        ArrayList<Long> out = new ArrayList<>();

        Uni<HashMap<Long, String>> safca = cacheAccess.getAsync(securityCtx.getSubject(),
                        k -> competitionRepository.list("system = ?1", CompetitionSystem.SAFCA)
                                .chain(competitionModels -> {
                                    CompletableFuture<HashMap<String, String>> f = new CompletableFuture<>();
                                    SReqCompet.getAllHaveAccess(serverCustom.clients, securityCtx.getSubject(), f);
                                    return Uni.createFrom().future(f, Duration.ofMillis(500))
                                            .onFailure().recoverWithItem(new HashMap<>())
                                            .map(map_ -> {
                                                HashMap<Long, String> map = new HashMap<>();
                                                map_.forEach((key, value) -> map.put(Long.parseLong(key), value));

                                                for (CompetitionModel model : competitionModels) {
                                                    if (model.getOwner().equals(securityCtx.getSubject()))
                                                        map.putIfAbsent(model.getId(), "owner");
                                                    else if (securityCtx.roleHas("federation_admin")
                                                            || securityCtx.roleHas("safca_super_admin"))
                                                        map.putIfAbsent(model.getId(), "admin");
                                                }
                                                return map;
                                            });
                                }))
                .onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));

        Uni<HashMap<Long, String>> none = cacheNoneAccess.getAsync(securityCtx.getSubject(),
                k -> competitionRepository.list("system = ?1", CompetitionSystem.INTERNAL)
                        .map(competitionModels -> {
                            HashMap<Long, String> map = new HashMap<>();
                            for (CompetitionModel model : competitionModels) {
                                if (model.getOwner().equals(securityCtx.getSubject()))
                                    map.putIfAbsent(model.getId(), "owner");
                                else if (securityCtx.roleHas("federation_admin"))
                                    map.putIfAbsent(model.getId(), "admin");
                                else if (securityCtx.isInClubGroup(
                                        model.getClub().getId()) && (securityCtx.isClubAdmin()))
                                    map.putIfAbsent(model.getId(), "admin");
                                else if (model.getAdmin().contains(securityCtx.getSubject()))
                                    map.putIfAbsent(model.getId(), "admin");
                                else if (model.getTable().contains(securityCtx.getSubject()))
                                    map.putIfAbsent(model.getId(), "table");

                            }
                            return map;
                        }));

        return safca.invoke(map ->
                        map.forEach((k, v) -> {
                            if (v.equals("owner") || v.equals("admin"))
                                out.add(k);
                        })
                )
                .call(__ -> none.invoke(map ->
                        map.forEach((k, v) -> {
                            if (v.equals("owner") || v.equals("admin"))
                                out.add(k);
                        })
                ))
                .map(__ -> out.stream().distinct().toList());
    }


    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm
     */
    public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
        return hasViewPerm(securityCtx, Uni.createFrom().item(competitionModel));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm
     */
    public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, long id) {
        return hasViewPerm(securityCtx, competitionRepository.findById(id));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm
     */
    public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
        return in.call(cm -> (cm.isPublicVisible() || cm.getRegisterMode() == RegisterMode.FREE
                || cm.getRegisterMode() == RegisterMode.HELLOASSO
                || (cm.getRegisterMode() == RegisterMode.CLUB_ADMIN && securityCtx.isClubAdmin())) ?
                Uni.createFrom().nullItem() :
                hasAdminViewPerm(securityCtx, cm).onFailure()
                        .recoverWithUni(__ ->
                                registerRepository.count("membre.userId = ?1 AND competition = ?2",
                                        securityCtx.getSubject(), cm).map(Unchecked.function(c -> {
                                    if (c == 0)
                                        throw new DForbiddenException();
                                    return cm;
                                }))
                        ));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm
     */
    public Uni<CompetitionModel> hasAdminViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
        return hasAdminViewPerm(securityCtx, Uni.createFrom().item(competitionModel));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm
     */
    public Uni<CompetitionModel> hasAdminViewPerm(SecurityCtx securityCtx, long id) {
        return hasAdminViewPerm(securityCtx, competitionRepository.findById(id));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm
     */
    public Uni<CompetitionModel> hasAdminViewPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
        return in.call(Unchecked.function(o -> {
                    if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin"))
                        return Uni.createFrom().nullItem();

                    if (o.getSystem() == CompetitionSystem.SAFCA)
                        return hasSafcaViewPerm(securityCtx, o.getId());

                    if (o.getAdmin().contains(securityCtx.getSubject()))
                        return Uni.createFrom().nullItem();

                    if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
                        throw new DForbiddenException();

                    if (o.getSystem() == CompetitionSystem.INTERNAL)
                        if (securityCtx.isClubAdmin())
                            return Uni.createFrom().nullItem();

                    throw new DForbiddenException();
                })
        );
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
     */
    public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
        return hasEditPerm(securityCtx, Uni.createFrom().item(competitionModel));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
     */
    public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, long id) {
        return hasEditPerm(securityCtx, competitionRepository.findById(id));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
     */
    public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
        return in.call(Unchecked.function(o -> {
                    if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin"))
                        return Uni.createFrom().nullItem();

                    if (o.getSystem() == CompetitionSystem.SAFCA)
                        return hasSafcaEditPerm(securityCtx, o.getId());

                    if (o.getSystem() == CompetitionSystem.INTERNAL) {
                        if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
                            return Uni.createFrom().nullItem();

                        if (o.getAdmin().contains(securityCtx.getSubject()))
                            return Uni.createFrom().nullItem();

                        throw new DForbiddenException();
                    }

                    throw new DForbiddenException();
                })
        );
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
     */
    public Uni<CompetitionModel> hasTablePerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
        return hasTablePerm(securityCtx, Uni.createFrom().item(competitionModel));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
     */
    public Uni<CompetitionModel> hasTablePerm(SecurityCtx securityCtx, long id) {
        return hasTablePerm(securityCtx, competitionRepository.findById(id));
    }

    /**
     * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
     */
    public Uni<CompetitionModel> hasTablePerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
        return in.call(Unchecked.function(o -> {
                    if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin"))
                        return Uni.createFrom().nullItem();

                    if (o.getSystem() == CompetitionSystem.SAFCA)
                        return hasSafcaTablePerm(securityCtx, o.getId());

                    if (o.getSystem() == CompetitionSystem.INTERNAL) {
                        if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
                            return Uni.createFrom().nullItem();

                        if (o.getAdmin().contains(securityCtx.getSubject()))
                            return Uni.createFrom().nullItem();
                        if (o.getTable().contains(securityCtx.getSubject()))
                            return Uni.createFrom().nullItem();

                        throw new DForbiddenException();
                    }

                    throw new DForbiddenException();
                })
        );
    }

    private Uni<?> hasSafcaViewPerm(SecurityCtx securityCtx, long id) {
        return securityCtx.roleHas("safca_super_admin") ?
                Uni.createFrom().nullItem()
                :
                getSafcaConfig(id).chain(Unchecked.function(o -> {
                    if (!o.admin().contains(UUID.fromString(securityCtx.getSubject()))
                            && !o.table().contains(UUID.fromString(securityCtx.getSubject())))
                        throw new DForbiddenException();
                    return Uni.createFrom().nullItem();
                }));
    }

    private Uni<?> hasSafcaEditPerm(SecurityCtx securityCtx, long id) {
        return securityCtx.roleHas("safca_super_admin") ?
                Uni.createFrom().nullItem()
                :
                getSafcaConfig(id).chain(Unchecked.function(o -> {
                    if (!o.admin().contains(UUID.fromString(securityCtx.getSubject())))
                        throw new DForbiddenException();
                    return Uni.createFrom().nullItem();
                }));
    }

    private Uni<?> hasSafcaTablePerm(SecurityCtx securityCtx, long id) {
        return securityCtx.roleHas("safca_super_admin") ?
                Uni.createFrom().nullItem()
                :
                getSafcaConfig(id).chain(Unchecked.function(o -> {
                    if (!o.admin().contains(UUID.fromString(securityCtx.getSubject()))
                            && !o.table().contains(UUID.fromString(securityCtx.getSubject())))
                        throw new DForbiddenException();
                    return Uni.createFrom().nullItem();
                }));
    }
}
