package com.oguerreiro.resilient.service.query;

import java.util.List;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

// for static metamodels
import com.oguerreiro.resilient.domain.InputData;
import com.oguerreiro.resilient.domain.InputData_;
import com.oguerreiro.resilient.domain.Variable_;
import com.oguerreiro.resilient.repository.InputDataRepository;
import com.oguerreiro.resilient.service.criteria.InputDataCriteria;
import com.oguerreiro.resilient.service.dto.InputDataDTO;
import com.oguerreiro.resilient.service.mapper.InputDataMapper;

import jakarta.persistence.criteria.JoinType;

/**
 * Service for executing complex queries for {@link InputData} entities in the
 * database. The main input is a {@link InputDataCriteria} which gets converted
 * to {@link Specification}, in a way that all the filters must apply. It
 * returns a {@link List} of {@link InputDataDTO} which fulfills the criteria.
 */
@Service
@Transactional(readOnly = true)
public class InventoryQueryService extends AbstractQueryService<InputData, InputDataDTO, Long, InputDataCriteria> {

  public InventoryQueryService(InputDataRepository inputDataRepository, InputDataMapper inputDataMapper) {
    super(inputDataRepository, inputDataMapper);
  }

  /**
   * Function to convert {@link InputDataCriteria} to a {@link Specification}
   * 
   * @param  criteria The object which holds all the filters, which the entities
   *                  should match.
   * @return          the matching {@link Specification} of the entity.
   */
  protected Specification<InputData> createSpecification(InputDataCriteria criteria) {
    Specification<InputData> specification = Specification.where(null);
    if (criteria != null) {
      // This has to be called first, because the distinct method returns null
      if (criteria.getDistinct() != null) {
        specification = specification.and(distinct(criteria.getDistinct()));
      }
      if (criteria.getId() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getId(), InputData_.id));
      }
      if (criteria.getSourceValue() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getSourceValue(), InputData_.sourceValue));
      }
      if (criteria.getVariableValue() != null) {
        specification = specification.and(
            buildRangeSpecification(criteria.getVariableValue(), InputData_.variableValue));
      }
      if (criteria.getImputedValue() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getImputedValue(), InputData_.imputedValue));
      }
      if (criteria.getSourceType() != null) {
        specification = specification.and(buildSpecification(criteria.getSourceType(), InputData_.sourceType));
      }
      if (criteria.getDataDate() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getDataDate(), InputData_.dataDate));
      }
      if (criteria.getChangeDate() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getChangeDate(), InputData_.changeDate));
      }
      if (criteria.getChangeUsername() != null) {
        specification = specification.and(
            buildStringSpecification(criteria.getChangeUsername(), InputData_.changeUsername));
      }
      if (criteria.getDataSource() != null) {
        specification = specification.and(buildStringSpecification(criteria.getDataSource(), InputData_.dataSource));
      }
      if (criteria.getDataUser() != null) {
        specification = specification.and(buildStringSpecification(criteria.getDataUser(), InputData_.dataUser));
      }
      if (criteria.getDataComments() != null) {
        specification = specification.and(
            buildStringSpecification(criteria.getDataComments(), InputData_.dataComments));
      }
      if (criteria.getCreationDate() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getCreationDate(), InputData_.creationDate));
      }
      if (criteria.getCreationUsername() != null) {
        specification = specification.and(
            buildStringSpecification(criteria.getCreationUsername(), InputData_.creationUsername));
      }
      if (criteria.getVersion() != null) {
        specification = specification.and(buildRangeSpecification(criteria.getVersion(), InputData_.version));
      }
      if (criteria.getInputDataId() != null) {
        specification = specification.and(buildSpecification(criteria.getInputDataId(),
            root -> root.join(InputData_.inputData, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getVariableId() != null) {
        specification = specification.and(buildSpecification(criteria.getVariableId(),
            root -> root.join(InputData_.variable, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getVariableNameOrCode() != null) {
        // TODO : make a OR and search both Variable.code and Variable.name
        specification = specification.and(buildSpecification(criteria.getVariableNameOrCode(),
            root -> root.join(InputData_.variable, JoinType.LEFT).get(Variable_.code)).or(
                buildSpecification(criteria.getVariableNameOrCode(),
                    root -> root.join(InputData_.variable, JoinType.LEFT).get(Variable_.name))));
      }
      if (criteria.getPeriodId() != null) {
        specification = specification.and(buildSpecification(criteria.getPeriodId(),
            root -> root.join(InputData_.period, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getSourceUnitId() != null) {
        specification = specification.and(buildSpecification(criteria.getSourceUnitId(),
            root -> root.join(InputData_.sourceUnit, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getUnitId() != null) {
        specification = specification.and(buildSpecification(criteria.getUnitId(),
            root -> root.join(InputData_.unit, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getOwnerId() != null) {
        specification = specification.and(buildSpecification(criteria.getOwnerId(),
            root -> root.join(InputData_.owner, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getSourceInputDataId() != null) {
        specification = specification.and(buildSpecification(criteria.getSourceInputDataId(),
            root -> root.join(InputData_.sourceInputData, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
      if (criteria.getSourceInputDataUploadId() != null) {
        specification = specification.and(buildSpecification(criteria.getSourceInputDataUploadId(),
            root -> root.join(InputData_.sourceInputDataUpload, JoinType.LEFT).get(ID_ATTRIBUTE_NAME)));
      }
    }
    return specification;
  }
}
