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

import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import io.delphiplatform.api.v3.model.video.YoutubeContentType;
import io.delphiplatform.api.v3.rdb.service.dto.JpaPropertyPath;

@Service
public class SpecificationProvider<T> {

    public Specification<T> isEmptyValueSpec(String propertyName, Boolean isEmpty) {
        return new IsEmptyPropertySpecification<>(propertyName, isEmpty);
    }

    public Specification<T> singleValueSpec(String propertyName, Object propertyValue) {
        return singleValueSpec(propertyName, propertyValue, Collections.emptyList());
    }

    public Specification<T> singleValueSpec(String propertyName, Boolean propertyValue) {
        return new BooleanValuePropertySpecification<>(propertyName, propertyValue);
    }

    public Specification<T> singleValueSpec(String propertyName, Object propertyValue, String join) {
        return singleValueSpec(Collections.singletonList(propertyName), propertyValue, Collections.singletonList(join));
    }

    public Specification<T> singleValueSpec(String propertyName, Object propertyValue, List<String> joins) {
        return singleValueSpec(Collections.singletonList(propertyName), propertyValue, joins);
    }

    public Specification<T> singleValueSpec(List<String> path, Object propertyValue, List<String> joins) {
        return new SingleValuePropertySpecification<>(path, propertyValue, joins);
    }

    public Specification<T> singleValueSpec(List<String> path, Object propertyValue) {
        return singleValueSpec(path, propertyValue, Collections.emptyList());
    }

    public Specification<T> singleValueSpec(List<String> propertyName, Collection<?> propertyValue) {
        return new SingleValuePropertySpecification<>(propertyName, propertyValue, null);
    }

    public <V extends Comparable<V>> Specification<T> greaterThanOrEqualToValueSpec(String propertyName,
        V propertyValue) {
        return new GreaterThanOrEqualToPropertySpecification<>(propertyName, propertyValue, null);
    }

    public <V extends Comparable<V>> Specification<T> greaterThanOrEqualToValueSpec(String propertyName,
        V propertyValue, List<String> joins) {
        return new GreaterThanOrEqualToPropertySpecification<>(propertyName, propertyValue, joins);
    }

    public <V extends Comparable<V>> Specification<T> lessThanOrEqualToValueSpec(String propertyName, V propertyValue) {
        return new LessThanOrEqualToPropertySpecification<>(propertyName, propertyValue, null);
    }

    public <V extends Comparable<V>> Specification<T> lessThanOrEqualToValueSpec(String propertyName,
        V propertyValue, List<String> joins) {
        return new LessThanOrEqualToPropertySpecification<>(propertyName, propertyValue, joins);
    }

    public Specification<T> multiValueSpec(String propertyName, Collection<?> propertyValue) {
        return multiValueSpec(propertyName, propertyValue, Collections.emptyList());
    }

    public Specification<T> multiTextIgnoreCaseValueSpec(String propertyName, Collection<String> propertyValue) {
        return multiTextIgnoreCaseValueSpec(propertyName, propertyValue, Collections.emptyList());
    }

    public Specification<T> multiValueSpec(String propertyName, Collection<?> propertyValue, boolean doDistinct) {
        return multiValueSpec(propertyName, propertyValue, Collections.emptyList(), doDistinct);
    }

    public Specification<T> multiValueSpec(String propertyName, Collection<?> propertyValue, String join) {
        return multiValueSpec(propertyName, propertyValue, Collections.singletonList(join), true);
    }

    public Specification<T> multiTextIgnoreCaseValueSpec(String propertyName, Collection<String> propertyValue,
        String join) {
        return multiTextIgnoreCaseValueSpec(propertyName, propertyValue, Collections.singletonList(join), true);
    }

    public Specification<T> multiTextIgnoreCaseValueSpec(String propertyName, Collection<String> propertyValue,
        List<String> joins) {
        return multiTextIgnoreCaseValueSpec(Collections.singletonList(propertyName), propertyValue, joins, true);
    }

    public Specification<T> multiValueSpec(String propertyName, Collection<?> propertyValue, List<String> joins) {
        return multiValueSpec(Collections.singletonList(propertyName), propertyValue, joins, true);
    }

    public Specification<T> multiValueSpec(String propertyName, Collection<?> propertyValue, List<String> joins, boolean doDistinct) {
        return multiValueSpec(Collections.singletonList(propertyName), propertyValue, joins, doDistinct);
    }

