package dev.fyloz.trial.colorrecipesexplorer.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.fyloz.trial.colorrecipesexplorer.config.Preferences;
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.exception.model.NullIdentifierException;
import dev.fyloz.trial.colorrecipesexplorer.model.Model;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;

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

public abstract class AbstractJavaService<E extends Model, R extends JpaRepository<E, Long>> implements IGenericJavaService<E> {

    protected Logger logger = Preferences.logger;
    protected R repository;
    protected Class<E> type;

    public AbstractJavaService(Class<E> type) {
        this.type = type;
    }

    public R getRepository() {
        return repository;
    }

    @Override
    public boolean existsById(Long id) {
        return repository.existsById(id);
    }

    /**
     * Vérifie si une entité correspondant à un identifiant existe.
     * <p>
     * Méthode pour la compatibilité avec les services Kotlin.
     *
     * @param id L'identifiant de l'entité
     * @return Si un entité correspondant à l'identifiant existe
     */
    public boolean existsById(long id) {
        return existsById(Long.valueOf(id));
    }

    /**
     * Méthode utilitaire pour wrapper les entités nullables dans un Optional.
     * <p>
     * Méthode pour la compatibilité avec les repository Kotlin.
     *
     * @param entity L'entité nullable
     * @return Un optional wrapper l'entité donnée
     */
    protected Optional<E> findOptional(@Nullable E entity) {
        return Optional.ofNullable(entity);
    }

    @Override
    public E getById(Long id) {
        Optional<E> found = repository.findById(id);
        if (found.isEmpty()) throw new EntityNotFoundException(type, EntityNotFoundException.IdentifierType.ID, id);

        return found.get();
    }

    /**
     * Récupère l'entité de type T correspondant à un identifiant.
     * <p>
     * Méthode pour la compatibilité avec les services Kotlin.
     *
     * @param id L'identifiant de l'entité
     * @return L'entité correspondant à l'identifiant
     */
    public E getById(long id) {
        return getById(Long.valueOf(id));
    }

    @Override
    public List<E> getAll() {
        return repository.findAll();
    }

    @Override
    public E save(@NotNull E entity) {
        if (entity.getId() != null && existsById(entity.getId()))
            throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.ID, entity.getId());

        return repository.save(entity);
    }

    @Override
    @Transactional
    public Collection<E> saveAll(@NotNull Collection<E> entities) {
        return entities
                .stream()
                .map(this::save)
                .collect(Collectors.toList());
    }

    @Override
    public E update(@NotNull E entity) {
        if (entity.getId() == null) throw new NullIdentifierException(type);
        if (!existsById(entity.getId()))
            throw new EntityNotFoundException(type, ModelException.IdentifierType.ID, entity.getId());

        return repository.save(entity);
    }

    @Override
    public void delete(@NotNull E entity) {
        repository.delete(entity);
    }

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

    /**
     * Supprime une entité correspondant à l'identifiant.
     * <p>
     * Méthode pour la compatibilité avec les services Kotlin.
     *
     * @param id L'identifiant de l'entité à supprimer
     */
    public void deleteById(long id) {
        deleteById(Long.valueOf(id));
    }

    @Override
    public void deleteAll(Collection<E> entities) {
        repository.deleteAll(entities);
    }

    /**
     * Transforme un objet en Json.
     *
     * @param obj L'objet à transformer
     * @return L'objet sous forme de Json.
     */
    public String asJson(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            Preferences.logger.error("Une erreur est survenue lors de la transformation d'un objet en Json", e);
            return null;
        }
    }
}
