package vn.azteam.tpf.service;

import java.math.BigInteger;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

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

import com.google.api.client.util.Lists;
import io.github.jhipster.service.filter.IntegerFilter;
import io.github.jhipster.service.filter.LongFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
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.Constants.Constants;
import vn.azteam.tpf.domain.TBCrop;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBCropRepository;
import vn.azteam.tpf.repository.search.TBCropSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBCropMapper;
import vn.azteam.tpf.service.mapper.TBGuidelineActivityMapper;
import vn.azteam.tpf.service.mapper.TBGuidelineDetailsMapper;
import vn.azteam.tpf.service.util.UserRoleUtil;

import static java.time.temporal.ChronoUnit.DAYS;
import static vn.azteam.tpf.web.rest.util.ConvertCharacterUtil.removeAccent;
/**
 * Service for executing complex queries for TBCrop entities in the database.
 * The main input is a {@link TBCropCriteria} which gets converted to {@link Specification},
 * in a way that all the filters must apply.
 * It returns a {@link List} of {@link TBCropDTO} or a {@link Page} of {@link TBCropDTO} which fulfills the criteria.
 */
@Service
@Transactional(readOnly = true)
public class TBCropQueryService extends QueryService<TBCrop> {

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

    private final String ACTIVE_TYPE_OTHER = "ACTIVE_TYPE_OTHER";

    private final TBCropRepository tBCropRepository;

    private final TBCropMapper tBCropMapper;

    private final TBCropService tBCropService;

    private final TBCropSearchRepository tBCropSearchRepository;

    private final EntityManager em;

    private final TBGuidelineDetailsMapper tBGuidelineDetailsMapper;

    private final TBActivityTypeQueryService tBActivityTypeQueryService;

    private final UserService userService;

    private final TBDetailUserService tbDetailUserService;

    private final UserRoleUtil userRoleUtil;

    private final PushNotificationService pushNotificationService;

    private final TBGuidelinePeriodQueryService tbGuidelinePeriodQueryService;

    private final TBGuidelineActivityMapper tbGuidelineActivityMapper;

    private final TBActivityTypeService tBActivityTypeService;

    @Value("${app.is-test-send-notification}")
    private Integer isTestSendNotification;

    public TBCropQueryService(
        TBCropRepository tBCropRepository,
        TBCropMapper tBCropMapper,
        TBCropService tBCropService,
        TBCropSearchRepository tBCropSearchRepository,
        EntityManager em,
        TBGuidelineDetailsMapper tBGuidelineDetailsMapper,
        TBActivityTypeQueryService tBActivityTypeQueryService,
        TBDetailUserService tbDetailUserService,
        UserRoleUtil userRoleUtil,
        PushNotificationService pushNotificationService,
        UserService userService,
        TBGuidelinePeriodQueryService tbGuidelinePeriodQueryService, TBGuidelineActivityMapper tbGuidelineActivityMapper, TBActivityTypeService tBActivityTypeService) {
        this.tBCropRepository = tBCropRepository;
        this.tBCropMapper = tBCropMapper;
        this.tBCropService = tBCropService;
        this.tBCropSearchRepository = tBCropSearchRepository;
        this.em = em;
        this.tBGuidelineDetailsMapper = tBGuidelineDetailsMapper;
        this.tBActivityTypeQueryService = tBActivityTypeQueryService;
        this.userService = userService;
        this.tbDetailUserService = tbDetailUserService;
        this.userRoleUtil = userRoleUtil;
        this.pushNotificationService = pushNotificationService;
        this.tbGuidelinePeriodQueryService = tbGuidelinePeriodQueryService;
        this.tbGuidelineActivityMapper = tbGuidelineActivityMapper;
        this.tBActivityTypeService = tBActivityTypeService;
    }

