package com.bcxin.tenant.domain.services.impls;

import com.bcxin.Infrastructures.TenantContext;
import com.bcxin.Infrastructures.TenantUserContext;
import com.bcxin.Infrastructures.UnitWork;
import com.bcxin.Infrastructures.components.CacheProvider;
import com.bcxin.Infrastructures.components.EventDispatcher;
import com.bcxin.Infrastructures.components.JsonProvider;
import com.bcxin.Infrastructures.components.models.DataCacheItem;
import com.bcxin.Infrastructures.entities.OperatorValueType;
import com.bcxin.Infrastructures.enums.EmploymentStatus;
import com.bcxin.Infrastructures.enums.MasterSlaveType;
import com.bcxin.Infrastructures.enums.ProcessedStatus;
import com.bcxin.Infrastructures.exceptions.*;
import com.bcxin.Infrastructures.utils.ExceptionUtil;
import com.bcxin.tenant.domain.conditions.TenantUserSameValidator;
import com.bcxin.tenant.domain.conditions.requests.TenantUserSameCheckRequest;
import com.bcxin.tenant.domain.configs.EnvConfig;
import com.bcxin.tenant.domain.entities.*;
import com.bcxin.tenant.domain.enums.ImportedDataCategory;
import com.bcxin.tenant.domain.exceptions.EmployeeConflictException;
import com.bcxin.tenant.domain.exceptions.EntryEmployeeValidationException;
import com.bcxin.tenant.domain.exceptions.TenantExceptionConverter;
import com.bcxin.tenant.domain.repositories.*;
import com.bcxin.tenant.domain.repositories.composites.EmployeeDepartWrapper;
import com.bcxin.tenant.domain.repositories.composites.EmployeeImportWrapper;
import com.bcxin.tenant.domain.services.ImportDataService;
import com.bcxin.tenant.domain.services.commands.BatchImportEmployeeCommand;
import com.bcxin.tenant.domain.services.commands.results.ProcessImportDataCommandResult;
import com.bcxin.tenant.domain.snapshots.EmployeeImportedItemSnapshot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ImportDataServiceImpl implements ImportDataService {
    private final JsonProvider jsonProvider;
    private final DepartmentRepository departmentRepository;
    private final ThreadPoolTaskExecutor taskExecutor;
    private final UnitWork unitWork;
    private final UniqueDataConstraintRepository uniqueDataConstraintRepository;
    private final EmployeeRepository employeeRepository;
    private final CacheProvider cacheProvider;
    private final ImportDataEntityRepository importDataEntityRepository;
    private final OrganizationRepository organizationRepository;
    private final EventDispatcher eventDispatcher;
    private final EnvConfig envConfig;

    private final TenantUserSameValidator tenantUserSameValidator;

    public ImportDataServiceImpl(JsonProvider jsonProvider,
                                 DepartmentRepository departmentRepository,
                                 ThreadPoolTaskExecutor taskExecutor, UnitWork unitWork,
                                 UniqueDataConstraintRepository uniqueDataConstraintRepository,
                                 EmployeeRepository employeeRepository,
                                 CacheProvider cacheProvider,
                                 ImportDataEntityRepository importDataEntityRepository,
                                 OrganizationRepository organizationRepository,
                                 EventDispatcher eventDispatcher, EnvConfig envConfig,
                                 TenantUserSameValidator tenantUserSameValidator) {
        this.jsonProvider = jsonProvider;
        this.departmentRepository = departmentRepository;
        this.taskExecutor = taskExecutor;
        this.unitWork = unitWork;
        this.uniqueDataConstraintRepository = uniqueDataConstraintRepository;
        this.employeeRepository = employeeRepository;
        this.cacheProvider = cacheProvider;
        this.importDataEntityRepository = importDataEntityRepository;
        this.organizationRepository = organizationRepository;
        this.eventDispatcher = eventDispatcher;
        this.envConfig = envConfig;
        this.tenantUserSameValidator = tenantUserSameValidator;
    }

    @Override
    public ProcessImportDataCommandResult waitForProcessing(String id, OperatorValueType operator, Set<String> treeCodes) {
        DataCacheItem<EmployeeImportWrapper> employeeImportWrapperCache = this.cacheProvider.get(id, () -> {
            Optional<ImportDataEntity> importDataOptional = this.importDataEntityRepository.findById(id);
            if (!importDataOptional.isPresent()) {
                return null;
            }

            Optional<OrganizationEntity> organizationOptional =
                    this.organizationRepository.findById(importDataOptional.get().getOrganizationId());
            if (!organizationOptional.isPresent()) {
                return null;
            }

            EmployeeImportWrapper importWrapper = EmployeeImportWrapper.create(organizationOptional.get(), importDataOptional.get());

            return DataCacheItem.create(importWrapper);
        });

        if (employeeImportWrapperCache == null || employeeImportWrapperCache.getData() == null) {
            throw new NotFoundTenantException("找不到导入数据");
        }

        OrganizationEntity organization = employeeImportWrapperCache.getData().getOrganization();
        ImportDataEntity importData = employeeImportWrapperCache.getData().getImportData();
        importData.setOperator(operator);
        if (importData.getCategory() == ImportedDataCategory.Employee) {
            this.processEmployee(organization, importData, treeCodes);
        }

        return ProcessImportDataCommandResult.create(importData.getId(),
                (int) importData.getDataItems().stream().count(),
                (int) importData.getDataItems().stream().filter(ii -> ii.getStatus() == ProcessedStatus.Done).count(),
                (int) importData.getDataItems().stream().filter(ii -> ii.getStatus() == ProcessedStatus.Error).count());

    }

    private void processEmployee(OrganizationEntity organization,ImportDataEntity importData, Set<String> treeCodes) {
        TenantUserContext.UserModel currentUser = TenantContext.getInstance().getUserContext().get();
        if (currentUser == null) {
            throw new UnAuthorizedTenantException("当前用户无效");
        }
        boolean isMaster = currentUser.isMaster();
        boolean isDepartAdmin = currentUser.isDepartAdmin();

        if (!isMaster && !isDepartAdmin) {
            throw new UnAuthorizedTenantException("只有组织管理员或部门管理员有权限操作");
        }

        Map<ImportDataItemEntity, BatchImportEmployeeCommand.EmployeeCommandItem> commandItemsMap = importData.getDataItems().stream()
                .filter(ii -> ii.getStatus() == ProcessedStatus.Init)
                .collect(Collectors.toMap(it -> it, ii -> {
                    EmployeeImportedItemSnapshot itemSnapshot = jsonProvider.toObject(EmployeeImportedItemSnapshot.class, ii.getData());

                    return BatchImportEmployeeCommand.EmployeeCommandItem.create(itemSnapshot);
                }));

        //验证部门是否存在
        Collection<String> departNames =
                commandItemsMap.values().stream().map(ii -> {
                    if (StringUtils.hasLength(ii.getDepartName())) {
                        return ii.getDepartName();
                    } else {
                        return null;
                    }
                }).distinct().collect(Collectors.toList());

        Collection<DepartmentEntity> selectedDeparts =
                this.departmentRepository.getDepartAndRootByOrganIdName(importData.getOrganizationId(), departNames);

        Map<ImportDataItemEntity, EmployeeDepartWrapper> employeeDepartWrappersMap = new HashMap<>();
        for (ImportDataItemEntity dataItem : commandItemsMap.keySet()) {
            BatchImportEmployeeCommand.EmployeeCommandItem commandItem = commandItemsMap.get(dataItem);
            DepartmentEntity selectedDepart = commandItem.validateDepartTree(selectedDeparts);
            if (selectedDepart != null) {
                if (!isMaster && treeCodes.stream().noneMatch(code -> selectedDepart.getIndexTree().startsWith(code))) {
                    //不是组织管理员，需要判断部门员工权限
                    dataItem.changeStatus(ProcessedStatus.Error, "您无当前导入部门的管理权限，请选择可管理部门进行导入");
                } else {
                    employeeDepartWrappersMap.put(dataItem, EmployeeDepartWrapper.create(commandItem, selectedDepart));
                }
            } else {
                dataItem.changeStatus(ProcessedStatus.Error, commandItem.getErrorResult());
            }
        }

        Collection<Map<ImportDataItemEntity, EmployeeDepartWrapper>> employeeDepartWrapperGroups =
                getGroupEmployeeDepartWrappers(employeeDepartWrappersMap, 50);


        CountDownLatch downLatch = new CountDownLatch(employeeDepartWrapperGroups.size());

        for (Map<ImportDataItemEntity, EmployeeDepartWrapper> map : employeeDepartWrapperGroups) {
            /**
             * 多线程分组执行导入操作.
             */
            this.taskExecutor.execute(() -> {
                try {
                    this.executeEmployeeMap(organization, map, importData.getOperator());
                } catch (Exception ex) {
                    ex.printStackTrace();
                    log.error("执行数据发生异常:id={};path={};mapItem={}", importData.getId(), importData.getPath(),map, ex);
                } finally {
                    downLatch.countDown();
                }
            });
        }

        try {
            downLatch.await();
            //downLatch.await(180*10, TimeUnit.SECONDS);

            this.unitWork.executeTran(()->{
                this.importDataEntityRepository.save(importData);
            });
        } catch (Exception ex) {
            log.error("导入发生异常:id={};path={}", importData.getId(), importData.getPath(), ex);
        }
    }

    /**
     * todo: 优化职员的批量导入功能 20230717
     * @param organization
     * @param employeeDepartWrapperMap
     * @param operator
     */
    private void executeEmployeeMap(
            OrganizationEntity organization,
            Map<ImportDataItemEntity, EmployeeDepartWrapper> employeeDepartWrapperMap,
            OperatorValueType operator) {
        if (employeeDepartWrapperMap == null || employeeDepartWrapperMap.size() == 0) {
            return;
        }

        /**
         * 批量导入的时候验证: 当手机号作为账号的时候; 需要确认导入时候指定的身份证是否被他人持有.
         * add by lhc: 20230718
         */
        Collection<TenantUserSameCheckRequest> userSameCheckRequests =
                employeeDepartWrapperMap.values().stream().map(ii -> {
                    TenantUserSameCheckRequest request =
                            TenantUserSameCheckRequest.create(
                                    ii.getItem().getCredentialType(),
                                    ii.getItem().getDataItem().getCredentialNumber(),
                                    ii.getItem().getDataItem().getTelephone());

                    return request;
                }).collect(Collectors.toList());

        EntryEmployeeValidationException validationException = null;
        try {
            this.tenantUserSameValidator.validate(userSameCheckRequests);
        } catch (EntryEmployeeValidationException ex) {
            validationException = ex;
        }

        /**
         * 当前操作者, 可以为当前用户/也可以是固定后台用户
         */
        //String userName = null;
        Map<EmployeeDepartWrapper, EmployeeEntity> employeeEntityMap = new HashMap<>();
        for (ImportDataItemEntity importDataItem : employeeDepartWrapperMap.keySet()) {
            EmployeeDepartWrapper employeeDepartWrapper = employeeDepartWrapperMap.get(importDataItem);
            AtomicReference<UniqueDataConstraintEntity> uniqueDataConstraintAtomic = new AtomicReference<>();
            AtomicReference<EmployeeEntity> employeeAtomic = new AtomicReference<>();
            EmployeeImportedItemSnapshot commandItem = employeeDepartWrapper.getItem().getDataItem();
            try {
                if (validationException != null) {
                    String errorMsg =
                            validationException.getMatchMessage(
                                    commandItem.getTelephone(),
                                    employeeDepartWrapper.getItem().getCredentialType(), commandItem.getCredentialNumber()
                            );
                    if (StringUtils.hasLength(errorMsg)) {
                        employeeDepartWrapper.getItem().addError(errorMsg);
                        continue;
                    }
                }
                EmployeeEntity existsEmployee =
                        this.employeeRepository
                                .getIdByOrgIdAndIdCard(
                                        organization.getId(),
                                        commandItem.getCredentialNumber()
                                );
                if(existsEmployee!=null) {
                    throw new EmployeeConflictException(existsEmployee);
                }else {
                    //入职
                    this.unitWork.executeTran(() -> {
                        EmployeeEntity employee = EmployeeEntity.create(
                                this.eventDispatcher,
                                organization,
                                employeeDepartWrapper.getDepartment(),
                                commandItem.getName(),
                                commandItem.getTelephone(),
                                null,
                                commandItem.getPositionText(),
                                employeeDepartWrapper.getItem().getHiredDate(),
                                MasterSlaveType.Normal,
                                employeeDepartWrapper.getItem().getCredentialType(),
                                commandItem.getCredentialNumber(),
                                employeeDepartWrapper.getItem().getOccupationType(),
                                commandItem.getNation(),
                                commandItem.getEducation(),
                                commandItem.getPoliticsStatus(),
                                commandItem.getMilitaryStatus(),
                                commandItem.getMaritalStatus(),
                                commandItem.getEmergencyContact(),
                                commandItem.getEmergencyPhone(),
                                commandItem.getAddress(),
                                commandItem.getHouseholdType(),
                                true,
                                operator);
                        employeeEntityMap.put(employeeDepartWrapper, employee);

                        employeeAtomic.set(employee);
                        /**
                         * 确保一个手机/身份证只能入职一家公司
                         */
//                    UniqueDataConstraintEntity uniqueDataConstraint = this.uniqueDataConstraintRepository.create(
//                            organization.getId(),
//                            String.format("%s+%s", employeeAtomic.get().getTenantUser().getId(), employeeAtomic.get().getStatus())
//                    );
//                    uniqueDataConstraintAtomic.set(uniqueDataConstraint);
                        this.employeeRepository.save(employeeAtomic.get());

                        employeeAtomic.get().dispatchAfterCreatedEvent(envConfig.isRequiredPhoneAsLoginName());
                    });
                }
            } catch (Exception ex) {
                try{
                    TenantExceptionAbstract tenantExceptionAbstract = TenantExceptionConverter.cast(ex);
                    if (tenantExceptionAbstract != null &&
                            (tenantExceptionAbstract instanceof ConflictTenantException ||
                                    tenantExceptionAbstract instanceof EmployeeConflictException)
                    ) {
                        if (employeeAtomic.get() != null) {
                            this.unitWork.detach(employeeAtomic.get());
                        }
                        if (uniqueDataConstraintAtomic.get() != null) {
                            this.unitWork.detach(uniqueDataConstraintAtomic.get());
                        }
                        /**
                         * 如果该职员存在, 那么直接进行复职操作
                         */
                        EmployeeEntity selectedEmployee = null;
                        if(tenantExceptionAbstract instanceof EmployeeConflictException)
                        {
                            selectedEmployee = ((EmployeeConflictException) tenantExceptionAbstract).getEmployee();
                        }else {
                            selectedEmployee = this.employeeRepository.getByIdNum(organization.getId(), commandItem.getCredentialNumber());
                        }

                        if (selectedEmployee == null) {
                            //throw ex;
                            employeeDepartWrapper.getItem().addError(String.format("无法入职, 同时找不到对应的人员(%s)信息, 请联系管理员", commandItem.getCredentialNumber()));
                            continue;
                        }

                        /**
                         * 如果该职员已经存在
                         */
                        if (selectedEmployee.getStatus() == EmploymentStatus.OnJob){
                            String errorMsg = "该职员已经存在(请确保该职员未导入或者处理离职状态)!";
                            employeeDepartWrapper.getItem().addError(errorMsg);
                        }

                        /**
                         * 针对此情况; 需要进行复职操作
                         */
                        if (selectedEmployee.getStatus() == EmploymentStatus.OffJob) {
                            EmployeeEntity finalSelectedEmployee = selectedEmployee;

                            this.unitWork.executeTran(() -> {
                                this.departmentRepository
                                        .clearDuplicatedEmployeeRelationByEmployeeIds(
                                                Collections.singleton(finalSelectedEmployee.getId())
                                        );

                                /**
                                 * 执行复职操作
                                 */
                                finalSelectedEmployee.back(
                                        this.eventDispatcher,
                                        employeeDepartWrapper.getDepartment(),
                                        employeeDepartWrapper.getItem().getOccupationType(),
                                        employeeDepartWrapper.getItem().getHiredDate(),
                                        operator,
                                        false
                                );

                                this.employeeRepository.save(finalSelectedEmployee);
                            });
                        }

                    } else {
                        if (ex instanceof UnexpectedTenantException) {
                            employeeDepartWrapper.getItem().addError(String.format("导入异常,请联系管理员：%s", ExceptionUtil.getStackMessage(ex)));
                        } else {
                            employeeDepartWrapper.getItem().addError(ex.getMessage());
                        }
                        log.error("数据项({})导入异常",employeeDepartWrapper.getItem().getDataItem().getCredentialNumber(), ex);
                    }
                }
                catch (Exception ex2) {
                    employeeDepartWrapper.getItem().addError(String.format("原始异常:%s-处理异常:%s", ex.getMessage(), ex2.getMessage()));
                    log.error("v2-数据项({})导入异常后({}); 处理异常", employeeDepartWrapper.getItem().getDataItem().getCredentialNumber(), ex, ex2);
                }
            } finally {
                BatchImportEmployeeCommand.EmployeeCommandItem selectedCommandItem = employeeDepartWrapper.getItem();
                ProcessedStatus status = ProcessedStatus.Done;
                if (StringUtils.hasLength(selectedCommandItem.getErrorResult())) {
                    status = ProcessedStatus.Error;
                }

                importDataItem.changeStatus(status, selectedCommandItem.getErrorResult());
            }
        }

    }

    private Collection<Map<ImportDataItemEntity,EmployeeDepartWrapper>> getGroupEmployeeDepartWrappers(
            Map<ImportDataItemEntity,EmployeeDepartWrapper> wholeWrappers,int pageSize) {
        Collection<Map<ImportDataItemEntity, EmployeeDepartWrapper>> group = new ArrayList<>();

        int index = 0;

        Map<ImportDataItemEntity, EmployeeDepartWrapper> employeeDepartWrapperMap = null;
        for (ImportDataItemEntity importDataItem : wholeWrappers.keySet()) {
            if (index % pageSize == 0) {
                employeeDepartWrapperMap = new HashMap<>();
                group.add(employeeDepartWrapperMap);
            }

            employeeDepartWrapperMap.put(importDataItem, wholeWrappers.get(importDataItem));

            index++;
        }

        return group;
    }
}
