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

import com.bcxin.tenant.open.document.domains.documents.*;
import com.bcxin.tenant.open.document.domains.repositories.RdCompanyDocumentRepository;
import com.bcxin.tenant.open.document.domains.repositories.RdDispatchDataScopeDocumentRepository;
import com.bcxin.tenant.open.document.domains.repositories.RdEmployeeDocumentRepository;
import com.bcxin.tenant.open.domains.dtos.UpdateEmployeeLocationDTO;
import com.bcxin.tenant.open.domains.entities.RdEmployeeEntity;
import com.bcxin.tenant.open.domains.readers.RdCompositedReader;
import com.bcxin.tenant.open.domains.repositories.AttendanceRepository;
import com.bcxin.tenant.open.domains.repositories.RdEmployeeRepository;
import com.bcxin.tenant.open.domains.repositories.RdSecurityStationPersonRepository;
import com.bcxin.tenant.open.domains.services.RdEmployeeService;
import com.bcxin.tenant.open.domains.services.commands.*;
import com.bcxin.tenant.open.domains.utils.RdEmployeeEntityUtils;
import com.bcxin.tenant.open.infrastructures.UnitWork;
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.NotSupportTenantException;
import com.redis.om.spring.search.stream.EntityStream;
import com.redis.om.spring.search.stream.SearchStream;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

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

/**
 *  职员服务类: 这边一般是保安人员或者监管
 */
@Service
public class RdEmployeeServiceImpl implements RdEmployeeService {
    private final UnitWork unitWork;
    private final RdEmployeeRepository rdEmployeeRepository;
    private final RdEmployeeDocumentRepository rdEmployeeDocumentRepository;
    private final RdSecurityStationPersonRepository rdSecurityStationPersonRepository;
    private final JsonProvider jsonProvider;
    private final AttendanceRepository attendanceRepository;
    private final RdCompositedReader compositedReader;
    private final BeanFactory beanFactory;
    private final EntityStream entityStream;

    public RdEmployeeServiceImpl(UnitWork unitWork,
                                 RdEmployeeRepository rdEmployeeRepository,
                                 RdEmployeeDocumentRepository rdEmployeeDocumentRepository,
                                 RdSecurityStationPersonRepository rdSecurityStationPersonRepository,
                                 JsonProvider jsonProvider, AttendanceRepository attendanceRepository,
                                 RdCompositedReader compositedReader, BeanFactory beanFactory, EntityStream entityStream) {
        this.unitWork = unitWork;
        this.rdEmployeeRepository = rdEmployeeRepository;
        this.rdEmployeeDocumentRepository = rdEmployeeDocumentRepository;
        this.rdSecurityStationPersonRepository = rdSecurityStationPersonRepository;
        this.jsonProvider = jsonProvider;
        this.attendanceRepository = attendanceRepository;
        this.compositedReader = compositedReader;
        this.beanFactory = beanFactory;
        this.entityStream = entityStream;
    }

    @Override
    public void dispatch(UpdateLatLonCommand command) {
        /*
        Optional<RdEmployeeDocument> documentOptional = this.rdEmployeeDocumentRepository.findById(command.getEmployeeId());
        if (!documentOptional.isPresent()) {
            throw new NoFoundTenantException("找不到该用户");
        }

        RdEmployeeDocument document = documentOptional.get();
        document.setLonLat(GeoPointValueType.create(command.getLatitude(), command.getLongitude()));

         */
    }

    @Override
    public void dispatch(CreateEmployeeSyncCommand command) {
        switch (command.getSyncType()) {
            case OrgPurse -> {
                throw new NotSupportTenantException(String.format("暂不支持该功能, 通过创建房间的时候验证联动值的方式进行验证"));
            }
            case DeviceBound -> {
                dispatch_deviceBound_handler((Collection<CreateEmployeeSyncCommand.DeviceBoundChangedEventCommand>) command.getData());
            }
            case DutyStatusChanged -> {
                throw new NotSupportTenantException(String.format("对于如此频繁更新的操作, 暂时不支持实时同步到数据库"));
            }
            case SecurityStationExpired -> {
                dispatch_securityStationExpired_handler((Collection<CreateEmployeeSyncCommand.SecurityStationExpiredSyncCommand>) command.getData());
            }
        }
    }

