package com.bcxin.tenant.open.domains.services.impls;

import com.bcxin.tenant.open.document.domains.documents.RdEmployeeDocument;
import com.bcxin.tenant.open.document.domains.documents.RdSecurityStationDocument;
import com.bcxin.tenant.open.document.domains.repositories.RdEmployeeDocumentRepository;
import com.bcxin.tenant.open.document.domains.repositories.RdSecurityStationDocumentRepository;
import com.bcxin.tenant.open.document.domains.utils.GeoPointUtils;
import com.bcxin.tenant.open.domains.components.CacheProvider;
import com.bcxin.tenant.open.domains.components.HotCacheManager;
import com.bcxin.tenant.open.domains.dtos.SystemSettingDTO;
import com.bcxin.tenant.open.domains.entities.TenantEmployeeAttendanceEntity;
import com.bcxin.tenant.open.domains.repositories.AttendanceRepository;
import com.bcxin.tenant.open.domains.services.AttendanceService;
import com.bcxin.tenant.open.domains.services.commands.BatchAttendanceCommitCommand;
import com.bcxin.tenant.open.domains.services.commands.CleanRecentlyAttendanceCommitCommand;
import com.bcxin.tenant.open.domains.services.commands.CreateAttendanceCommand;
import com.bcxin.tenant.open.domains.services.commands.ValidateAttendanceCommand;
import com.bcxin.tenant.open.domains.services.commands.results.ValidateAttendanceCommandResult;
import com.bcxin.tenant.open.infrastructures.UnitWork;
import com.bcxin.tenant.open.infrastructures.components.IdWorker;
import com.bcxin.tenant.open.infrastructures.components.JsonProvider;
import com.bcxin.tenant.open.infrastructures.constants.BusinessConstants;
import com.bcxin.tenant.open.infrastructures.constants.KafkaConstants;
import com.bcxin.tenant.open.infrastructures.enums.ReferenceType;
import com.bcxin.tenant.open.infrastructures.enums.ResourceType;
import com.bcxin.tenant.open.infrastructures.exceptions.*;
import com.bcxin.tenant.open.infrastructures.utils.BusinessUtil;
import com.bcxin.tenant.open.infrastructures.valueTypes.TrafficTagValueType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.geo.Point;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;


/**
 *  警情上报
 */
@Service
public class AttendanceServiceImpl implements AttendanceService {
    private static Logger logger = LoggerFactory.getLogger(AttendanceService.class);

    private final UnitWork unitWork;
    private final AttendanceRepository attendanceRepository;
    private final RdEmployeeDocumentRepository employeeDocumentRepository;
    private final RdSecurityStationDocumentRepository securityStationDocumentRepository;
    private final JsonProvider jsonProvider;
    private final CacheProvider cacheProvider;
    private final KafkaTemplate kafkaTemplate;
    private final TrafficTagValueType tagValueType;

    private final IdWorker idWorker;

    public AttendanceServiceImpl(UnitWork unitWork, AttendanceRepository attendanceRepository,
                                 RdEmployeeDocumentRepository employeeDocumentRepository,
                                 RdSecurityStationDocumentRepository securityStationDocumentRepository,
                                 JsonProvider jsonProvider, CacheProvider cacheProvider,
                                 KafkaTemplate kafkaTemplate, TrafficTagValueType tagValueType, IdWorker idWorker) {
        this.unitWork = unitWork;
        this.attendanceRepository = attendanceRepository;
        this.employeeDocumentRepository = employeeDocumentRepository;
        this.securityStationDocumentRepository = securityStationDocumentRepository;
        this.jsonProvider = jsonProvider;
        this.cacheProvider = cacheProvider;
        this.kafkaTemplate = kafkaTemplate;
        this.tagValueType = tagValueType;
        this.idWorker = idWorker;
    }

