package fr.titionfire.ffsaf.domain.service;

import fr.titionfire.ffsaf.data.model.CardModel;
import fr.titionfire.ffsaf.data.model.ClubCardModel;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.ws.recv.RCard;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.panache.common.Sort;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@WithSession
@ApplicationScoped
public class CardService {
    @Inject
    CardRepository cardRepository;

    @Inject
    ClubCardRepository clubCardRepository;

    @Inject
    RegisterRepository registerRepository;

    @Inject
    CompetitionGuestRepository competitionGuestRepository;

    @Inject
    MatchRepository matchRepository;

    @Inject
    TradService trad;

    private static final List<CardModel.CardType> COMPETITION_LEVEL_CARDS = List.of(
            CardModel.CardType.YELLOW,
            CardModel.CardType.RED,
            CardModel.CardType.BLACK
    );

    private List<Long> extractCombIds(MatchModel match) {
        List<Long> ids = new ArrayList<>();
        if (match.getC1_id() != null)
            ids.add(match.getC1_id().getId());
        if (match.getC2_id() != null)
            ids.add(match.getC2_id().getId());
        if (match.getC1_guest() != null)
            ids.add(match.getC1_guest().getId() * -1);
        if (match.getC2_guest() != null)
            ids.add(match.getC2_guest().getId() * -1);
        return ids;
    }

    public Uni<List<CardModel>> getForMatch(MatchModel match) {
        return cardRepository.list(
                "competition = ?1 AND (type IN ?2 OR (type = CardType.BLUE AND category = ?4)) AND comb IN ?3",
                match.getCategory().getCompet(), COMPETITION_LEVEL_CARDS,
                extractCombIds(match), match.getCategory().getId());
    }

    public Uni<List<CardModel>> getAll(CompetitionModel competition) {
        return cardRepository.list("competition = ?1", competition);
    }

    public Uni<RCard.SendCardAdd> checkCanBeAdded(RCard.SendCardAdd card, MatchModel matchModel) {
        return cardRepository.find("competition = ?1 AND comb = ?2",
                        Sort.descending("type"),
                        matchModel.getCategory().getCompet(), card.combId())
                .firstResult()
                .map(card_ -> {
                    if (card.type() == CardModel.CardType.BLUE) {
                        return card_ == null || (card_.getType() == CardModel.CardType.BLUE
                                && !Objects.equals(card_.getCategory(), matchModel.getCategory().getId()));
                    }
                    if (card.type() == CardModel.CardType.BLACK) {
                        return card_ != null && card_.getType() == CardModel.CardType.RED;
                    }

                    return card_ == null || card_.getType().ordinal() < card.type().ordinal();
                })
                .chain(b -> {
                    if (b)
                        return Uni.createFrom().item(card);
                    else
                        return Uni.createFrom().failure(new DBadRequestException(trad.t("card.cannot.be.added")));
                });
    }

    public Uni<List<CardModel>> addTeamCartToNewComb(Long combId, String teamUuid, String teamName,
                                                     CompetitionModel competition) {
        return clubCardRepository.list("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
                        Sort.ascending("type"), competition.getId(), teamUuid, teamName)
                .chain(clubCards -> {
                    Uni<?> queue = Uni.createFrom().voidItem();
                    List<CardModel> addCards = new ArrayList<>();
                    for (ClubCardModel clubCard : clubCards) {
                        CardModel model = new CardModel();
                        model.setCompetition(competition);
                        model.setCompetitionId(competition.getId());
                        model.setComb(combId);
                        model.setTeamCard(true);
                        model.setType(clubCard.getType());
                        model.setDate(clubCard.getDate());

                        queue = queue.call(__ -> Panache.withTransaction(() -> cardRepository.persist(model))
                                .invoke(addCards::add)
                                .call(() -> {
                                    clubCard.getCardIds().add(model.getId());
                                    return Panache.withTransaction(() -> clubCardRepository.persist(clubCard));
                                }));
                    }
                    return queue.replaceWith(addCards);
                });
    }

    public Uni<Void> rmTeamCardFromComb(Long combId, String uuid) {
        return cardRepository.delete("comb = ?1 AND competition.uuid = ?2 AND teamCard = True", combId, uuid)
                .replaceWithVoid();
    }