    @Override
    public void dispatch(RefreshEmployeeStationCommand command) {
        String tranId = this.unitWork.beginTransaction();
        try {
            this.rdSecurityStationPersonRepository.refreshEmployeeStationByEmployeeIds(command.getEmployeeIds());
            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }
    }

    @Override
    public void dispatch(CreateSyncEmployeeCommand command) {
        String tranId = this.unitWork.beginTransaction();
        try {
            RdEmployeeEntity employee = this.jsonProvider.toObject(RdEmployeeEntity.class, command.getContent());
            if (employee == null) {
                throw new BadTenantException(String.format("无效数据格式:%s", command.getContent()));
            }

            if (employee.getStatus() == null) {
                employee.setStatus(EmploymentStatus.OnJob);
            }

            if (employee.getDutyStatus() == null) {
                employee.setDutyStatus(DutySignInType.None);
            }

            if (employee.getOccupationType() == null) {
                employee.setOccupationType(OccupationType.Normal);
            }

            if (employee.getCheckedStatus() == null) {
                employee.setCheckedStatus(UserCheckedStatus.None);
            }

            if (employee.getAuthenticatedStatus() == null) {
                employee.setAuthenticatedStatus(RealNameAuthenticatedStatus.UnAuthenticated);
            }

            /**
             * 内保单位需要可以调度该保安人员
             */
            Collection<RdDispatchDataScopeDocument> dataScopeDocuments = new ArrayList<>();

            Collection<RdCommunityUserDocument> communityUserDocuments = new ArrayList<>();

            Collection<RdCompanyDocument> proprietorCompanyDocuments = new ArrayList<>();

            /**
             * 有安排驻勤点才需要设置社区力量
             */
            if (!BusinessConstants.DefaultEmptyValue.equalsIgnoreCase(employee.getSecurityStationId())) {
                EntityStream entityStream =
                        beanFactory.getBean(EntityStream.class);
                SearchStream<RdCommunityUserDocument> searchStream =
                        entityStream.of(RdCommunityUserDocument.class);
                searchStream =
                        searchStream
                                .filter(RdCommunityUserDocument$.TENANT_USER_ID.eq(employee.getTenantUserId()));
                communityUserDocuments =
                        searchStream.collect(Collectors.toList());

                RdDispatchDataScopeDocument dispatchDataScopeDocument =
                        beanFactory.getBean(RdDispatchDataScopeDocumentRepository.class)
                                .findById(employee.getSecurityStationId()).orElse(null);
                if (dispatchDataScopeDocument != null) {
                    dataScopeDocuments = Collections.singleton(dispatchDataScopeDocument);

                    Collection<String> orgIds = dispatchDataScopeDocument.getScopes();
                    if(!CollectionUtils.isEmpty(orgIds)) {
                        Collection<RdCompanyDocument> companyDocuments =
                                beanFactory.getBean(RdCompanyDocumentRepository.class)
                                        .findAllById(orgIds);
                        proprietorCompanyDocuments.addAll(companyDocuments);
                    }
                }
            }

            RdEmployeeDocument document =
                    RdEmployeeEntityUtils.toDocument(employee, false,
                            rdEmployeeDocumentRepository,
                            attendanceRepository,
                            compositedReader, beanFactory,
                            communityUserDocuments,
                            dataScopeDocuments,
                            proprietorCompanyDocuments
                            );
            if (document == null) {
                throw new BadTenantException(String.format("职员数据-数据库解析异常:id=%s;name=%s;", employee.getId(), employee.getName()));
            }

            this.rdEmployeeDocumentRepository.save(document);

            if (this.rdEmployeeRepository.getByNoPkId(employee.getId()) != null) {
                this.rdEmployeeRepository.update(employee);
            } else {
                this.rdEmployeeRepository.insert(employee);
            }

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

    @Override
    public int dispatch(CleanNonUsedEmployeeCommand command) {
        int affectedCount = 0;
         this.rdEmployeeDocumentRepository.deleteAllById(command.getIds());

        String tranId = this.unitWork.beginTransaction();

        try {
            affectedCount = this.rdEmployeeRepository.deleteNonUsedRecords(command.getIds());
            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }

        return affectedCount;
    }

    @Override
    public void dispatch(BatchUpdateEmployeeLocationCommand command) {
        String tranId = this.unitWork.beginTransaction();
        try {
            Collection<UpdateEmployeeLocationDTO> locationDTOS =
                    command.getLocations().stream().map(ii -> UpdateEmployeeLocationDTO.create(
                            ii.getId(),
                            ii.getSignInType(),
                            ii.getLon(),
                            ii.getLat(),
                            this.jsonProvider
                    )).collect(Collectors.toList());

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

    /**
     * 根据设备绑定情况进行人员信息同步更新
     * @param eventCommands
     */
    private void dispatch_deviceBound_handler(Collection<CreateEmployeeSyncCommand.DeviceBoundChangedEventCommand> eventCommands) {
        if (CollectionUtils.isEmpty(eventCommands)) {
            return;
        }

        Collection<String> employeeIds = eventCommands.stream().map(ii -> ii.getEmployeeId()).collect(Collectors.toList());
        Collection<RdEmployeeEntity> employees = this.rdEmployeeRepository.getByPage(employeeIds, 0, Integer.MAX_VALUE);

        this.doExecuteSync(employees, (employee, employeeDocumentOptional) -> {
            Optional<CreateEmployeeSyncCommand.DeviceBoundChangedEventCommand>
                    selectedEventCommandOptional = eventCommands.stream()
                    .filter(ii -> ii.getEmployeeId().equals(employee.getId()))
                    .findFirst();
            if (!selectedEventCommandOptional.isPresent()) {
                throw new BadTenantException(String.format("代码逻辑异常, 请求中不应该找到指定职员(%s)参数", employee.getId()));
            }
            employee.changeDeviceNumber(selectedEventCommandOptional.get().getDeviceNumber());
            if (employeeDocumentOptional.isPresent()) {
                employeeDocumentOptional.get().changeDeviceNumber(selectedEventCommandOptional.get().getDeviceNumber());
            }
        });
    }

    private void dispatch_securityStationExpired_handler(Collection<CreateEmployeeSyncCommand.SecurityStationExpiredSyncCommand> eventCommands) {
        if (CollectionUtils.isEmpty(eventCommands)) {
            return;
        }

        String[] stationIds = eventCommands.stream().map(ii -> ii.getStationId())
                .collect(Collectors.toList())
                .toArray(ix -> new String[ix]);
        Collection<RdEmployeeDocument> documents = this.entityStream.of(RdEmployeeDocument.class)
                .filter(RdEmployeeDocument$.SECURITY_STATION_ID.in(stationIds))
                .collect(Collectors.toList());
        documents.forEach(document -> {
            document.setSecurityStationId(null);
            document.setSecurityStationName(null);
        });

        this.rdEmployeeDocumentRepository.saveAll(documents);
        Collection<String> expiredStationEmployeeIds =
                documents.stream().map(ii -> ii.getId()).collect(Collectors.toList());

        this.rdEmployeeRepository.resetExpiredStationIdByIds(expiredStationEmployeeIds);
    }

    private void doExecuteSync(Collection<RdEmployeeEntity> employees, ExecuteFunction<RdEmployeeEntity,RdEmployeeDocument> doConsumerAction) {

        Collection<String> ids = employees.stream().map(ii -> ii.getId()).collect(Collectors.toList());

        Collection<RdEmployeeDocument> employeeDocuments = rdEmployeeDocumentRepository.findAllById(ids);
        for (RdEmployeeEntity employee : employees) {
            Optional<RdEmployeeDocument> employeeDocumentOptional = employeeDocuments.stream()
                    .filter(ii -> ii.getId().equalsIgnoreCase(employee.getId())).findFirst();

            doConsumerAction.accept(employee, employeeDocumentOptional);
        }

        this.rdEmployeeDocumentRepository.saveAll(employeeDocuments);
        this.rdEmployeeRepository.batchUpdate(employees);
    }

    public static interface ExecuteFunction<A,B> {
        public  void accept(A a, Optional<B> b);
    }
}