    @Override
    public void dispatch(CreateAttendanceCommand command) {
        /**
         * 先放消息队列, 在通过队列进行批量插入数据库
         */
        TenantEmployeeAttendanceEntity entity = new TenantEmployeeAttendanceEntity();
        entity.setAddress(command.getAddress());
        entity.setNote(command.getNote());
        entity.setLonLat(command.getLonLat());
        entity.setCreatedTime(Timestamp.from(Instant.now()));
        entity.setRecordStatus(command.getRecordStatus());
        entity.setAttachments(this.jsonProvider.getJson(command.getAttachments()));

        entity.setTenantEmployeeId(command.getTenantEmployeeId());
        entity.setReferenceType(command.getReferenceType());
        entity.setReferenceNumber(command.getReferenceNumber());
        /**
         * 如下两个将在插入数据库的时候执行
         */
        entity.setTenantUserId(BusinessConstants.DefaultEmptyValue);
        entity.setOrganizationId(BusinessConstants.DefaultEmptyValue);
        entity.setExtendJsonInfo(this.jsonProvider.getJson(command.getItems()));
        /*
        entity.setName(document.getName());
        entity.setIdCardNo(document.getIdCardNo());
        entity.setCompanyName(document.getCompanyName());
        entity.setSuperviseDepartId(superviseDepartId);
        entity.setSuperviseDepartName(superviseDepartName);
        entity.setCalculatedResourceType(ResourceType.getCalculatedResourceValue(resourceTypes));
        entity.setSecurityCertificateNo(StringUtils.hasLength(document.getSecurityCertificateNo()) ? document.getSecurityCertificateNo() : BusinessConstants.DefaultEmptyValue);
   */
        try {
            entity.setHeaderAgent(command.getHeaderOptionValue().toString());
        }
        catch (Exception ex) {
            //todo: 忽略此异常
            entity.setHeaderAgent(String.format("找不到头信息:%s", ex.getMessage()));
        }

        int partition = KafkaConstants.calculatePartition(entity.getTenantEmployeeId());
        String data = this.jsonProvider.getJson(entity);
        this.kafkaTemplate.send(
                KafkaConstants.TOPIC_DISPATCH_ATTENDANCE,
                partition,
                entity.getTenantEmployeeId(),
                data
        );
    }

    @Override
    public ValidateAttendanceCommandResult dispatch(ValidateAttendanceCommand command) {
        ValidateAttendanceCommandResult commandResult =
                HotCacheManager.get(HotCacheManager.CacheDataType.SecurityStation, command.getReferenceNumber());

        if (commandResult != null) {
            boolean enableFaceValidation = getEnableFaceValidation();

            commandResult.assignEnableFaceValidation(enableFaceValidation);

            return commandResult;
        }

        if (command.getReferenceType() == ReferenceType.Station) {
            if (!StringUtils.hasLength(command.getReferenceNumber())) {
                throw new NoFoundTenantException("驻勤点/岗点信息无效");
            }
            RdSecurityStationDocument securityStationDocument =
                    securityStationDocumentRepository.findById(command.getReferenceNumber()).orElse(null);
            if (securityStationDocument == null) {
                throw new NoFoundTenantException(String.format("该职员所在的驻勤点无效(%s)", command.getReferenceNumber()));
            }

            if (securityStationDocument.getPerformRange() == null) {
                throw new NoAllowedTenantException("驻勤点未设置执行范围, 因此无法执行签到/签退操作");
            }

            /**
             * 针对赛演临保项目, 赛演之前直接忽略签到范围
             */
            boolean ignoredPerformRangeLimited = BusinessUtil.checkIfCommunityPolicing(securityStationDocument.getInstitutional());
            if (BusinessUtil.isEventProject(securityStationDocument.getProjectId())) {
                if (securityStationDocument.getBeginDate() != null && securityStationDocument.getBeginDate().after(new Date())) {
                    ignoredPerformRangeLimited = true;
                }
            }

            commandResult = ValidateAttendanceCommandResult.create(
                    securityStationDocument.getId(),
                    securityStationDocument.getName(),
                    securityStationDocument.getAddress(),
                    securityStationDocument.getLonLat() == null ? null : securityStationDocument.getLonLat().getY(),
                    securityStationDocument.getLonLat() == null ? null : securityStationDocument.getLonLat().getX(),
                    securityStationDocument.getPerformRange(),
                    ignoredPerformRangeLimited
            );

            boolean enableFaceValidation = getEnableFaceValidation();

            commandResult.assignEnableFaceValidation(enableFaceValidation);

            HotCacheManager.put(HotCacheManager.CacheDataType.SecurityStation, command.getReferenceNumber(), commandResult);
        } else {
            commandResult = checkAttendanceSettings();
        }

        return commandResult;
    }

