package com.eactive.httpmockserver.common.search;

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

import java.util.List;

/**
 * Overview:
 *
 * BaseSearch 클래스는 Spring Data JPA 프레임워크를 사용하여 복잡한 검색 쿼리를 편리하게 작성할 수 있는 방법을 제공하는 추상 클래스입니다.
 * 이 클래스를 사용하면 SearchModel 객체 목록을 사용하여 검색 쿼리를 생성할 수 있으며, 이를 결합하여 보다 복잡하고 구체적인 검색 조건을 만들 수 있습니다.
 *
 * How to Use:
 *
 * BaseSearch 클래스를 사용하려면 검색하려는 엔티티에 특정한 BaseSearch의 하위 클래스를 만들어야 합니다.
 * 예를 들어 User 개체를 검색하려는 경우 BaseSearch<User>를 확장하는 UserSearch라는 클래스를 만들 수 있습니다.
 *
 * 하위 클래스에서 buildSearchCondition() 메서드를 구현해야 합니다.
 * 이 메서드는 사용하려는 검색 조건을 나타내는 SearchModel 객체 목록을 반환해야 합니다.
 *
 * 예를 들어, 다음은 이름에 특정 문자열이 포함된 사용자를 검색하기 위해 buildSearchCondition() 메서드를 구현하는 방법입니다:
 *
 * public List<SearchModel> buildSearchCondition() {
 *     List<SearchModel> models = new ArrayList<>();
 *     models.add(new ColumnSearchModel("firstName", SearchCondition.LIKE, "John"));
 *     models.add(new ColumnSearchModel("lastName", SearchCondition.LIKE, "Doe"));
 *     return models;
 * }
 *
 * 이 예제에서는 이름과 성에 각각 문자열 "John"과 "Doe"가 포함된 사용자를 검색하는 두 개의 ColumnSearchModel 객체를 만들고 있습니다.
 *
 * 검색 쿼리를 빌드하려면 UserSearch 클래스의 인스턴스를 생성하고 그 인스턴스에서 buildSpecification() 메서드를 호출하면 됩니다.
 * 이 메서드는 Spring 데이터 JPA를 사용하여 데이터베이스를 쿼리하는 데 사용할 수 있는 Specification 객체를 반환합니다.
 *
 * UserSearch search = new UserSearch();
 * Specification<User> spec = search.buildSpecification();
 * List<User> users = userRepository.findAll(spec);
 *
 * 이 예제에서는 UserSearch 인스턴스를 만들고 buildSpecification() 메서드를 호출하여 사양 객체를 빌드한 다음 이 사양 객체를 사용하여 데이터베이스에서 검색 조건과 일치하는 모든 사용자 객체를 쿼리합니다.
 * 결과는 User 객체 목록으로 반환됩니다.
 *
 * 추가 활용 방법
 *
 * BaseSearch 클래스는 유연하고 확장 가능하도록 설계되어 필요에 따라 더 복잡한 검색 쿼리를 사용할 수 있습니다.
 * getSpecification() 메서드를 구현하는 SearchModel의 새로운 서브클래스를 생성하여 검색 조건을 추가할 수 있습니다.
 * 필요에 따라 추가 검색 조건을 포함하도록 SpecificationUtil 클래스를 수정할 수도 있습니다.
 *
 * 또한 BaseSearch 클래스를 확장하여 정렬 및 페이징과 같은 추가 기능을 제공할 수 있습니다.
 * 하위 클래스에 메서드와 속성을 추가하여 애플리케이션의 특정 요구 사항을 충족하는 검색 쿼리를 만들 수 있습니다.
 *
 * ColumnSearchModel은 검색조건이 컬럼명과 비교 연산자, 값으로 구성된다. SearchCondition 참조
 * SpecificationSearchModel은 검색조건이 Specification을 리턴하는 형태로 구성된다. 검색조건을 복잡하게 구성해야 할 때 사용한다.
 *
 * public static Specification current(String user, ApproverStatus approverStatus) {
 *     return (root, criteriaQuery, criteriaBuilder) -> {
 *         ListJoin join = root.joinList("approvers");
 *         return criteriaBuilder.and(
 *             criteriaBuilder.equal(join.get("user"), user),
 *             criteriaBuilder.equal(join.get("status"), approverStatus));
 *         };
 * }
 */
public interface BaseSearch<T> {

    List<SearchModel> buildSearchCondition();

    default Specification<T> buildSpecification() {

        List<SearchModel> models = buildSearchCondition();
        Specification<T> specifications = null;

        for (SearchModel model : models) {
            Specification spec = getSpecification(model);

            if (spec != null) {
                if (specifications == null) {
                    specifications = Specification.where(spec);
                } else {
                    specifications = specifications.and(spec);
                }
            }
        }

        return specifications;
    }

    default Specification<T> getSpecification(SearchModel model) {
        return model.getSpecification();
    }
}
