package vn.azteam.tpf.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

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

import com.google.common.collect.Lists;
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 io.github.jhipster.service.filter.LongFilter;
import vn.azteam.tpf.domain.TBCustomerHasCropType;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBCustomerHasCropTypeRepository;
import vn.azteam.tpf.repository.search.TBCustomerHasCropTypeSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBCustomerHasCropTypeMapper;
import vn.azteam.tpf.web.rest.errors.BadRequestAlertException;

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

    private static final String ENTITY_NAME = "tBCustomerHasCropType";

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

    private final TBCustomerHasCropTypeRepository tBCustomerHasCropTypeRepository;

    private final TBCustomerHasCropTypeMapper tBCustomerHasCropTypeMapper;

    private final TBCustomerHasCropTypeService tbCustomerHasCropTypeService;

    private final TBCropTypeService tbCropTypeService;

    private final TBActivityTypeService tbActivityTypeService;

    private final TBCustomerHasCropTypeSearchRepository tBCustomerHasCropTypeSearchRepository;

    private final UserService userService;

    private final EntityManager em;

    private final TBCustomerHasCropTypeMapper tbCustomerHasCropTypeMapper;

    public TBCustomerHasCropTypeQueryService(EntityManager em,
                                             TBCustomerHasCropTypeRepository tBCustomerHasCropTypeRepository,
                                             TBCustomerHasCropTypeMapper tBCustomerHasCropTypeMapper,
                                             TBCustomerHasCropTypeService tbCustomerHasCropTypeService,
                                             TBCropTypeService tbCropTypeService,
                                             TBActivityTypeService tbActivityTypeService,
                                             TBCustomerHasCropTypeSearchRepository tBCustomerHasCropTypeSearchRepository,
                                             UserService userService,
                                             TBCustomerHasCropTypeMapper tbCustomerHasCropTypeMapper) {
        this.em = em;
        this.tBCustomerHasCropTypeRepository = tBCustomerHasCropTypeRepository;
        this.tBCustomerHasCropTypeMapper = tBCustomerHasCropTypeMapper;
        this.tbCustomerHasCropTypeService = tbCustomerHasCropTypeService;
        this.tbCropTypeService = tbCropTypeService;
        this.tbActivityTypeService = tbActivityTypeService;
        this.tBCustomerHasCropTypeSearchRepository = tBCustomerHasCropTypeSearchRepository;
        this.userService = userService;
        this.tbCustomerHasCropTypeMapper = tbCustomerHasCropTypeMapper;
    }

    /**
     * Return a {@link List} of {@link TBCustomerHasCropTypeDTO} 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<TBCustomerHasCropTypeDTO> findByCriteria(TBCustomerHasCropTypeCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBCustomerHasCropType> specification = createSpecification(criteria);
        return tBCustomerHasCropTypeMapper.toDto(tBCustomerHasCropTypeRepository.findAll(specification));
    }

    @Transactional(readOnly = true)
    public List<Long> getActivityTypeIdsByCustomerId(Long customerId) {
        String query = "SELECT cct.tb_activity_type_id \n" +
        "FROM "+TBCustomerHasCropType.class.getAnnotation(Table.class).name()+" cct \n" +
        "WHERE cct.tb_customer_id = :customerId \n" +
        "GROUP BY cct.tb_activity_type_id ";
        Query nativeQuery = em.createNativeQuery(query);
        nativeQuery.setParameter("customerId", customerId);
        return (List<Long>) nativeQuery.getResultList();
    }

    @Transactional(readOnly = true)
    public List<Long> getCropTypeIdsByCustomerId(Long customerId) {
        String query = "SELECT cct.tb_crop_type_id \n" +
        "FROM "+TBCustomerHasCropType.class.getAnnotation(Table.class).name()+" cct \n" +
        "WHERE cct.tb_customer_id = :customerId \n" +
        "GROUP BY cct.tb_crop_type_id ";
        Query nativeQuery = em.createNativeQuery(query);
        nativeQuery.setParameter("customerId", customerId);
        return (List<Long>) nativeQuery.getResultList();
    }

    /**
     * Return a {@link Page} of {@link TBCustomerHasCropTypeDTO} 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<TBCustomerHasCropTypeDTO> findByCriteria(TBCustomerHasCropTypeCriteria criteria, Pageable page) {
        log.debug("find by criteria : {}, page: {}", criteria, page);
        final Specification<TBCustomerHasCropType> specification = createSpecification(criteria);
        return tBCustomerHasCropTypeRepository.findAll(specification, page)
            .map(tBCustomerHasCropTypeMapper::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(TBCustomerHasCropTypeCriteria criteria) {
        log.debug("count by criteria : {}", criteria);
        final Specification<TBCustomerHasCropType> specification = createSpecification(criteria);
        return tBCustomerHasCropTypeRepository.count(specification);
    }

    /**
     * Function to convert TBCustomerHasCropTypeCriteria to a {@link Specification}
     */
    private Specification<TBCustomerHasCropType> createSpecification(TBCustomerHasCropTypeCriteria criteria) {
        Specification<TBCustomerHasCropType> specification = Specification.where(null);
        if (criteria != null) {
            UserDTO currentUser = userService.getCurrentUserDTO().get();

            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBCustomerHasCropType_.id));
            }
            if (criteria.getTbCropTypeId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbCropTypeId(),
                    root -> root.join(TBCustomerHasCropType_.tbCropType, JoinType.LEFT).get(TBCropType_.id)));
            }
            // Allow find records of customer only if current user is not AllCustomer case.
            if (currentUser.getCustomerId() != null) {
                LongFilter customerId = new LongFilter();
                customerId.setEquals(currentUser.getCustomerId());
                specification = specification.and(buildSpecification(customerId,
                    root -> root.join(TBCustomerHasCropType_.tbCustomer, JoinType.LEFT).get(TBCustomer_.id)));
            } else {
                // Using filter as default if current user is AllCustomer case
                if (criteria.getTbCustomerId() != null) {
                    specification = specification.and(buildSpecification(criteria.getTbCustomerId(),
                        root -> root.join(TBCustomerHasCropType_.tbCustomer, JoinType.LEFT).get(TBCustomer_.id)));
                }
            }
            if (criteria.getTbActivityTypeId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbActivityTypeId(),
                    root -> root.join(TBCustomerHasCropType_.tbActivityType, JoinType.LEFT).get(TBActivityType_.id)));
            }
        }
        return specification;
    }

    public List<TBCustomerHasCropTypeDTO> getAllCustomerHasCropTypeByCustomerAndCropType(Long customerId, Long cropTypeId){
        String query = "SELECT * \n" +
            "FROM tb_customer_has_crop_type AS cct \n" +
            "WHERE cct.tb_customer_id = :customerId ";
        if(cropTypeId != null){
            query += "AND cct.tb_crop_type_id = :cropTypeId";
        }
        Query nativeQuery = em.createNativeQuery(query ,TBCustomerHasCropType.class);
        nativeQuery.setParameter("customerId", customerId);
        if(cropTypeId != null){
            nativeQuery.setParameter("cropTypeId", cropTypeId);
        }
        return tbCustomerHasCropTypeMapper.toDto(nativeQuery.getResultList());
    }

    public List<Long> getListActivityTypeIdByByCustomerAndCropType(Long customerId, Long cropTypeId){
        List<TBCustomerHasCropTypeDTO> tbCustomerHasCropTypeDTOS = getAllCustomerHasCropTypeByCustomerAndCropType(customerId,cropTypeId);
        List<Long> result = tbCustomerHasCropTypeDTOS.stream()
            .map(TBCustomerHasCropTypeDTO::getTbActivityTypeId)
            .collect(Collectors.toList());
        return result;
    }

    @Transactional
    public TBCustomerHasCropTypeUpdateDTO getCustomerHasCropTypeUpdate(Long customerId, Pageable pageable){
        TBCustomerHasCropTypeUpdateDTO result = new TBCustomerHasCropTypeUpdateDTO();
        List<TBCustomerHasCropTypeDTO> activityTypeCurrentList = getAllCustomerHasCropTypeByCustomerAndCropType(customerId,null);
        if(activityTypeCurrentList.size() > 0){
            List<TBCropTypeDTO> tbCropTypeDTOS = tbCropTypeService.findAll(pageable).getContent();
            List<TBActivityTypeDTO> tbActivityTypeDTOS = tbActivityTypeService.findAll(pageable).getContent();
            List<ActivityByCropTypeDTO> activityByCropTypeDTOList = new ArrayList<>();
            tbCropTypeDTOS.forEach(item -> {
                ActivityByCropTypeDTO activityByCropTypeDTO = new ActivityByCropTypeDTO();
                HashMap<String, Boolean> listHashActivityType = new HashMap<>();
                tbActivityTypeDTOS.forEach(culture -> {
                    listHashActivityType.put(culture.getName(),false);
                });
                List<TBCustomerHasCropTypeDTO> tbCustomerHasCropTypeDTOFilterList = activityTypeCurrentList.stream()
                    .filter(type -> type.getTbCropTypeId().equals(item.getId()) && type.getTbCustomerId().equals(customerId))
                    .collect(Collectors.toList());

                activityByCropTypeDTO.setTbCropTypeChecked(tbCustomerHasCropTypeDTOFilterList.size() > 0);

                tbCustomerHasCropTypeDTOFilterList.forEach(type -> {
                    if(result.getTbCustomerId() == null){
                        result.setTbCustomerId(customerId);
                    }
                    if(activityByCropTypeDTO.getTbCropTypeId() == null){
                        activityByCropTypeDTO.setTbCropTypeId(type.getTbCropTypeId());
                    }
                    if (type.getTbActivityTypeName() == null || type.getTbActivityTypeName().isEmpty()
                        && type.getTbActivityTypeId() != null){
                        TBActivityTypeDTO activityTypeDTO = tbActivityTypeService.findOne(type.getTbActivityTypeId()).get();
                        type.setTbActivityTypeName(activityTypeDTO.getName());
                    }
                    if ((type.getTbCropTypeName() == null || type.getTbCropTypeName().isEmpty())
                        && type.getTbCropTypeId() != null){
                        TBCropTypeDTO tbCropTypeDTO = tbCropTypeService.findOne(type.getTbCropTypeId()).get();
                        type.setTbCropTypeName(tbCropTypeDTO.getName());
                    }
                    activityByCropTypeDTO.setTbCropTypeName(type.getTbCropTypeName());
                    listHashActivityType.put(type.getTbActivityTypeName(),true);
                });

                if (activityByCropTypeDTO.getTbCropTypeId() != null){
                    activityByCropTypeDTO.setTbActivityTypeGetList(listHashActivityType);
                    activityByCropTypeDTOList.add(activityByCropTypeDTO);
                }
            });
            if (activityByCropTypeDTOList != null){
                result.setActivityTypeByCropTypeDTOList(activityByCropTypeDTOList);
            }
        }
        return result;
    }

    @Transactional
    public boolean updateCustomerHasCropTypeUpdate(TBCustomerHasCropTypeUpdateDTO tbCustomerHasCropTypeUpdateDTO){

        if(tbCustomerHasCropTypeUpdateDTO.getActivityTypeByCropTypeDTOList().size() > 0){
            tbCustomerHasCropTypeUpdateDTO.getActivityTypeByCropTypeDTOList().forEach(item -> {
                if (item.getTbCropTypeId() == null) {
                    throw new BadRequestAlertException("1031", ENTITY_NAME, "1031");
                }
                List<TBCustomerHasCropTypeDTO> activityTypeCurrentList = getAllCustomerHasCropTypeByCustomerAndCropType(tbCustomerHasCropTypeUpdateDTO.getTbCustomerId(),item.getTbCropTypeId());
                if(item.isTbCropTypeChecked()){
                    List<Long> activityTypeIdCurrentList = activityTypeCurrentList.stream().map(TBCustomerHasCropTypeDTO::getTbActivityTypeId).collect(Collectors.toList());
                    List<Long> nonDupDeleteList = Lists.newArrayList(activityTypeIdCurrentList);
                    nonDupDeleteList.removeAll(item.getTbActivityTypeIds());
                    activityTypeCurrentList.forEach(del -> {
                        if(nonDupDeleteList.contains(del.getTbActivityTypeId())){
                            tbCustomerHasCropTypeService.delete(del.getId());
                        }
                    });
//                    List<Long> nonDupCreateList = item.getTbActivityTypeIds();
                    List<Long> nonDupCreateList = Lists.newArrayList(item.getTbActivityTypeIds());
                    nonDupCreateList.removeAll(activityTypeIdCurrentList);
                    nonDupCreateList.forEach(create -> {
                        TBCustomerHasCropTypeDTO tbCustomerHasCropTypeDTO = new TBCustomerHasCropTypeDTO();
                        tbCustomerHasCropTypeDTO.setTbCustomerId(tbCustomerHasCropTypeUpdateDTO.getTbCustomerId());
                        tbCustomerHasCropTypeDTO.setTbCropTypeId(item.getTbCropTypeId());
                        tbCustomerHasCropTypeDTO.setTbActivityTypeId(create);
                        tbCustomerHasCropTypeService.save(tbCustomerHasCropTypeDTO);
                    });
                }
                else {
                    activityTypeCurrentList.forEach(type -> {
                        tbCustomerHasCropTypeService.delete(type.getId());
                    });
                }
            });
            return true;
        }
        return false;
    }
}
