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

import dev.fyloz.trial.colorrecipesexplorer.exception.CannotDeleteDefaultMaterialTypeException;
import dev.fyloz.trial.colorrecipesexplorer.exception.CannotEditDefaultMaterialTypeException;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsException;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.ModelException;
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialType;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.MaterialTypeEditorDto;
import dev.fyloz.trial.colorrecipesexplorer.repository.MaterialTypeRepository;
import dev.fyloz.trial.colorrecipesexplorer.service.AbstractJavaService;
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialTypeKt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.validation.constraints.NotNull;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class MaterialTypeJavaService extends AbstractJavaService<MaterialType, MaterialTypeRepository> {

    private MaterialJavaService materialService;

    private List<MaterialType> defaultMaterialTypes = new LinkedList<>();

    public MaterialTypeJavaService() {
        super(MaterialType.class);
    }

    @Autowired
    public void setMaterialTypeDao(MaterialTypeRepository materialTypeRepository) {
        this.repository = materialTypeRepository;
    }

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

    public void setDefaultMaterialTypes(List<MaterialType> defaultMaterialTypes) {
        this.defaultMaterialTypes = defaultMaterialTypes;
    }

    /**
     * Ajoute un type de produit dans les types de produit par défaut.
     *
     * @param materialType Le type de produit
     */
    public void addDefault(MaterialType materialType) {
        defaultMaterialTypes.add(materialType);
    }

    /**
     * Vérifie si un type de produit est un type de produit par défaut.
     *
     * @param materialType Le type de produit
     * @return Si le type de produite est un type de produit par défaut
     */
    public boolean isDefault(MaterialType materialType) {
        return defaultMaterialTypes.contains(materialType);
    }

    /**
     * Vérifie si un type de produit correspondant à un nom existe.
     *
     * @param name Le nom à vérifier
     * @return Si un type de produit ayant le nom existe
     */
    public boolean existsByName(String name) {
        return repository.existsByName(name);
    }

    /**
     * Vérifie si un type de produit correspondant à un préfixe existe.
     *
     * @param prefix Le préfixe à vérifier
     * @return Si un type de produit ayant le préfixe existe
     */
    public boolean existsByPrefix(String prefix) {
        return repository.existsByPrefix(prefix);
    }

    /**
     * Vérifie si un type de produit est utilisé par des produits.
     *
     * @param materialType Le type de produit
     * @return si le type de produit est utilisé par des produits.
     */
    public boolean isLinkedToMaterials(MaterialType materialType) {
        return materialService.existsByMaterialType(materialType);
    }

    /**
     * Récupère tous les type de produits qui ne sont pas des types de produit.
     *
     * @return Tous les type de produits qui ne sont pas des types de produit
     */
    public List<MaterialType> getAllNotDefault() {
        return getAll().stream()
                .filter(t -> !isDefault(t))
                .collect(Collectors.toList());
    }

    /**
     * Récupère un type de produit par son nom.
     *
     * @param name Le nom du type de produit à récupérer
     * @return Le type de produit correspondant au nom.
     */
    public MaterialType getByName(String name) {
        Optional<MaterialType> found = findOptional(repository.findByName(name));
        if (found.isEmpty())
            throw new EntityNotFoundException(type, ModelException.IdentifierType.NAME, name);

        return found.get();
    }

    @Override
    public MaterialType save(@NotNull MaterialType materialType) {
        if (existsByName(materialType.getName()))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.NAME, materialType.getName());
        if (existsByPrefix(materialType.getPrefix()))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, MaterialTypeKt.IDENTIFIER_PREFIX_NAME, materialType.getPrefix());

        return super.save(materialType);
    }

    public MaterialType update(MaterialTypeEditorDto materialTypeDto) {
        MaterialType materialType = materialTypeDto.getMaterialType();

        if (isDefault(materialTypeDto.getOldMaterialType()))
            throw new CannotEditDefaultMaterialTypeException(materialTypeDto);
        if (!existsByName(materialTypeDto.getOldName()))
            throw new EntityNotFoundException(type, ModelException.IdentifierType.NAME, materialTypeDto.getOldName());
        if (!materialTypeDto.getOldName().equals(materialType.getName()) && existsByName(materialType.getName()))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.NAME, materialType.getName());
        if (!materialTypeDto.getOldPrefix().equals(materialType.getPrefix()) && existsByPrefix(materialType.getPrefix()))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, MaterialTypeKt.IDENTIFIER_PREFIX_NAME, materialType.getPrefix());

        return super.update(materialType);
    }

    @Override
    public void delete(MaterialType materialType) {
        if (isDefault(materialType)) throw new CannotDeleteDefaultMaterialTypeException(materialType);

        super.delete(materialType);
    }
}
