package dev.fyloz.trial.colorrecipesexplorer.service.model;

import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsException;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.ModelException;
import dev.fyloz.trial.colorrecipesexplorer.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.model.MixType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.MixFormDto;
import dev.fyloz.trial.colorrecipesexplorer.service.AbstractJavaService;
import dev.fyloz.trial.colorrecipesexplorer.utils.MixBuilder;
import dev.fyloz.trial.colorrecipesexplorer.repository.MixRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.Comparator;
import java.util.stream.Collectors;

@Service
public class MixService extends AbstractJavaService<Mix, MixRepository> {

    private MaterialJavaService materialService;
    private MixQuantityService mixQuantityService;
    private MixTypeService mixTypeService;

    public MixService() {
        super(Mix.class);
    }

    @Autowired
    public void setMixDao(MixRepository mixRepository) {
        this.repository = mixRepository;
    }

    @Autowired
    public void setMaterialService(MaterialJavaService materialService) {
        this.materialService = materialService;
    }

    @Autowired
    public void setMixQuantityService(MixQuantityService mixQuantityService) {
        this.mixQuantityService = mixQuantityService;
    }

    @Autowired
    public void setMixTypeService(MixTypeService mixTypeService) {
        this.mixTypeService = mixTypeService;
    }

    /**
     * Récupère les produits disponibles pour un mélange.
     * Le mélange peut ne pas exister.
     *
     * @param recipe La recette dans laquelle se trouve le mélange
     * @param mixId  L'identifiant du mélange (-1 si le mélange n'existe pas)
     * @return Les produits disponibles pour ce mélange
     */
    public Collection<Material> getAvailableMaterialsForMixId(Recipe recipe, Long mixId) {
        return existsById(mixId) ? getAvailableMaterialsForMix(getById(mixId)) : getAvailableMaterialsForNewMix(recipe);
    }

    /**
     * Récupère les produits disponibles pour un mélange existant.
     * `
     *
     * @param mix Le mélange
     * @return Les produits disponibles pour ce mélange
     */
    public Collection<Material> getAvailableMaterialsForMix(Mix mix) {
        return getAvailableMaterialsForNewMix(mix.getRecipe())
                .stream()
                .filter(m -> !m.equals(mix.getMixType().getMaterial()))
                .collect(Collectors.toList());
    }

    /**
     * Récupère les produits disponibles pour un mélange inexistant.
     *
     * @param recipe La recette dans laquelle se trouve le mélange
     * @return Les produits disponibles pour le nouveau mélange
     */
    public Collection<Material> getAvailableMaterialsForNewMix(Recipe recipe) {
        Collection<MixType> recipeMixTypes = recipe.getMixTypes();

        return materialService
                .getAll()
                .stream()
                .filter(m -> !m.isMixType() || recipeMixTypes.contains(mixTypeService.getByMaterial(m)))
                .sorted(Comparator.comparing(Material::getName))
                .sorted(Comparator.comparing(m -> m.getMaterialType().getName()))
                .collect(Collectors.toList());
    }

    @Transactional
    public void save(MixFormDto formDto) {
        Mix mix = new MixBuilder(mixTypeService, materialService)
                .withDto(formDto)
                .build();

        if (mix.getRecipe().hasMixType(mix.getMixType()))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, Mix.IDENTIFIER_MIX_TYPE_NAME, mix.getMixType().getName());

        mixTypeService.save(mix.getMixType());
        save(mix);
    }

    public void update(Mix mix, MixFormDto formDto) {
        mix = new MixBuilder(mixTypeService, materialService)
                .withMix(mix)
                .withDto(formDto)
                .build();

        MixType mixType = mix.getMixType();

        if (!formDto.getOldMixTypeName().equals(mixType.getName()) && mixTypeService.existsByName(formDto.getMixTypeName()) && mix.getRecipe().hasMixType(mixType))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, Mix.IDENTIFIER_MIX_TYPE_NAME, mix.getMixType().getName());

        if (materialService.existsByName(mixType.getName()) && !materialService.getByName(mixType.getName()).isMixType())
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, MixType.IDENTIFIER_MATERIAL_NAME, mixType.getName());

        mixTypeService.update(mixType);
        update(mix);
    }

    @Deprecated(since = "1.3.0", forRemoval = true)
    public void deleteMix(Mix mix) {
        mixQuantityService.deleteAll(mix.getMixQuantities());
        delete(mix);
    }

    @Override
    public void delete(Mix mix) {
        mixQuantityService.deleteAll(mix.getMixQuantities());

        super.delete(mix);
    }

    @Override
    public void deleteById(Long id) {
        delete(getById(id));
    }
}
