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

import dev.fyloz.trial.colorrecipesexplorer.model.Company;
import dev.fyloz.trial.colorrecipesexplorer.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.RecipeEditorFormDto;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.RecipeExplorerFormDto;
import dev.fyloz.trial.colorrecipesexplorer.service.AbstractJavaService;
import dev.fyloz.trial.colorrecipesexplorer.service.files.ImagesService;
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class RecipeService extends AbstractJavaService<Recipe, RecipeRepository> {

    private CompanyService companyService;
    private MixService mixService;
    private StepService stepService;
    private ImagesService imagesService;

    public RecipeService() {
        super(Recipe.class);
    }

    @Autowired
    public void setRecipeDao(RecipeRepository recipeRepository) {
        this.repository = recipeRepository;
    }

    @Autowired
    public void setCompanyService(CompanyService companyService) {
        this.companyService = companyService;
    }

    @Autowired
    public void setMixService(MixService mixService) {
        this.mixService = mixService;
    }

    @Autowired
    public void setStepService(StepService stepService) {
        this.stepService = stepService;
    }

    @Autowired
    public void setImagesService(ImagesService imagesService) {
        this.imagesService = imagesService;
    }

    /**
     * Vérifie s'il y a une recette liée à une compagnie.
     *
     * @param company La compagnie
     * @return S'il y a une recette liée à la compagnie
     */
    public boolean existsByCompany(Company company) {
        return repository.existsByCompany(company);
    }

    /**
     * Récupère toutes les recettes pour une compagnie.
     *
     * @param company La compagnie
     * @return Toutes les recettes pour la compagnie
     */
    public Collection<Recipe> getAllByCompany(Company company) {
        return repository.findAllByCompany(company);
    }

    /**
     * Récupère toutes les recettes dans la base de données et les classes par compagnie.
     *
     * @return Un Map contenant les recettes classées par compagnie.
     */
    public Map<Company, Collection<Recipe>> getAllMappedByCompany() {
        return companyService.getAll().stream()
                .collect(Collectors.toMap(c -> c, this::getAllByCompany));
    }

    /**
     * Met à jour une recette ainsi que ses étapes.
     *
     * @param recipeDto Les informations de la recette à mettre à jour
     * @return La recette mise à jour
     */
    @Transactional
    public Recipe updateRecipeAndSteps(RecipeEditorFormDto recipeDto) {
        Recipe recipe = recipeDto.update(getById(recipeDto.getId()));

        stepService.deleteAll(recipe.getRecipeSteps());
        recipe.setRecipeSteps(stepService.createAllForRecipe(recipe, recipeDto.getStep()));

        return update(recipe);
    }

    /**
     * Met à jour les informations d'une recette trouvées dans l'explorateur de recette.
     *
     * @param form Le formulaire contenant les données mises à jour
     */
    @Transactional
    public void updateRecipeExplorerInfos(RecipeExplorerFormDto form) {
        long recipeId = form.getRecipeId();
        Map<Long, String> locations = form.getLocations();
        String note = form.getNote();

        Recipe recipe = getById(recipeId);

        // Note
        recipe.setNote(note);
        update(recipe);

        // Casiers
        for (Map.Entry<Long, String> location : locations.entrySet()) {
            Mix mix = mixService.getById(location.getKey());
            mix.setLocation(location.getValue());
            mixService.update(mix);
        }
    }

    @Override
    public void delete(Recipe recipe) {
        super.delete(recipe);
        removeAllFiles(recipe);
    }

    /**
     * Récupère le nom des images liées à une recette.
     *
     * @param recipe La recette dont on veut récupérer les images
     * @return Une liste contenant le nom des images liées à la recette.
     */
    public List<String> getImageFiles(Recipe recipe) {
        String imageName = getImageFileName(recipe);
        File[] allImages = imagesService.getDirectoryFile().listFiles((d, n) -> n.startsWith(imageName));

        if (allImages == null) return new ArrayList<>();
        return Arrays.stream(allImages).map(File::getName).collect(Collectors.toList());
    }

    /**
     * Récupère l'index de la prochaine image pour une recette.
     *
     * @param recipe La recette
     * @return L'index de la prochaine image pour la recette
     */
    public int getNextImageIndex(Recipe recipe) {
        String imageName = getImageFileName(recipe);
        List<String> allImages = getImageFiles(recipe);
        List<Integer> indexes = allImages.stream().map(i -> Integer.parseInt(i.replace(imageName + "-", "").replace(".jpeg", ""))).collect(Collectors.toList());
        return indexes.size() > 0 ? Collections.max(indexes) + 1 : 0;
    }

    /**
     * Récupère le nom des fichiers image d'une recette.
     *
     * @param recipe La recette
     * @return Le nom des fichiers image de la recette
     */
    public String getImageFileName(Recipe recipe) {
        return recipe.getId().toString();
    }

    /**
     * Récupère le nom du fichier image d'une recette avec le prochain index disponible.
     *
     * @param recipe La recette
     * @return Le nom du fichier image de la recette avec le prochain index disponible
     */
    public String getImageFileNameWithIndex(Recipe recipe) {
        return getImageFileNameWithIndex(recipe, getNextImageIndex(recipe));
    }

    /**
     * Récupère le nom du fichier image d'une recette avec un index.
     *
     * @param recipe La recette
     * @param index  L'index du fichier image
     * @return Le nom du fichier image de la recette avec l'index
     */
    public String getImageFileNameWithIndex(Recipe recipe, int index) {
        return String.format("%s-%s", getImageFileName(recipe), index);
    }

    /**
     * Supprime tous les fichiers image d'une recette.
     *
     * @param recipe La recette
     */
    private void removeAllFiles(Recipe recipe) {
        getImageFiles(recipe).forEach(f -> imagesService.delete(f));
    }
}