    public Uni<List<CardModel>> addTeamCard(CompetitionModel competition, String teamUuid, String teamName,
                                            CardModel.CardType type, String reason) {
        return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
                        Sort.descending("type"), competition.getId(), teamUuid, teamName)
                .firstResult()
                .map(card_ -> {
                    if (type == CardModel.CardType.BLACK) {
                        return card_ != null && card_.getType() == CardModel.CardType.RED;
                    }

                    return card_ == null || card_.getType().ordinal() < type.ordinal();
                })
                .chain(b -> {
                    if (!b)
                        return Uni.createFrom().failure(new DBadRequestException(trad.t("card.cannot.be.added")));

                    if (teamUuid != null) {
                        return registerRepository.list("competition = ?1 AND club.clubId = ?2", competition, teamUuid)
                                .map(l -> l.stream().map(r -> r.getMembre().getId()).toList());
                    } else {
                        return competitionGuestRepository.list("competition = ?1 AND club = ?2", competition,
                                        teamName)
                                .map(l -> l.stream().map(r -> r.getId() * -1).toList());
                    }
                })
                .chain(combIds -> cardRepository.list("competition = ?1 AND comb IN ?2", competition, combIds)
                        .map(cards -> {
                            List<CardModel> newCards = new ArrayList<>();
                            for (Long id : combIds) {
                                Optional<CardModel> optional = cards.stream()
                                        .filter(c -> id.equals(c.getComb()) && c.getType() == type).findAny();

                                CardModel model = new CardModel();
                                model.setCompetition(competition);
                                model.setCompetitionId(competition.getId());
                                model.setComb(id);
                                model.setTeamCard(true);

                                if (optional.isEmpty()) {
                                    model.setType(type);
                                } else {
                                    model.setType(
                                            CardModel.CardType.values()[Math.min(optional.get().getType().ordinal() + 1,
                                                    CardModel.CardType.BLACK.ordinal())]);
                                }
                                newCards.add(model);
                            }
                            return newCards;
                        })
                )
                .call(newCards -> Panache.withTransaction(() -> cardRepository.persist(newCards)))
                .call(newCards -> {
                    ClubCardModel model = new ClubCardModel();
                    model.setCompetition(competition.getId());
                    model.setTeamUuid(teamUuid);
                    model.setTeamName(teamName);
                    model.setType(type);
                    model.setReason(reason);
                    model.setCardIds(newCards.stream().map(CardModel::getId).toList());

                    return Panache.withTransaction(() -> clubCardRepository.persist(model));
                });
    }

    public Uni<List<CardModel>> recvReturnState(CompetitionModel competition, RCard.SendTeamCardReturnState state) {
        return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3) AND type = ?4",
                        competition.getId(), state.teamUuid(), state.teamName(), state.type())
                .firstResult()
                .chain(o -> cardRepository.list("id IN ?1", o.getCardIds()))
                .call(cards -> matchRepository.list("category.compet = ?1 AND category.id IN ?2", competition,
                                state.selectedCategory())
                        .invoke(matches -> {
                            for (CardModel card : cards) {
                                for (MatchModel m : matches.stream()
                                        .filter(m -> extractCombIds(m).contains(card.getComb())).toList()) {

                                    if (state.state() == 1) {
                                        card.setCategory(m.getCategory().getId());
                                    } else if (state.state() == 2) {
                                        card.setCategory(m.getCategory().getId());
                                        if (Objects.equals(m.getId(), state.selectedMatch()))
                                            card.setMatch(m.getId());
                                    }
                                }
                            }
                        })
                        .chain(() -> Panache.withTransaction(() -> cardRepository.persist(cards))));
    }

    public Uni<List<Long>> rmTeamCard(CompetitionModel competition, String teamUuid, String teamName,
                                      CardModel.CardType type) {
        return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3) AND type = ?4",
                        competition.getId(), teamUuid, teamName, type)
                .firstResult()
                .chain(card -> Uni.createFrom().item(card.getCardIds())
                        .call(() -> Panache.withTransaction(() -> cardRepository.delete("id IN ?1", card.getCardIds())))
                        .call(() -> Panache.withTransaction(() -> clubCardRepository.delete(card))));
    }
}
