package io.delphiplatform.api.v3.rdb.service;

import com.google.common.collect.ImmutableMap;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.util.JpaUtils;
import io.delphiplatform.api.util.OffsetBasedPageRequest;
import io.delphiplatform.api.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.view.util.Params;

public abstract class EntityService<E> {

    public static final Supplier<ResponseStatusException> NOT_FOUND =
        () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "The item you searched for could not be found.");

    protected final Pageable defaultPageable = PageRequest.of(0, ApplicationConstants.DEFAULT_PAGE_SIZE);

    private final JpaSpecificationExecutor<E> specificationExecutor;
    private final ImmutableMap<String, String> sortByToEntityFieldMap;

    protected EntityService(JpaSpecificationExecutor<E> specificationExecutor) {
        this(specificationExecutor, ImmutableMap.<String, String>builder().build());
    }

    protected EntityService(JpaSpecificationExecutor<E> specificationExecutor,
        ImmutableMap<String, String> sortByToEntityFieldMap) {
        this.specificationExecutor = specificationExecutor;
        this.sortByToEntityFieldMap = sortByToEntityFieldMap;
    }

    @Transactional(readOnly = true)
    public List<E> findEntities(Pageable pageable) {
        return getResult(null, pageable);
    }

    @Transactional(readOnly = true)
    public List<E> find(Specification<E> specification, Pageable pageable) {
        return getResult(specification, pageable);
    }

    @Transactional(readOnly = true)
    public List<E> find(Specification<E> specification) {
        return getResult(specification, null);
    }

    protected <T> Specification<T> and(List<Specification<T>> specifications) {
        return JpaUtils.and(specifications.stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toList()));
    }

    protected Specification<E> or(List<Specification<E>> specifications) {
        //noinspection ConstantConditions Specification::and is nullable only if both specs are null
        return specifications.stream()
            .filter(Objects::nonNull)
            .reduce(Specification::or)
            .orElse(null);
    }

    protected OffsetBasedPageRequest getPageRequestSortByToCamelCase(Params params) {
        String sortBy = convertToEntityField(params.getSortBy());
        return OffsetBasedPageRequest
            .ofParamsToCamelCase(params.getOffset(), params.getLimit(), params.getSortOrder(), sortBy);
    }

    protected OffsetBasedPageRequest getPageRequestSortByKeepCase(Params params) {
        String sortBy = convertToEntityField(params.getSortBy());
        return OffsetBasedPageRequest
            .ofParamsKeepCase(params.getOffset(), params.getLimit(), params.getSortOrder(), sortBy);
    }

    protected OffsetBasedPageRequest getPageRequestLimitOffset(Params params) {
        return OffsetBasedPageRequest.ofParamsKeepCase(params.getOffset(), params.getLimit());
    }

    protected String convertToEntityField(String inputSortBy) {
        if (CollectionUtils.isEmpty(inputSortBy)) {
            return null;
        }
        return sortByToEntityFieldMap.getOrDefault(inputSortBy, inputSortBy);
    }

    protected List<E> getResult(Specification<E> specification, Pageable pageable) {
        return specificationExecutor.findAll(specification, pageable == null ? defaultPageable : pageable).getContent();
    }

}
