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

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
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.domains.entities.RdSecurityStationRailEntity;
import com.bcxin.tenant.open.domains.entities.RdSecurityStationRailMessageEntity;
import com.bcxin.tenant.open.domains.repositories.RdSecurityStationRailMessageRepository;
import com.bcxin.tenant.open.domains.repositories.RdSecurityStationRailRepository;
import com.bcxin.tenant.open.domains.services.RdSecurityStationRailService;
import com.bcxin.tenant.open.domains.services.commands.CheckStationRailWarningCommand;
import com.bcxin.tenant.open.domains.services.commands.CreateRdSecurityStationRailCommand;
import com.bcxin.tenant.open.domains.services.commands.DeleteRdSecurityStationRailCommand;
import com.bcxin.tenant.open.domains.services.commands.UpdateRdSecurityStationRailCommand;
import com.bcxin.tenant.open.infrastructures.TenantContext;
import com.bcxin.tenant.open.infrastructures.TenantEmployeeContext;
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.enums.*;
import com.bcxin.tenant.open.infrastructures.exceptions.BadTenantException;
import com.bcxin.tenant.open.infrastructures.exceptions.NoAllowedTenantException;
import com.bcxin.tenant.open.infrastructures.exceptions.NoFoundTenantException;
import com.bcxin.tenant.open.infrastructures.utils.GisPointUtil;
import com.bcxin.tenant.open.infrastructures.utils.StringUtil;
import com.bcxin.tenant.open.infrastructures.valueTypes.GeoLocationValueType;
import com.bcxin.tenant.open.infrastructures.valueTypes.LonLatValueType;
import com.bcxin.tenant.open.infrastructures.valueTypes.RdSecurityStationRailSnapshootValueType;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.geo.Point;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class RdSecurityStationRailServiceImpl implements RdSecurityStationRailService {
    private static final Logger logger = LoggerFactory.getLogger(RdSecurityStationRailServiceImpl.class);
    private final UnitWork unitWork;
    private final IdWorker idWorker;
    private final JsonProvider jsonProvider;
    private final RdSecurityStationDocumentRepository securityStationDocumentRepository;
    private final RdSecurityStationRailRepository rdSecurityStationRailRepository;
    private final RdSecurityStationRailMessageRepository rdSecurityStationRailMessageRepository;
    private final RdEmployeeDocumentRepository employeeDocumentRepository;

    public RdSecurityStationRailServiceImpl(UnitWork unitWork, IdWorker idWorker,
                                            JsonProvider jsonProvider,
                                            RdSecurityStationDocumentRepository securityStationDocumentRepository,
                                            RdSecurityStationRailRepository rdSecurityStationRailRepository,
                                            RdSecurityStationRailMessageRepository rdSecurityStationRailMessageRepository,
                                            RdEmployeeDocumentRepository employeeDocumentRepository) {
        this.unitWork = unitWork;
        this.idWorker = idWorker;
        this.jsonProvider = jsonProvider;
        this.securityStationDocumentRepository = securityStationDocumentRepository;
        this.rdSecurityStationRailRepository = rdSecurityStationRailRepository;
        this.rdSecurityStationRailMessageRepository = rdSecurityStationRailMessageRepository;
        this.employeeDocumentRepository = employeeDocumentRepository;
    }

    @Override
    public long dispatch(CreateRdSecurityStationRailCommand command) {
        command.validate();
        if(StringUtil.isEmpty(command.getOwnerOrganizationId())){
            throw new BadTenantException("电子围栏所属驻勤点的组织不能为空");
        }
        String stationName = command.getStationName();
        String superviseDepartId = command.getSuperviseDepartId();
        String superviseDepartName = command.getSuperviseDepartName();
        if (!command.isFromStationManagement()) {
            RdSecurityStationDocument document =
                    securityStationDocumentRepository.findById(command.getStationId())
                            .orElse(null);
            if (document == null) {
                throw new NoFoundTenantException(String.format("找不到该驻勤点(%s)信息", command.getStationId()));
            }

            stationName = document.getName();
            superviseDepartId = document.getSuperviseDepartId();
            superviseDepartName = document.getSuperviseDepartName();
        }

        long id = this.idWorker.getNextId();
        String tranId = this.unitWork.beginTransaction();
        try {
            TenantEmployeeContext userContext =
                    TenantContext.getInstance().getUserContext();
            String userId = userContext.get().getEmployeeId();
            String userName = userContext.get().getName();
            String areaJson =
                    getAreaJson(command.getShapeType(), command.getShapedLocation());

            RdSecurityStationRailEntity securityStationRail =
                    RdSecurityStationRailEntity.create(id,
                            userContext.get().getOrganizationId(),
                            command.getName(),
                            command.getShapeType(), areaJson, RailBusinessType.Station,
                            command.getStationId(),
                            stationName,
                            command.getBeginTime(), command.getEndTime(),
                            command.getRuleType(),
                            userId,
                            command.getNote(),
                            superviseDepartId,
                            superviseDepartName,
                            userName,
                            command.getOwnerOrganizationId()
                    );
            this.rdSecurityStationRailRepository.insert(securityStationRail);

            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);

            logger.error("CreateRdSecurityStationRailCommand 发生异常", ex);

            throw ex;
        }

        return id;
    }

    @Override
    public void dispatch(UpdateRdSecurityStationRailCommand command) {
        command.validate();

        RdSecurityStationRailEntity securityStationRail =
                this.rdSecurityStationRailRepository.getById(command.getId());
        if (securityStationRail == null || securityStationRail.isDeleted()) {
            throw new NoFoundTenantException("找不到该电子围栏");
        }

        String tranId = this.unitWork.beginTransaction();
        try {
            String areaJson = getAreaJson(command.getShapeType(), command.getShapedLocation());
            TenantEmployeeContext userContext = TenantContext.getInstance().getUserContext();
            String userId = userContext.get().getEmployeeId();
            String userName = userContext.get().getName();
            String stationName = securityStationRail.getReferenceName();
            String superviseDepartId = securityStationRail.getSuperviseDepartId();
            String superviseDepartName = securityStationRail.getSuperviseDepartName();

            if (command.isFromStationManagement()) {
                stationName = command.getStationName();
                superviseDepartId = command.getSuperviseDepartId();
                superviseDepartName = command.getSuperviseDepartName();
            } else if (!StringUtil.isEqual(command.getStationId(), securityStationRail.getReferenceNumber())) {
                Optional<RdSecurityStationDocument> documentOptional =
                        securityStationDocumentRepository.findById(command.getStationId());
                if (!documentOptional.isPresent()) {
                    throw new NoFoundTenantException(String.format("找不到该驻勤点(%s)信息", command.getStationId()));
                }

                stationName = documentOptional.get().getName();
                superviseDepartId = documentOptional.get().getSuperviseDepartId();
                superviseDepartName = documentOptional.get().getSuperviseDepartName();
            }

            securityStationRail.change(
                    command.getName(),
                    command.getShapeType(), areaJson,
                    securityStationRail.getBusinessType(),
                    command.getStationId(),
                    stationName,
                    command.getBeginTime(),
                    command.getEndTime(),
                    command.getRuleType(),
                    userId,
                    command.getNote(),
                    superviseDepartId,
                    superviseDepartName,
                    userName);

            this.rdSecurityStationRailRepository.update(securityStationRail);

            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }
    }

    @Override
    public void dispatch(DeleteRdSecurityStationRailCommand command) {
        RdSecurityStationRailEntity securityStationRail =
                this.rdSecurityStationRailRepository.getById(command.getId());
        if (securityStationRail == null || securityStationRail.isDeleted()) {
            throw new NoFoundTenantException("找不到该电子围栏");
        }

        TenantEmployeeContext userContext = TenantContext.getInstance().getUserContext();
        String userId = userContext.get().getEmployeeId();
        String userName = userContext.get().getName();
        if (!StringUtil.isEqual(securityStationRail.getOrganizationId(), userContext.get().getOrganizationId())) {
            throw new NoAllowedTenantException("该驻勤点非该组织创建, 因此, 无法删除");
        }

        String tranId = this.unitWork.beginTransaction();
        try {
            securityStationRail.markAsDeleted(userId, userName);

            this.rdSecurityStationRailRepository.update(securityStationRail);

            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }
    }

    @Override
    public void dispatch(CheckStationRailWarningCommand command) {
        Collection<String> employeeIds = command.getEmployeeLocations()
                .stream()
                .filter(ii->!CollectionUtils.isEmpty(ii.getEmployeeIds()))
                .flatMap(ix -> ix.getEmployeeIds().stream())
                .filter(ix -> StringUtils.hasLength(ix))
                .distinct()
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(employeeIds)) {
            return;
        }

        Collection<RdEmployeeDocument> employeeDocuments
                = this.employeeDocumentRepository.findAllById(employeeIds);
        if (CollectionUtils.isEmpty(employeeDocuments)) {
            return;
        }

        try {
            Collection<String> stationIds =
                    employeeDocuments.stream()
                            .map(ix -> ix.getSecurityStationId())
                            .distinct()
                            .collect(Collectors.toList());

            Collection<RdSecurityStationRailEntity> stationRails =
                    this.rdSecurityStationRailRepository.getValidRails(stationIds);
            if (CollectionUtils.isEmpty(stationRails)) {
                logger.warn("未找到对应驻勤({})的电子围栏信息", stationIds.stream().collect(Collectors.joining(";")));
                return;
            }

            Collection<RdSecurityStationRailMessageEntity> railMessages = new ArrayList<>();

            /**
             * 获取驻勤点信息
             */
            Collection<RdSecurityStationDocument> securityStationDocuments = new ArrayList<>();
            stationRails.forEach(str -> {
                if(!StringUtils.hasLength(str.getReferenceNumber()) || str.getBusinessType()!=RailBusinessType.Station) {
                    return;
                }

                if (!StringUtil.isEmpty(str.getAreaJson())) {
                    RdSecurityStationRailSnapshootValueType.ShapedLocationValueType
                            shapedLocationValueType =
                            this.jsonProvider.toObject(RdSecurityStationRailSnapshootValueType.ShapedLocationValueType.class, str.getAreaJson());

                    if (shapedLocationValueType != null && shapedLocationValueType.getGeos() != null) {
                        Collection<LonLatValueType> validLocations = shapedLocationValueType.getGeos().stream()
                                .filter(ix -> ix != null).collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(validLocations)) {
                            command.getEmployeeLocations().forEach(eln -> {
                                Optional<RdEmployeeDocument> employeeDocumentOptional =
                                        employeeDocuments.stream()
                                                .filter(ix ->
                                                        eln.getEmployeeIds().stream().anyMatch(ik->ik.equalsIgnoreCase(ix.getTenantEmployeeId()))
                                                ).findFirst();
                                if (!employeeDocumentOptional.isPresent()) {
                                    return;
                                }

                                /**
                                 * 添加电子围栏的告警功能
                                 */
                                RdEmployeeDocument employeeDocument = employeeDocumentOptional.get();

                                /**
                                 * 驻勤点不匹配的情况下, 不生成消息
                                 */
                                if(!StringUtils.hasLength(employeeDocument.getSecurityStationId()) ||
                                        !employeeDocument.getSecurityStationId().equalsIgnoreCase(str.getReferenceNumber())) {
                                    return;
                                }

                                boolean isMatchedWarning = false;
                                boolean addNewMessage = false;
                                try {
                                    switch (str.getShapeType()) {
                                        /**
                                         * 是否在圆型内部
                                         */
                                        case Circle -> {
                                            LonLatValueType selectedLocation = validLocations.stream().findFirst().get();

                                            boolean isInCircle =
                                                    GisPointUtil.isInCircle(
                                                            eln.getLongitude(), eln.getLatitude(),
                                                            selectedLocation.getLon(), selectedLocation.getLat(),
                                                            shapedLocationValueType.getRadius());
                                            if (str.getRuleType() == RailRuleType.Exit) {
                                                if (!isInCircle) {
                                                    isMatchedWarning = true;
                                                }
                                            } else if (isInCircle) {
                                                isMatchedWarning = true;
                                            }
                                        }
                                        /**
                                         * 多边形
                                         */
                                        default -> {
                                            /**
                                             * x是经度 lon
                                             * y是纬度 lat
                                             */
                                            JSONArray array = new JSONArray();
                                            for (LonLatValueType ix : validLocations) {
                                                JSONObject jo = new JSONObject();
                                                jo.put("x", ix.getLon());
                                                jo.put("y", ix.getLat());

                                                array.add(jo);
                                            }

                                            boolean isInPolygon =
                                                    GisPointUtil.isInPolygon(
                                                            eln.getLongitude(), eln.getLatitude(),
                                                            array);
                                            if (str.getRuleType() == RailRuleType.Exit) {
                                                if (!isInPolygon) {
                                                    isMatchedWarning = true;
                                                }
                                            } else if (isInPolygon) {
                                                isMatchedWarning = true;
                                            }
                                        }
                                    }

                                    if (isMatchedWarning) {
                                        if (employeeDocument.hasMatchedStationRailMessage(String.valueOf(str.getPkId()))) {
                                            logger.error("该电子围栏的信息已经记录到该人员; 因此；我们将不进行重复记录");
                                            return;
                                        }

                                        addNewMessage=true;

                                        /**
                                         * 查找匹配的驻勤点
                                         */
                                        Optional<RdSecurityStationDocument> stationDocumentOptional =
                                                securityStationDocuments.stream()
                                                        .filter(ix -> ix.getId().equalsIgnoreCase(str.getReferenceNumber()) &&
                                                                str.getBusinessType() == RailBusinessType.Station)
                                                        .findFirst();


                                        if (!stationDocumentOptional.isPresent()) {
                                            stationDocumentOptional = this.securityStationDocumentRepository.findById(str.getReferenceNumber());
                                            if (stationDocumentOptional.isPresent()) {
                                                securityStationDocuments.add(stationDocumentOptional.get());
                                            } else {
                                                logger.error("找不到该电子围栏({})的驻勤点({})信息;", str.getPkId(), str.getReferenceNumber());
                                                return;
                                            }
                                        }

                                        employeeDocument.addStationRailMessage(String.valueOf(str.getPkId()));
                                        /**
                                         * 先马上保存, 避免重复执行
                                         */
                                        this.employeeDocumentRepository.save(employeeDocument);

                                        RdSecurityStationDocument stationDocument =
                                                stationDocumentOptional.get();


                                        GeoLocationValueType geoLocation =
                                                GeoLocationValueType.create(
                                                        eln.getLatitude(), eln.getLongitude(),
                                                        GeoLocationValueType.AddressRequest.create(
                                                                eln.getCountry(),
                                                                eln.getProvince(),
                                                                eln.getCity(),
                                                                eln.getDistrict(),
                                                                eln.getStreet(),
                                                                eln.getStreetNum(),
                                                                eln.getPoiName(),
                                                                eln.getCityCode()
                                                        ));

                                        RdSecurityStationRailMessageEntity messageEntity
                                                = RdSecurityStationRailMessageEntity.create(
                                                idWorker.getNextId(),
                                                eln.getEventTime(),
                                                employeeDocument.getId(),
                                                employeeDocument.getName(),
                                                employeeDocument.getOrganizationId(),
                                                employeeDocument.getCompanyName(),
                                                jsonProvider.getJson(geoLocation),
                                                str.getPkId(),
                                                str.getName(),
                                                str.getRuleType(),
                                                str.getShapeType(),
                                                str.getBusinessType(),
                                                str.getAreaJson(),
                                                str.getReferenceNumber(),
                                                str.getReferenceName(),
                                                stationDocument.getAddress(),
                                                stationDocument.getSuperviseDepartId(),
                                                stationDocument.getSuperviseDepartName(),
                                                employeeDocument.getIdCardNo(),
                                                ResourceType.toResourceTypes(employeeDocument.getResourceTypes())
                                        );

                                        railMessages.add(
                                                messageEntity
                                        );
                                    } else {
                                        employeeDocument.removeFromStationRailMessage(String.valueOf(str.getPkId()));
                                    }
                                }
                                finally {
                                    /*
                                    logger.error("该保安人员的实时({}})记录(满足={}) 电子围栏(RuleType={};pkId={};json={})的规则(addNewMessage={};)",
                                            eln,
                                            isMatchedWarning,
                                            str.getRuleType().getTypeName(),
                                            str.getPkId(), str.getAreaJson(), addNewMessage
                                    );
                                     */
                                }
                            });
                        }
                    }
                }
            });

            /**
             * 扩展: 如果没有再规定的时间内实时上报; 也产生预警
             * todo:....
             */

            /**
             * 出现跨出的情况才进行计算
             */
            if (!CollectionUtils.isEmpty(railMessages)) {
                String tranId = this.unitWork.beginTransaction();
                try {
                    this.rdSecurityStationRailMessageRepository.insertAll(railMessages);
                    this.unitWork.commit(tranId);
                } catch (Exception ex) {
                    this.unitWork.rollback(tranId);
                    throw ex;
                }
            }
        } finally {
            doUpdateEmployeeLocation(command, employeeDocuments);
        }
    }

    private String getAreaJson(
            RailShapeType railShapeType,
            RdSecurityStationRailSnapshootValueType.ShapedLocationValueType shapedLocationValueType) {
        if (shapedLocationValueType == null || CollectionUtils.isEmpty(shapedLocationValueType.getGeos())) {
            throw new BadTenantException("电子围栏的坐标信息不能为空");
        }

        if (railShapeType == RailShapeType.Circle && shapedLocationValueType.getRadius() == null) {
            throw new BadTenantException("园型的电子围栏必须设置Radius半径大小");
        }

        return this.jsonProvider.getJson(shapedLocationValueType);
    }

    private void doUpdateEmployeeLocation(
            CheckStationRailWarningCommand command,Collection<RdEmployeeDocument> employeeDocuments) {
        if (CollectionUtils.isEmpty(employeeDocuments) || CollectionUtils.isEmpty(command.getEmployeeLocations())) {
            return;
        }

        Collection<String> selectedStationIds
                = employeeDocuments.stream()
                .filter(ii -> ii.getDutySignInEnumType()== DutySignInType.SignIn &&
                        !BusinessConstants.DefaultEmptyValue.equalsIgnoreCase(ii.getSecurityStationId()))
                .map(ii -> ii.getSecurityStationId()).distinct()
                .collect(Collectors.toList());
        Collection<RdSecurityStationDocument> securityStationDocuments = new ArrayList<>();
        if(!CollectionUtils.isEmpty(selectedStationIds)) {
            securityStationDocuments
                    = this.securityStationDocumentRepository.findAllById(selectedStationIds);
        }

        try {
            for (RdEmployeeDocument document : employeeDocuments) {
                Collection<CheckStationRailWarningCommand.StationEmployeeLocation> locations =
                        command.getEmployeeLocations().stream()
                                .filter(ii -> ii.getEmployeeIds().stream().anyMatch(ik->ik.equalsIgnoreCase(document.getId())))
                                .collect(Collectors.toList());
                for (CheckStationRailWarningCommand.StationEmployeeLocation el : locations) {
                    Point point = new Point(el.getLongitude(), el.getLatitude());
                    try {
                        /**
                         * 如果当前是签到状态, 那么自己调整为离岗状态
                         */
                        if (document.getDutySignInEnumType()==DutySignInType.SignIn &&
                                (document.getLastDutyStatusChangedTime() == null || el.getEventTime() == null ||
                                document.getLastDutyStatusChangedTime().before(el.getEventTime()))) {

                            RdSecurityStationDocument matchedStationDoc =
                                    securityStationDocuments.stream().filter(ii -> StringUtil.isEqual(ii.getId(), document.getSecurityStationId()))
                                            .findFirst().orElse(null);
                            if (matchedStationDoc != null) {
                                Point stationLonLat = matchedStationDoc.getLonLat();
                                double longitude = stationLonLat.getX();
                                double latitude = stationLonLat.getY();

                                if (stationLonLat != null) {
                                    double distance = GisPointUtil.getDistance(
                                            el.getLatitude(),
                                            el.getLongitude(),
                                            latitude,
                                            longitude);
                                    if (distance >= matchedStationDoc.getPerformRange() + 500) {
                                        document.makeDispatchable(false, String.format("由于跨出驻勤范围(距离=%s), 所以设置为离岗状态(非签退操作)", distance));
                                    } else {
                                        document.makeDispatchable(true, String.format("回到驻勤的范围(距离=%s)内", distance));
                                    }
                                }
                            }
                        }
                    } finally {
                        document.changeLonLat(point);
                    }
                }
            }
        } finally {
            this.employeeDocumentRepository.saveAll(employeeDocuments);
        }
    }
}