    public Specification<T> multiTextIgnoreCaseValueSpec(String propertyName, Collection<String> propertyValue,
        List<String> joins, boolean doDistinct) {
        return multiTextIgnoreCaseValueSpec(Collections.singletonList(propertyName), propertyValue, joins, doDistinct);
    }

    public Specification<T> multiValueSpec(List<String> propertyName, Collection<?> propertyValue) {
        return new MultiValuePropertySpecification<>(propertyName, propertyValue, Collections.emptyList(), true);
    }

    public Specification<T> multiTextIgnoreCaseValueSpec(List<String> propertyName, Collection<String> propertyValue) {
        return new MultiTextIgnoreCaseValuePropertySpecification<>(propertyName, propertyValue, Collections.emptyList(), true);
    }

    public Specification<T> singleTextLikeIgnoreCaseValueSpec(String propertyName, String propertyValue) {
        return new SingleValuePropertyLikeIgnoreCaseSpecification<>(List.of(propertyName), propertyValue, Collections.emptyList(), true);
    }

    public Specification<T> multiValueSpec(List<String> propertyName, Collection<?> propertyValue, boolean doDistinct) {
        return new MultiValuePropertySpecification<>(propertyName, propertyValue, Collections.emptyList(), doDistinct);
    }

    public Specification<T> multiValueSpec(List<String> propertyName, Collection<?> propertyValue, List<String> joins) {
        return multiValueSpec(propertyName, propertyValue, joins, true);
    }

    public Specification<T> multiValueSpec(List<String> propertyName, Collection<?> propertyValue, List<String> joins,
        boolean doDistinct) {
        return new MultiValuePropertySpecification<>(propertyName, propertyValue, joins, doDistinct);
    }

    public Specification<T> multiTextIgnoreCaseValueSpec(List<String> propertyName, Collection<String> propertyValue,
        List<String> joins, boolean doDistinct) {
        return new MultiTextIgnoreCaseValuePropertySpecification<>(propertyName, propertyValue, joins, doDistinct);
    }

    public Specification<T> notMultiValueSpec(List<String> propertyName, Collection<?> propertyValue) {
        return new ExcludeMultiValuePropertySpecification<>(propertyName, propertyValue, Collections.emptyList(), true);
    }

    public Specification<T> notMultiValueSpec(String propertyName, Collection<?> propertyValue) {
        return new ExcludeMultiValuePropertySpecification<>(List.of(propertyName), propertyValue,
            Collections.emptyList(), true);
    }

    public Specification<T> videosByIsrcsSpec(Collection<?> isrcs, Collection<YoutubeContentType> contentTypes,
        Long minPremiumUgcViews) {
        return new VideoByIsrcsSpecification<>(isrcs, contentTypes, minPremiumUgcViews);
    }

    public Specification<T> booleanValueSpec(Boolean value) {
        return new BooleanSpecification<>(value);
    }

    public Specification<T> videosByContentTypeSpec(Collection<YoutubeContentType> contentTypes,
        Long minPremiumUgcViews) {
        return new VideoContentTypeSpecification<T>(contentTypes, minPremiumUgcViews);
    }

    public Specification<T> nullValueSpec(String property, Boolean checkForNull, List<String> joins) {
        return new NullPropertySpecification<T>(property, checkForNull, joins);
    }

    public Specification<T> nullValueSpec(String property, Boolean checkForNull) {
        return new NullPropertySpecification<T>(property, checkForNull);
    }

    public Specification<T> fetchesSpec(Collection<String> fetches) {
        return fetchesSpec(fetches, false);
    }

    public Specification<T> fetchesSpecOfPaths(Collection<JpaPropertyPath> paths) {
        return fetchesSpecOfPaths(paths, false);
    }

    public Specification<T> fetchesSpec(Collection<String> fetches, boolean distinct) {
        List<JpaPropertyPath> paths = fetches.stream()
            .map(JpaPropertyPath::ofJoin)
            .collect(Collectors.toList());
        return new FetchSpecification<>(paths, distinct);
    }

    public Specification<T> fetchesSpecOfPaths(Collection<JpaPropertyPath> paths, boolean distinct) {
        return new FetchSpecification<>(paths, distinct);
    }

}