    /**
     * Return a {@link List} of {@link TBCropDTO} 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<TBCropDTO> findByCriteria(TBCropCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBCrop> specification = createSpecification(criteria);
        return tBCropMapper.toDto(tBCropRepository.findAll(specification));
    }

    /**
     * Return a {@link Page} of {@link TBCropDTO} 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<TBCropDTO> findByCriteria(TBCropCriteria criteria, Pageable page) {
        log.debug("find by criteria : {}, page: {}", criteria, page);
        final Specification<TBCrop> specification = createSpecification(criteria);
        return tBCropRepository.findAll(specification, page)
            .map(tBCropMapper::toDto);
    }

    /**
     * earch crop  which matches the criteria
     *
     * @param query
     * @param areaId
     * @param netHouseId
     * @param page
     * @return
     */
    @Transactional(readOnly = true)
    public Page<TBCropDTO> searchCrop(String query, Long areaId, Long netHouseId, Pageable page) {
        TBCropCriteria criteria = new TBCropCriteria();

        if (netHouseId != null) {
            LongFilter netHouseFilter = new LongFilter();
            netHouseFilter.setEquals(netHouseId);
            criteria.setNetHouseId(netHouseFilter);
        }

        List<TBCropDTO> crops = findByCriteria(criteria);

        if (areaId != null) {
            crops = crops.stream()
                .filter(item ->item.getAreaId() == areaId)
                .collect(Collectors.toList());
        }

        if (query != null && !query.equals("")) {
            crops = crops.stream()
                .filter(item ->
                    queryContain(item.getCode(), query)
                        || queryContain(item.getArea(), query)
                        || queryContain(item.getNetHouseName(), query)
                        || queryContain(item.getSuppliesName(), query)
                        || queryContain(item.getStatus(), query)
                        || queryContainPIC(item.getTbDetailUsers(), query)
                )
                .collect(Collectors.toList());
        }
        List<Long> cropIds = crops.stream().map(TBCropDTO::getId).collect(Collectors.toList());
        if (cropIds.isEmpty()) {
            return new PageImpl<>(Lists.newArrayList(), page, 0);
        }
        LongFilter longFilter = new LongFilter();
        longFilter.setIn(cropIds);
        criteria.setId(longFilter);

        return findByCriteria(criteria, page);
    }

