package vn.azteam.tpf.service;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.JoinType;

import io.github.jhipster.service.filter.InstantFilter;
import io.github.jhipster.service.filter.StringFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import io.github.jhipster.service.QueryService;

import vn.azteam.tpf.domain.TBObjectParameter;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBObjectParameterRepository;
import vn.azteam.tpf.repository.search.TBObjectParameterSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBObjectParameterMapper;
import java.util.ArrayList;

/**
 * Service for executing complex queries for TBObjectParameter entities in the database.
 * The main input is a {@link TBObjectParameterCriteria} which gets converted to {@link Specification},
 * in a way that all the filters must apply.
 * It returns a {@link List} of {@link TBObjectParameterDTO} or a {@link Page} of {@link TBObjectParameterDTO} which fulfills the criteria.
 */
@Service
@Transactional(readOnly = true)
public class TBObjectParameterQueryService extends QueryService<TBObjectParameter> {

    private final Logger log = LoggerFactory.getLogger(TBObjectParameterQueryService.class);

    private final TBObjectParameterRepository tBObjectParameterRepository;

    private final TBObjectParameterMapper tBObjectParameterMapper;

    private final TBObjectParameterSearchRepository tBObjectParameterSearchRepository;

    private final TBExternalTableQueryService tbExternalTableQueryService;

    private final EntityManager em;

    public TBObjectParameterQueryService(TBObjectParameterRepository tBObjectParameterRepository,
                                         TBObjectParameterMapper tBObjectParameterMapper,
                                         TBObjectParameterSearchRepository tBObjectParameterSearchRepository,
                                         TBExternalTableQueryService tbExternalTableQueryService,
                                         EntityManager em) {
        this.tBObjectParameterRepository = tBObjectParameterRepository;
        this.tBObjectParameterMapper = tBObjectParameterMapper;
        this.tBObjectParameterSearchRepository = tBObjectParameterSearchRepository;
        this.tbExternalTableQueryService = tbExternalTableQueryService;
        this.em = em;
    }

    /**
     * Return a {@link List} of {@link TBObjectParameterDTO} which matches the criteria from the database
     *
     * @param criteria The object which holds all the filters, which the entities should match.
     * @return the matching entities.
     */
    @Transactional(readOnly = true)
    public List<TBObjectParameterDTO> findByCriteria(TBObjectParameterCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBObjectParameter> specification = createSpecification(criteria);
        return tBObjectParameterMapper.toDto(tBObjectParameterRepository.findAll(specification));
    }

    /**
     * Return a {@link Page} of {@link TBObjectParameterDTO} which matches the criteria from the database
     *
     * @param criteria The object which holds all the filters, which the entities should match.
     * @param page     The page, which should be returned.
     * @return the matching entities.
     */
    @Transactional(readOnly = true)
    public Page<TBObjectParameterDTO> findByCriteria(TBObjectParameterCriteria criteria, Pageable page) {
        log.debug("find by criteria : {}, page: {}", criteria, page);
        final Specification<TBObjectParameter> specification = createSpecification(criteria);
        return tBObjectParameterRepository.findAll(specification, page)
            .map(tBObjectParameterMapper::toDto);
    }

    /**
     * Return the number of matching entities in the database
     *
     * @param criteria The object which holds all the filters, which the entities should match.
     * @return the number of matching entities.
     */
    @Transactional(readOnly = true)
    public long countByCriteria(TBObjectParameterCriteria criteria) {
        log.debug("count by criteria : {}", criteria);
        final Specification<TBObjectParameter> specification = createSpecification(criteria);
        return tBObjectParameterRepository.count(specification);
    }

    /**
     * Function to convert TBObjectParameterCriteria to a {@link Specification}
     */
    private Specification<TBObjectParameter> createSpecification(TBObjectParameterCriteria criteria) {
        Specification<TBObjectParameter> specification = Specification.where(null);
        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBObjectParameter_.id));
            }
            if (criteria.getName() != null) {
                specification = specification.and(buildStringSpecification(criteria.getName(), TBObjectParameter_.name));
            }
            if (criteria.getUnit() != null) {
                specification = specification.and(buildStringSpecification(criteria.getUnit(), TBObjectParameter_.unit));
            }
            if (criteria.getDescription() != null) {
                specification = specification.and(buildStringSpecification(criteria.getDescription(), TBObjectParameter_.description));
            }
            if (criteria.getCreatedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getCreatedDate(), TBObjectParameter_.createdDate));
            }
            if (criteria.getModifiedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getModifiedDate(), TBObjectParameter_.modifiedDate));
            }