    @Override
    public void dispatch(BatchAttendanceCommitCommand command) {

        Collection<TenantEmployeeAttendanceEntity> data =
                command.getData().stream()
                        .map(ii -> {
                            TenantEmployeeAttendanceEntity entity = this.jsonProvider.toObject(TenantEmployeeAttendanceEntity.class, ii);
                            try {
                                if (StringUtils.hasLength(entity.getHeaderAgent()) && entity.getHeaderAgent().length() > 500) {
                                    entity.setHeaderAgent(entity.getHeaderAgent().substring(0, 500));
                                    logger.error("该签到数据的长度大于500:{}-{}-{}", entity.getTenantEmployeeId(), entity.getCreatedTime(), entity.getHeaderAgent());
                                }
                            } catch (Exception ex) {
                                //todo: 以防万一异常
                                logger.error("截取签到数据发生异常:{}-{}-{}",
                                        entity.getTenantEmployeeId(),
                                        entity.getCreatedTime(),
                                        entity.getHeaderAgent(), ex);
                            }

                            if (entity.getRecordStatus() == null) {
                                logger.error("异常签到数据:empId={}-time={}-ha={}-rs={}",
                                        entity.getTenantEmployeeId(), entity.getCreatedTime(),
                                        entity.getHeaderAgent(), entity.getRecordStatus());
                                return null;
                            }

                            return entity;
                        })
                        .filter(ii -> ii.getRecordStatus() != null).collect(Collectors.toList());
        Collection<String> employeeIds =
                data.stream().map(ii -> ii.getTenantEmployeeId()).distinct().collect(Collectors.toList());

        Collection<RdEmployeeDocument> employeeDocuments =
                this.employeeDocumentRepository.findAllById(employeeIds);
        data.forEach(entity -> {
            RdEmployeeDocument document =
                    employeeDocuments.stream().filter(ix -> ix.getId()
                                    .equalsIgnoreCase(entity.getTenantEmployeeId()))
                            .findFirst().orElse(null);
            try {
                if (document != null) {
                    String superviseDepartId = "#1";
                    String superviseDepartName = "#1.";

                    Set<ResourceType> resourceTypes = ResourceType.toResourceTypes(document.getResourceTypes());
                    if (StringUtils.hasLength(document.getSuperviseDepartId()) &&
                            !BusinessConstants.DefaultEmptyValue.equalsIgnoreCase(document.getSuperviseDepartId())) {
                        superviseDepartId = document.getSuperviseDepartId();
                        superviseDepartName = document.getSuperviseDepartName();
                    }
                    Point point = null;
                    if (entity.getLonLat() != null) {
                        //point = new Point(command.getLonLat().getLon(), command.getLonLat().getLat());
                        point = GeoPointUtils.translateFromLocation(entity.getLonLat().getLon(), entity.getLonLat().getLat());
                    }

                    //实时同步到RedisDocument, 但是 todo: 定期到RdEmployeeEntity的表中
                    document.changeDutyStatus(entity.getRecordStatus(), point);

                    entity.setTenantUserId(document.getTenantUserId());
                    entity.setOrganizationId(document.getOrganizationId());

                    entity.setName(document.getName());
                    entity.setIdCardNo(document.getIdCardNo());
                    entity.setCompanyName(document.getCompanyName());
                    entity.setSuperviseDepartId(superviseDepartId);
                    entity.setSuperviseDepartName(superviseDepartName);
                    entity.setCalculatedResourceType(ResourceType.getCalculatedResourceValue(resourceTypes));

                    entity.setSecurityCertificateNo(StringUtils.hasLength(document.getSecurityCertificateNo()) ? document.getSecurityCertificateNo() : BusinessConstants.DefaultEmptyValue);
                } else {
                    entity.setTenantUserId(BusinessConstants.DefaultEmptyValue);
                    entity.setOrganizationId(BusinessConstants.DefaultEmptyValue);
                    entity.setName(BusinessConstants.DefaultEmptyValue);
                    entity.setIdCardNo(BusinessConstants.DefaultEmptyValue);
                    entity.setCompanyName(BusinessConstants.DefaultEmptyValue);
                    entity.setSuperviseDepartId(BusinessConstants.DefaultEmptyValue);
                    entity.setSuperviseDepartName(BusinessConstants.DefaultEmptyValue);
                }
            } catch (Exception ex) {
                logger.error("签到数异常:tenant-employee-id={},record-status={},time={},", entity.getTenantEmployeeId(), entity.getRecordStatus(), entity.getCreatedTime(), ex);
            }
        });

        data = data.stream()
                .filter(ii -> employeeDocuments.stream().anyMatch(ix -> ix.getId().equalsIgnoreCase(ii.getTenantEmployeeId())))
                .collect(Collectors.toList());
        this.employeeDocumentRepository.saveAll(employeeDocuments);

        Collection<TenantEmployeeAttendanceEntity> stationAttendances = data
                .stream().filter(ii -> ii.getReferenceType() == ReferenceType.Station)
                .collect(Collectors.toList());

        Collection<TenantEmployeeAttendanceEntity> jobStations = data
                .stream().filter(ii -> ii.getReferenceType() == ReferenceType.JobStation)
                .collect(Collectors.toList());

        Collection<TenantEmployeeAttendanceEntity> excludeStations = data
                .stream().filter(ii -> ii.getReferenceType() != ReferenceType.Station && ii.getReferenceType()!=ReferenceType.JobStation)
                .collect(Collectors.toList());


        if(!CollectionUtils.isEmpty(data)) {
            String tranId = this.unitWork.beginTransaction();
            try {
                this.attendanceRepository.batchInsert(excludeStations);
                this.attendanceRepository.batchInsertForStation(stationAttendances);
                this.attendanceRepository.batchInsertForJobStation(jobStations);
                this.unitWork.commit(tranId);
            } catch (Exception ex) {
                this.unitWork.rollback(tranId);

                throw ex;
            }
        }
    }