    private Boolean queryContainPIC(Set<TBDetailUserDTO> pics, String query) {
        for (TBDetailUserDTO pic : pics) {
            if (queryContain(pic.getFullName(), query))
                return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    private Boolean queryContain(String data, String query) {
        if (data == null) {
            return Boolean.FALSE;
        } else {
            return removeAccent(data.toLowerCase()).contains(removeAccent(query.toLowerCase()));
        }
    }

    /**
     * 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(TBCropCriteria criteria) {
        log.debug("count by criteria : {}", criteria);
        final Specification<TBCrop> specification = createSpecification(criteria);
        return tBCropRepository.count(specification);
    }

    /**
     * Function to convert TBCropCriteria to a {@link Specification}
     */
    private Specification<TBCrop> createSpecification(TBCropCriteria criteria) {
        Specification<TBCrop> specification = initializeCropSpecification();
        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBCrop_.id));
            }
            if (criteria.getQrCode() != null) {
                specification = specification.and(buildStringSpecification(criteria.getQrCode(), TBCrop_.qrCode));
            }
            if (criteria.getCode() != null) {
                specification = specification.and(buildStringSpecification(criteria.getCode(), TBCrop_.code));
            }
            if (criteria.getAreaM2() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getAreaM2(), TBCrop_.areaM2));
            }
            if (criteria.getTbCropType() != null) {
                specification = specification.and(buildSpecification(criteria.getTbCropType(),
                    root -> root.join(TBCrop_.tbCropType, JoinType.LEFT).get(TBCropType_.id)));
            }
            if (criteria.getStartDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getStartDate(), TBCrop_.startDate));
            }
            if (criteria.getEndDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getEndDate(), TBCrop_.endDate));
            }
            if (criteria.getStatus() != null) {
                specification = specification.and(buildStringSpecification(criteria.getStatus(), TBCrop_.status));
            }
            if (criteria.getDescription() != null) {
                specification = specification.and(buildStringSpecification(criteria.getDescription(), TBCrop_.description));
            }
            if (criteria.getAgeDayStartAt() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getAgeDayStartAt(), TBCrop_.ageDayStartAt));
            }
            if (criteria.getCreatedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getCreatedDate(), TBCrop_.createdDate));
            }
            if (criteria.getModifiedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getModifiedDate(), TBCrop_.modifiedDate));
            }
            if (criteria.getDeletedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getDeletedDate(), TBCrop_.deletedDate));
            }
            if (criteria.getTbSuppliesId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbSuppliesId(),
                    root -> root.join(TBCrop_.tbSupplies, JoinType.LEFT).get(TBSupplies_.id)));
            }
            if (criteria.getTbGuidelineId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbGuidelineId(),
                    root -> root.join(TBCrop_.tbGuideline, JoinType.LEFT).get(TBGuideline_.id)));
            }
            if (criteria.getNetHouseId() != null) {
                specification = specification.and(buildSpecification(criteria.getNetHouseId(),
                    root -> root.join(TBCrop_.tbEntity, JoinType.LEFT).get(TBEntity_.id)));
            }
            if (criteria.getCreatedById() != null) {
                specification = specification.and(buildSpecification(criteria.getCreatedById(),
                    root -> root.join(TBCrop_.createdBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getModifiedById() != null) {
                specification = specification.and(buildSpecification(criteria.getModifiedById(),
                    root -> root.join(TBCrop_.modifiedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getDeletedById() != null) {
                specification = specification.and(buildSpecification(criteria.getDeletedById(),
                    root -> root.join(TBCrop_.deletedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getTbDetailUserId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbDetailUserId(),
                    root -> root.join(TBCrop_.tbDetailUsers, JoinType.LEFT).get(TBDetailUser_.id)));
            }
        }
        return specification;
    }

    /**
     * Function to convert TBCropCriteria to a {@link Specification}
     */
    private Specification<TBCrop> createSearchCropSpecification(TBCropCriteria criteria) {
        Specification<TBCrop> specification = Specification.where(null);
        if (criteria != null) {
            if (criteria.getCode() != null) {
                specification = specification.or(buildStringSpecification(criteria.getCode(), TBCrop_.code));
            }
            if (criteria.getStatus() != null) {
                specification = specification.or(buildStringSpecification(criteria.getStatus(), TBCrop_.status));
            }
            if (criteria.getArea() != null) {
                specification = specification.or(buildSpecification(criteria.getArea(),
                    root -> root.join(TBCrop_.tbEntity, JoinType.LEFT).join(TBEntity_.tbEntity, JoinType.LEFT).get(TBEntity_.name)));
            }
            if (criteria.getNetHouseName() != null) {
                specification = specification.or(buildSpecification(criteria.getNetHouseName(),
                    root -> root.join(TBCrop_.tbEntity, JoinType.LEFT).get(TBEntity_.name)));
            }
            if (criteria.getDescription() != null) {
                specification = specification.or(buildStringSpecification(criteria.getDescription(), TBCrop_.description));
            }
            if (criteria.getTbDetailUserFullName() != null) {
                specification = specification.or(buildSpecification(criteria.getTbDetailUserFullName(),
                    root -> root.join(TBCrop_.tbDetailUsers, JoinType.LEFT).get(TBDetailUser_.fullName)));
            }
            if (criteria.getTbSuppliesSku() != null) {
                specification = specification.or(buildSpecification(criteria.getTbSuppliesSku(),
                    root -> root.join(TBCrop_.tbSupplies, JoinType.LEFT).get(TBSupplies_.sku)));
            }
            if (criteria.getAreaId() != null) {
                specification = specification.and(buildSpecification(criteria.getAreaId(),
                    root -> root.join(TBCrop_.tbEntity, JoinType.LEFT).join(TBEntity_.tbEntity, JoinType.LEFT).get(TBEntity_.id)));
            }
            if (criteria.getNetHouseId() != null) {
                specification = specification.and(buildSpecification(criteria.getNetHouseId(),
                    root -> root.join(TBCrop_.tbEntity, JoinType.LEFT).get(TBEntity_.id)));
            }
        }
        return Specification.where(initializeCropSpecification()).and(specification);
    }

    private Specification<TBCrop> initializeCropSpecification() {
        return (Specification<TBCrop>) (root, query, cb) -> {
            final Join<TBCrop, TBDetailUser> detailUserJoin = root.join(TBCrop_.deletedBy, JoinType.LEFT);
            return cb.isNull(detailUserJoin.get(TBDetailUser_.id));
        };
    }

    public int getAgeDayByCropID(Long id, Instant executeDate) {
        Optional<TBCropDTO> tBCropDTOOptional = tBCropService.findOne(id);
        TBCropDTO tBCropDTO = tBCropDTOOptional.get();

        LocalDate startDate = tBCropDTO.getStartDate().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate endDate = executeDate.atZone(ZoneId.systemDefault()).toLocalDate();
        int daysBetween = (int) DAYS.between(startDate, endDate);

        int ageDay = 0;
        if (tBCropDTO.getAgeDayStartAt() != null) {
            ageDay = daysBetween + tBCropDTO.getAgeDayStartAt();
        } else {

            ageDay = daysBetween;
        }
        return ageDay;
    }

//    public String generateRandomStringFromUUID(int stringLength) {
//        String qrCode = UUID
//            .randomUUID()
//            .toString()
//            .replace("-", "")
//            .substring(0, stringLength)
//            .toUpperCase();
//        return qrCode;
//    }

    /**
     * Get all history of activities
     *
     * @param cropId
     * @param allActivities
     * @param pageable
     * @return
     */
    public ActivityTimelineDTO getActivityTimeline(
        Long cropId,
        List<TBActivityDTO> allActivities,
        Pageable pageable) {
        ActivityTimelineDTO activityTimelineDTO = new ActivityTimelineDTO();
        List<GuidelineDetailWrapperDTO> historyOfActivities = new ArrayList<>();
        List<TBGuidelineDetailsDTO> guidelineDetails = findGuidelineDetails(cropId);
        List<Long> guidelineDetailIds =
            guidelineDetails
                .stream()
                .map(TBGuidelineDetailsDTO::getId)
                .collect(Collectors.toList());

        Optional<UserDTO> currentUser = userService.getCurrentUserDTO();
        if (!guidelineDetailIds.isEmpty()) {
            List<TBActivityTypeDTO> activityTypes =
                tBActivityTypeQueryService.findByCriteria(new TBActivityTypeCriteria());
            //Filter assignment croptype by customer
            List<Long> listActivityTypeId = new ArrayList<>();
            if (currentUser.get().getCustomerId() != null) {
                TBCropDTO tBCropDTO = tBCropService.findOne(cropId).get();
                listActivityTypeId = tBCropService.getListActivityTypeIdByByCustomerAndCropType(currentUser.get().getCustomerId(),tBCropDTO.getTbCropTypeId());
            }
            for (TBGuidelineDetailsDTO guidelineDetailsDTO : guidelineDetails) {
                GuidelineDetailWrapperDTO guidelineDetailWrapperDTO = new GuidelineDetailWrapperDTO();
                guidelineDetailWrapperDTO.setGuidelineDetail(guidelineDetailsDTO);
                List<TBGuidelineActivityDTO> guidelineActivities = findActivityTypesInGuidelineDetail(guidelineDetailsDTO.getId(), listActivityTypeId);
                //TODO add list guideline details
                List<ActivityGuidelineDTO> activityGuidelines = new ArrayList<>();
                guidelineActivities.forEach(item -> {
                    ActivityGuidelineDTO activityGuidelineDTO = new ActivityGuidelineDTO();
                    activityGuidelineDTO.setId(item.getId());
                    TBActivityTypeDTO tbActivityTypeDTO = tBActivityTypeService.findOne(item.getTbActivityTypeId()).get();
                    activityGuidelineDTO.setActivityTypeName(tbActivityTypeDTO.getName());
                    activityGuidelineDTO.setActivityTypeDescription(tbActivityTypeDTO.getDescription());
                    activityGuidelines.add(activityGuidelineDTO);
                });

                guidelineDetailWrapperDTO.setActivityGuidelines(activityGuidelines);
                historyOfActivities.add(guidelineDetailWrapperDTO);
            }
            activityTimelineDTO.setGuidelineDetails(historyOfActivities);
        }

        List<TBGuidelinePeriodDTO> guidelinePeriodDTOs = tbGuidelinePeriodQueryService.findByCropId(cropId);
        if (!guidelinePeriodDTOs.isEmpty()) {
            activityTimelineDTO.setGuidelinePeriods(guidelinePeriodDTOs);
        }
        Comparator<TBActivityDTO> compareByAgeDay = Comparator.comparing( TBActivityDTO::getAgeDay );
        Comparator<TBActivityDTO> compareByExecuteDate = Comparator.comparing( TBActivityDTO::getExecuteDate );
        Comparator<TBActivityDTO> compareTimeline = compareByAgeDay.thenComparing(compareByExecuteDate);
        List<TBActivityDTO> activities = allActivities.stream()
            .sorted(compareTimeline)
            .collect(Collectors.toList());

        activityTimelineDTO.setActivities(activities);
        return activityTimelineDTO;
    }

    private void addGuidelineDetailToList(
        List<TBActivityTypeDTO> activityTypes,
        List<Object[]> guidelineDetailIds,
        TBGuidelineDetailsDTO guidelineDetailsDTO,
        List<ActivityGuidelineDTO> activityGuidelines,
        String activityType) {

        List<Long> guidelineDetailActivityIdsByGuidelineDetail = guidelineDetailIds.stream()
            .filter(x -> guidelineDetailsDTO.getId().equals(((BigInteger) x[1]).longValue()))
            .map(x -> ((BigInteger) x[0]).longValue())
            .collect(Collectors.toList());

        String activityTypeDescription = activityTypes
            .stream()
            .filter(x -> x.getName().equals(activityType))
            .map(TBActivityTypeDTO::getDescription)
            .findAny()
            .orElse(null);

        for (Long guidelineDetailActivityId : guidelineDetailActivityIdsByGuidelineDetail) {
            ActivityGuidelineDTO activityGuidelineDTO = new ActivityGuidelineDTO();
            activityGuidelineDTO.setId(guidelineDetailActivityId);
            activityGuidelineDTO.setActivityTypeName(activityType);
            activityGuidelineDTO.setActivityTypeDescription(activityTypeDescription);
            activityGuidelines.add(activityGuidelineDTO);
        }
    }

    private List<TBGuidelineActivityDTO> findActivityTypesInGuidelineDetail(Long guidelineDetailsId, List<Long> listActivityTypeId){
        String sqlString = "SELECT * \n" +
            "FROM smf.tb_guideline_activity \n" +
            "WHERE tb_guideline_details_id = :guidelineDetailsId ";
        if(listActivityTypeId.size() > 0){
            sqlString += "AND tb_activity_type_id IN (:listActivityTypeId)";
        }
        Query query = em.createNativeQuery(sqlString, TBGuidelineActivity.class);
        query.setParameter("guidelineDetailsId", guidelineDetailsId);
        if(listActivityTypeId.size() > 0){
            query.setParameter("listActivityTypeId", listActivityTypeId);
        }

        return tbGuidelineActivityMapper.toDto(query.getResultList());
    }
    private List<TBGuidelineDetailsDTO> findGuidelineDetails(Long cropId) {
        String sqlString =
            " select guideline_details.* \n" +
                " from tb_guideline_details as guideline_details \n" +
                " inner join tb_guideline as guideline on guideline_details.tb_guideline_id = guideline.id \n" +
                " inner join tb_crop as crop on crop.tb_guideline_id = guideline.id \n" +
                " left join tb_guideline_period as guideline_period on guideline_period.id = guideline_details.tb_guideline_period_id \n" +
                " where crop.id = :cropId";
        Query query = em.createNativeQuery(sqlString, TBGuidelineDetails.class);
        query.setParameter("cropId", cropId);

        return tBGuidelineDetailsMapper.toDto(query.getResultList());
    }
    private List<Object[]> getActivityGuidelineDetails(List<Long> guidelineDetailIds, String tableName) {
        Query nativeQuery = em.createNativeQuery(
            String.format(
                "select tbl.id, tbl.tb_guideline_details_id\n" +
                    " from %s as tbl\n" +
                    " where tbl.tb_guideline_details_id in (:guidelineDetailIds)", tableName));
        nativeQuery.setParameter("guidelineDetailIds", guidelineDetailIds);

        return (List<Object[]>) nativeQuery.getResultList();
    }

    public Long getAllEntityInCropOfCustomer(Long cropId) {
        //UserDTO currentUser = userService.getCurrentUserDTO().get();
        TBCropDTO tBCropDTO = tBCropService.findOne(cropId).get();
        return tBCropDTO.getNetHouseId();
    }

    private List<UserDTO> getListUserByCustomerId(Pageable pageable, Long customerId) {
        List<UserDTO> userDTOs = userService.getAllUsers(pageable).getContent();
        List<UserDTO> userDTOByCustomers = new ArrayList<>();
        for (UserDTO userDTO : userDTOs) {
            if (userDTO.getCustomerId() == customerId) {
                userDTOByCustomers.add(userDTO);
            }
        }
        return userDTOByCustomers;
    }

    @Transactional
    public void sendNotificationPersonInCharge(CropPersonInChargeDTO notificationPersonInChargeDTO,List<Long> listAssignmentPrev) {
        List<Long> listPersonRemovedInCharge = new ArrayList<Long>(listAssignmentPrev);
        TBCropDTO tbCropDTO = tBCropService.findOne(notificationPersonInChargeDTO.getCropId()).get();

        if (notificationPersonInChargeDTO.getDetailUserIds().size() > 0) {
            listPersonRemovedInCharge.removeAll(notificationPersonInChargeDTO.getDetailUserIds());
            String titleContentEnv = "";

            for (Long personInCharge : notificationPersonInChargeDTO.getDetailUserIds()) {
                if(listAssignmentPrev.contains(personInCharge)){
                    continue;
                }
                TBDetailUserDTO tbDetailUserDTO = tbDetailUserService.findOne(personInCharge).get();
                titleContentEnv = genTitlePushNotify(tbCropDTO);
                if (tbDetailUserDTO.getFcmToken() != null && !tbDetailUserDTO.getFcmToken().isEmpty()) {
                    PushNotificationRequest request = new PushNotificationRequest(Constants.NOTIFICATION_TITLE_PERSON_IN_CHARGE_UPDATE
                        , titleContentEnv
                        , tbDetailUserDTO.getFcmToken()
                        , Constants.NOTIFICATION_REQUEST_TYPE_PIC_UPDATE
                        , tbDetailUserDTO.getId()
                        , tbCropDTO.getNetHouseId()
                        , tbCropDTO.getId()
                        , tbCropDTO.getTbCropTypeId());
                    if (isTestSendNotification == 0) {
                        pushNotificationService.sendPushNotificationToToken(request);
                    } else {
                        log.info("----Send notification----- :" + titleContentEnv + " to " + tbDetailUserDTO.getFullName());
                    }
                }
            }
        }

        if(listPersonRemovedInCharge.size() > 0){
            String titleContent = "";
            for (Long personRemovedInCharge: listPersonRemovedInCharge) {
                TBDetailUserDTO tbDetailUserDTO = tbDetailUserService.findOne(personRemovedInCharge).get();
                titleContent = genTitleRemovedPersonInCharge(tbCropDTO);
                if (tbDetailUserDTO.getFcmToken() != null && !tbDetailUserDTO.getFcmToken().isEmpty()) {
                    PushNotificationRequest request = new PushNotificationRequest(Constants.NOTIFICATION_TITLE_PERSON_IN_CHARGE_UPDATE
                        , titleContent
                        , tbDetailUserDTO.getFcmToken()
                        , Constants.NOTIFICATION_REQUEST_TYPE_PIC_UPDATE
                        , tbDetailUserDTO.getId()
                        , tbCropDTO.getNetHouseId()
                        , tbCropDTO.getId()
                        , tbCropDTO.getTbCropTypeId());
                    if (isTestSendNotification == 0) {
                        pushNotificationService.sendPushNotificationToToken(request);
                    } else {
                        log.info("----Send notification----- :" + titleContent + " to " + tbDetailUserDTO.getFullName());
                    }
                }
            }
        }
    }

    private String genTitlePushNotify(TBCropDTO tbCropDTO) {
        String result = "";
        result += "Bạn được giao phụ trách trên lô " + tbCropDTO.getCode() + " của nhà màng " + tbCropDTO.getNetHouseName() + " tại khu vực " + tbCropDTO.getArea();
        return result;
    }

    private String genTitleRemovedPersonInCharge(TBCropDTO tbCropDTO) {
        String result = "";
        result += "Bạn đã bị hủy phụ trách trên lô " + tbCropDTO.getCode() + " của nhà màng " + tbCropDTO.getNetHouseName() + " tại khu vực " + tbCropDTO.getArea();
        return result;
    }

    public List<TBCropDTO> updateListToHasGuidelinePeriod(List<TBCropDTO> tbCropDTOs) {
        List<TBCropDTO> newTBCrops = new ArrayList<>();
        for(TBCropDTO tbCropDTO : tbCropDTOs) {

            //Get current guideline period of tbCrop
            TBGuidelinePeriodDTO tbGuidelinePeriodDTO = getTBGuidelinePeriodByCrop(tbCropDTO);

            //Set current guideline period to tbCrop
            if(tbGuidelinePeriodDTO != null) {
                tbCropDTO.setTbGuidelinePeriodId(tbGuidelinePeriodDTO.getId());
                tbCropDTO.setTbGuidelinePeriodName(tbGuidelinePeriodDTO.getName());
            }
            newTBCrops.add(tbCropDTO);
        }
        return newTBCrops;
    }

    public TBGuidelinePeriodDTO getTBGuidelinePeriodByCrop(TBCropDTO tbCropDTO) {
        Long ageDay = Math.abs(ChronoUnit.DAYS.between(Instant.now(), tbCropDTO.getStartDate()));
        Integer ageDayStartAt = tbCropDTO.getAgeDayStartAt() != null ? tbCropDTO.getAgeDayStartAt() : 0;
        Integer finalAgeDay = ((Long)(ageDayStartAt + ageDay)).intValue();

        TBGuidelinePeriodCriteria criteria = new TBGuidelinePeriodCriteria();
        IntegerFilter ageDayFilter = new IntegerFilter();
        ageDayFilter.equals(finalAgeDay);
        criteria.setAgeDayStart(ageDayFilter);

        List<TBGuidelinePeriodDTO> tbGuidelinePeriodDTOs= tbGuidelinePeriodQueryService.findByCriteria(criteria);
        if(tbGuidelinePeriodDTOs != null && tbGuidelinePeriodDTOs.size() > 0) {
            return  tbGuidelinePeriodDTOs.get(0);
        }
        return null;
    }
}