//            if (criteria.getDeletedDate() != null) {
//                specification = specification.and(buildRangeSpecification(criteria.getDeletedDate(), TBObjectParameter_.deletedDate));
//            }
            InstantFilter instantFilter = new InstantFilter();
            instantFilter.setSpecified(criteria.getDeletedDate() != null);
            criteria.setDeletedDate(instantFilter);
            specification = specification.and(buildRangeSpecification(criteria.getDeletedDate(), TBObjectParameter_.deletedDate));

            if (criteria.getDeletedById() != null) {
                specification = specification.and(buildSpecification(criteria.getDeletedById(),
                    root -> root.join(TBObjectParameter_.deletedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getCreatedById() != null) {
                specification = specification.and(buildSpecification(criteria.getCreatedById(),
                    root -> root.join(TBObjectParameter_.createdBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getModifiedById() != null) {
                specification = specification.and(buildSpecification(criteria.getModifiedById(),
                    root -> root.join(TBObjectParameter_.modifiedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getActivityTypeId() != null) {
                specification = specification.and(buildSpecification(criteria.getActivityTypeId(),
                    root -> root.join(TBObjectParameter_.activityType, JoinType.LEFT).get(TBActivityType_.id)));
            }
            if (criteria.getTbActivityExtendTypeId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbActivityExtendTypeId(),
                    root -> root.join(TBObjectParameter_.tbActivityExtendType, JoinType.LEFT).get(TBActivityExtendType_.id)));
            }
            if (criteria.getIsGuidelineUsing() != null) {
                specification = specification.and(buildSpecification(criteria.getIsGuidelineUsing(), TBObjectParameter_.isGuidelineUsing));
            }
            if (criteria.getIsMandatory() != null) {
                specification = specification.and(buildSpecification(criteria.getIsMandatory(), TBObjectParameter_.isMandatory));
            }
            if (criteria.getTbControlTypeId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbControlTypeId(),
                    root -> root.join(TBObjectParameter_.tbControlType, JoinType.LEFT).get(TBControlType_.id)));
            }
            if (criteria.getGroupName() != null) {
                specification = specification.and(buildStringSpecification(criteria.getGroupName(), TBObjectParameter_.groupName));
            }
        }
        return specification;
    }

    public List<TBActivityExtendTypeDropDownDTO> getAllDataFromTableCommon(String tableName) {
        String sqlString = "select tbl.id, tbl.name, tbl.description\n" +
            " from " + tableName + " as tbl\n";

        Query nativeQuery = em.createNativeQuery(sqlString);
        List<Object[]> listObjects = nativeQuery.getResultList();
        List<TBActivityExtendTypeDropDownDTO> result = new ArrayList<>();
        for(Object[] obj : listObjects) {
            TBActivityExtendTypeDropDownDTO tbActivityExtendTypeDropDownDTO = new TBActivityExtendTypeDropDownDTO();
            tbActivityExtendTypeDropDownDTO.setId(Long.parseLong(obj[0].toString()));
            tbActivityExtendTypeDropDownDTO.setName(obj[1].toString());
            tbActivityExtendTypeDropDownDTO.setDescription(obj[2].toString());
            result.add(tbActivityExtendTypeDropDownDTO);
        }
        return result;
    }

    public TBActivityExtendTypeDropDownDTO getObjectFromRelationTableByConditionCommon(String relationTable, String condition) {
        String sqlString = "select tbl.id, tbl.name, tbl.description\n" +
            " from " + relationTable + " as tbl\n" +
            " where name = \"" + condition + "\"" ;

        Query nativeQuery = em.createNativeQuery(sqlString);
        List<Object[]> itemObjects = nativeQuery.getResultList();
        Object[] itemObject = itemObjects.get(0);
        TBActivityExtendTypeDropDownDTO result = new TBActivityExtendTypeDropDownDTO();
        result.setId(Long.parseLong(itemObject[0].toString()));
        result.setName(itemObject[1].toString());
        result.setDescription(itemObject[2]!=null ? itemObject[2].toString():null);
        return result;
    }


    public List<TBActivityExtendTypeDropDownDTO> getDataFromTableCommon(String tableName, String condition) {
        String sqlString = "select tbl.id, tbl.name, tbl.description\n" +
            " from " + tableName + " as tbl\n" +
            " where 1 = 1";

        if(condition != null && !condition.isEmpty()) {
            // Get external_table by table name
            TBExternalTableCriteria tbExternalTableCriteria = new TBExternalTableCriteria();
            StringFilter tableNameFilter = new StringFilter();
            tableNameFilter.setEquals(tableName);
            tbExternalTableCriteria.setName(tableNameFilter);
            TBExternalTableDTO tbExternalTableDTO = tbExternalTableQueryService.findByCriteria(tbExternalTableCriteria).get(0);

            // Get object from relation_table by condition
            TBActivityExtendTypeDropDownDTO objectRelation = getObjectFromRelationTableByConditionCommon(tbExternalTableDTO.getRelationTable(), condition);

            sqlString = sqlString + " and ( tbl." + tbExternalTableDTO.getForeignKey() + " = " + objectRelation.getId() + ")\n";
        }

        Query nativeQuery = em.createNativeQuery(sqlString);
        List<Object[]> listObjects = nativeQuery.getResultList();
        List<TBActivityExtendTypeDropDownDTO> result = new ArrayList<>();
        for(Object[] obj : listObjects) {
            TBActivityExtendTypeDropDownDTO tbActivityExtendTypeDropDownDTO = new TBActivityExtendTypeDropDownDTO();
            tbActivityExtendTypeDropDownDTO.setId(Long.parseLong(obj[0].toString()));
            tbActivityExtendTypeDropDownDTO.setName(obj[1].toString());
            tbActivityExtendTypeDropDownDTO.setDescription(obj[2]!=null ? obj[2].toString():null);
            result.add(tbActivityExtendTypeDropDownDTO);
        }
        return result;
    }

}