    @Override
    public int dispatch(CleanRecentlyAttendanceCommitCommand command) {
        String tranId = this.unitWork.beginTransaction();
        int affectedCount =-0;
        try {
            affectedCount = this.attendanceRepository.cleanRecentlyRecords();
            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);

            throw ex;
        }

        return affectedCount;
    }

    @Override
    public boolean checkIfEnableFaceValidation() {
        return this.getEnableFaceValidation();
    }

    private ValidateAttendanceCommandResult checkAttendanceSettings() {
        ValidateAttendanceCommandResult commandResult = ValidateAttendanceCommandResult.create(
                null,
                "",
                "",
                null,
                null,
                0d,
                true
        );

        boolean enableFaceValidation = getEnableFaceValidation();

        commandResult.assignEnableFaceValidation(enableFaceValidation);

        return commandResult;
    }

    private boolean getEnableFaceValidation() {
        Boolean enableFaceValidation = HotCacheManager.get(HotCacheManager.CacheDataType.EnableFaceValidation, HotCacheManager.KEY_ENABLE_FACE_VALIDATION);
        if (enableFaceValidation == null) {
            try {
                SystemSettingDTO settingDTO =
                        this.cacheProvider.get(SystemSettingDTO.CACHE_KEY, () -> {
                            return SystemSettingDTO.getDefault();
                        }, SystemSettingDTO.class);

                if (settingDTO != null) {
                    enableFaceValidation = settingDTO.isEnableFaceValidation();

                    HotCacheManager.put(HotCacheManager.CacheDataType.EnableFaceValidation, HotCacheManager.KEY_ENABLE_FACE_VALIDATION, enableFaceValidation);
                }
            } catch (Exception ex) {
                logger.error("验证签到前置条件发生异常", ex);
            }
        }

        return enableFaceValidation;
    }
}
