package com.bcxin.backend.domain.syncs.services.impls;

import com.bcxin.backend.core.AppConfigProperty;
import com.bcxin.backend.core.components.StorageProvider;
import com.bcxin.backend.core.utils.ExceptionUtils;
import com.bcxin.backend.domain.models.DomainSuperviseDTO;
import com.bcxin.backend.domain.repositories.DomainSuperviseRepository;
import com.bcxin.backend.domain.services.DomainSuperviseService;
import com.bcxin.backend.domain.syncs.components.FormDataExporterImpl;
import com.bcxin.backend.domain.syncs.components.IFormDataExporter;
import com.bcxin.backend.domain.syncs.dtos.SyncDataMeta;
import com.bcxin.backend.domain.syncs.dtos.SyncDataSetDTO;
import com.bcxin.backend.domain.syncs.events.SyncDataQueueApplicationEvent;
import com.bcxin.backend.domain.syncs.repositories.DataSyncQueueRepository;
import com.bcxin.backend.domain.syncs.services.FormTableSyncService;
import com.bcxin.runtime.domain.enums.BooleanStatus;
import com.bcxin.runtime.domain.metas.entities.FormMetaEntity;
import com.bcxin.runtime.domain.metas.entities.FormSyncMetaEntity;
import com.bcxin.runtime.domain.metas.repositories.ApplicationMetaRepository;
import com.bcxin.runtime.domain.metas.repositories.FormSyncMetaRepository;
import com.bcxin.runtime.domain.snapshoots.FormSyncConfigSnapshot;
import com.bcxin.runtime.domain.snapshoots.FormSyncTargetConfigSnapshot;
import com.bcxin.runtime.domain.snapshoots.enums.SyncTargetType;
import com.bcxin.runtime.domain.syncs.dtos.DataSetDto;
import com.bcxin.runtime.domain.syncs.dtos.FormSyncConfigDto;
import com.bcxin.runtime.domain.syncs.entities.DataSyncQueueEntity;
import com.bcxin.runtime.domain.syncs.enums.SyncProcessStatus;
import com.bcxin.saas.core.components.JsonProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
public class FormTableSyncServiceImpl implements FormTableSyncService {
    private static Logger logger = LoggerFactory.getLogger(FormTableSyncServiceImpl.class);

    private final ApplicationMetaRepository applicationMetaRepository;
    private final FormSyncMetaRepository formSyncMetaRepository;
    private final FormDataExporterImpl formDataExporter;
    private final DataSyncQueueRepository dataSyncQueueRepository;
    private final JsonProvider jsonProvider;
    private final StorageProvider storageProvider;
    private final DomainSuperviseRepository domainSuperviseRepository;
    private final ApplicationEventPublisher eventPublisher;
    private final DomainSuperviseService domainSuperviseService;
    private final AppConfigProperty appConfigProperty;

    public FormTableSyncServiceImpl(ApplicationMetaRepository applicationMetaRepository,
                                    FormSyncMetaRepository formSyncMetaRepository,
                                    FormDataExporterImpl formDataExporter,
                                    DataSyncQueueRepository dataSyncQueueRepository,
                                    JsonProvider jsonProvider,
                                    StorageProvider storageProvider,
                                    DomainSuperviseRepository domainSuperviseRepository,
                                    ApplicationEventPublisher eventPublisher,
                                    DomainSuperviseService domainSuperviseService,
                                    AppConfigProperty appConfigProperty) {
        this.applicationMetaRepository = applicationMetaRepository;
        this.formSyncMetaRepository = formSyncMetaRepository;
        this.formDataExporter = formDataExporter;
        this.dataSyncQueueRepository = dataSyncQueueRepository;
        this.jsonProvider = jsonProvider;
        this.storageProvider = storageProvider;
        this.domainSuperviseRepository = domainSuperviseRepository;
        this.eventPublisher = eventPublisher;
        this.domainSuperviseService = domainSuperviseService;
        this.appConfigProperty = appConfigProperty;
    }

    @Override
    public void dispatch() {
        Collection<FormSyncMetaEntity> formSyncMetaEntities =
                getAllOnlineFormSyncMetaEntities()
                        .stream().filter(ii -> {
                    if (ii.getLastSyncedTime() == null) {
                        return true;
                    }
                    //2小时执行一次表单变更数据查询
                    try {
                        java.util.Date lastSyncedTime = ii.getLastSyncedTime();
                        return lastSyncedTime.before(Date.from(Instant.now().minus(2, ChronoUnit.HOURS)));
                    } catch (Exception ex) {
                        logger.error(String.format("转换时间出错:%s;明细:%s", ii.getLastSyncedTime(), ex.toString()), ex);

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

        logger.error(String.format("2.总共执行的表单如下:%s个表单",
                formSyncMetaEntities.size() > 0 ? formSyncMetaEntities.size() : "【同步表单为达到间隔时间(每2小时1次)】"));
        Collection<FormSyncMetaEntity> savedFormSyncMetaEntities = new HashSet<>();
        formSyncMetaEntities.stream().forEachOrdered(fsm -> {
            FormMetaEntity formMetaEntity = fsm.getFormMeta();
            String dsSql = null;
            try {
                FormSyncTargetConfigSnapshot targetConfigSnapshot = this.jsonProvider.getData(fsm.getTargetMetaEntity().getConfig(),
                        FormSyncTargetConfigSnapshot.class);

                Timestamp fetchedModifiedTime = null;
                if (fsm.getLastSyncedTime() != null) {
                    fetchedModifiedTime = Timestamp.from(Instant.ofEpochMilli(fsm.getLastSyncedTime().getTime()));
                } else {
                    fetchedModifiedTime = IFormDataExporter.DEFAULT_INITIAL_DATE;
                }

                DataSetDto ds = null;
                int pageIndex = 0;
                do {
                    ds = formDataExporter.export(fsm, fetchedModifiedTime, pageIndex);
                    dsSql = ds.getSql();

                    Collection<SyncDataSetDTO> syncDataSetDTOS = splitDataSetDtos(targetConfigSnapshot, ds,fsm);

                    Timestamp finalFetchedModifiedTime = fetchedModifiedTime;
                    int finalPageIndex = pageIndex;
                    Collection<DataSyncQueueEntity> totalDataSyncQueueEntities =
                            syncDataSetDTOS.stream().map(syncDataSetDTO ->
                                    translate2DataSyncQueueEntity(fsm, syncDataSetDTO, finalFetchedModifiedTime, finalPageIndex))
                                    .collect(Collectors.toList());

                    Collection<DataSyncQueueEntity> dataSyncQueueEntities = totalDataSyncQueueEntities.stream()
                            .filter(ii -> ii.getSize() > 0)
                            .collect(Collectors.toList());

                    dataSyncQueueEntities.forEach(ii -> {
                        this.eventPublisher.publishEvent(SyncDataQueueApplicationEvent.create(ii));
                    });

                    //修改last_synced_time同步时间
                    if (ds.getRows().size() == 0 || ds.getRows().size() < FormDataExporterImpl.PAGE_SIZE) {
                        Date comparedDate = Timestamp.from(Instant.now().minus(2, ChronoUnit.SECONDS));
                        Date updateDoneSyncDate = fsm.getLastSyncedTime();
                        if (comparedDate.after(fsm.getLastSyncedTime())) {
                            updateDoneSyncDate = comparedDate;
                        }

                        Calendar calendar = Calendar.getInstance();
                        calendar.setTime(updateDoneSyncDate);
                        calendar.add(Calendar.SECOND, 1);
                        updateDoneSyncDate = calendar.getTime();
                        fsm.markSyncQueue(updateDoneSyncDate, fsm.getSyncedRecordCount(), fsm.getNote());
                    }

                    Collection<DataSyncQueueEntity> zeroDataSyncQueueEntities =
                            totalDataSyncQueueEntities.stream().filter(ii -> ii.getSize() == 0)
                                    .collect(Collectors.toList());

                    if (zeroDataSyncQueueEntities.size() > 0) {
                        logger.error(String.format("%s:表[%s]执行完毕;结果=%s; 总共%s条数据,查询sql为(%s)",
                                Instant.now(), formMetaEntity.getTableName(), fsm.getNote(),
                                zeroDataSyncQueueEntities.size(), dsSql));
                    }

                    savedFormSyncMetaEntities.add(fsm);
                    commitData2Db(savedFormSyncMetaEntities, dataSyncQueueEntities);

                    pageIndex++;
                }
                while (ds != null && (ds.getRows().size() > 0));
            } catch (Exception ex) {
                logger.error("表{}创建queues数据异常，查询sql为：{},异常为：{}", formMetaEntity.getTableName(), dsSql, ex);
                fsm.markSyncQueue(fsm.getLastSyncedTime(), -1,
                        String.format("%s: 执行同步失败: %s", new Date(), ExceptionUtils.getStackMessage(ex))
                );
            } finally {
                Calendar calendar = Calendar.getInstance();
                if (calendar.get(Calendar.SECOND) % 20 == 0) {
                    logger.error(String.format("%s:表[%s]执行完毕;结果=%s", Instant.now(), formMetaEntity.getTableName(), fsm.getNote()));
                }
            }
        });
    }

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    private DataSyncQueueEntity translate2DataSyncQueueEntity(
            FormSyncMetaEntity formSyncMetaEntity, SyncDataSetDTO syncDataSetDTO, Timestamp startDateTime, int pageIndex) {
        DataSetDto ds = syncDataSetDTO.getDataSetDto();
       String data = null;
        try {
             data = this.jsonProvider.getJson(syncDataSetDTO);
        }catch (Exception e){
            logger.error("translate2DataSyncQueueEntity转换数据异常，{}", e);
        }
        if(StringUtils.isEmpty(data)){
            DataSyncQueueEntity dataSyncQueueEntity = DataSyncQueueEntity.create(null,
                    null,
                    0,
                    false,
                    null, null);
            return dataSyncQueueEntity;
        }
        String path = String.format("/uploads/sync/request/form/%s/%s-%s.txt",
                simpleDateFormat.format(new Date()),
                ds.getTableName(), UUID.randomUUID());

        byte[] content = data.getBytes(StandardCharsets.UTF_8);
        String realPath = this.storageProvider.save(path, content);

        Collection<String> processedDataIds =
                ds.getRows().stream().limit(FormDataExporterImpl.PAGE_SIZE).map(ii -> String.valueOf(ii.getId()))
                        .collect(Collectors.toList());

        SyncDataMeta dataMeta = SyncDataMeta.create(path,
                ds.getRows().size(),
                realPath,
                processedDataIds
        );

        FormSyncConfigDto configVo = getFormSyncConfigDto(formSyncMetaEntity);
        String formSyncConfig = this.jsonProvider.getJson(configVo);

        String tableName = ds.getTableName();
        try {
            String enterpriseDesc = null;
            if (syncDataSetDTO.getSyncTargetType() == SyncTargetType.SyncViaFTP) {
                enterpriseDesc = (syncDataSetDTO.getDomainSuperviseDTO() == null ||
                        syncDataSetDTO.getDomainSuperviseDTO().getSupervise() == null) ?
                        "无归属地" : syncDataSetDTO.getDomainSuperviseDTO()
                        .getSupervise().getTargetApp();
            } else {
                enterpriseDesc = "直接推送到目标API";
            }


            tableName = String.format("企业-[%s]: %s",
                    enterpriseDesc,
                    ds.getSql()
            );

            if (StringUtils.hasLength(syncDataSetDTO.getNote())) {
                tableName = "合并:" + tableName;
            }
        } catch (Exception ex) {
            logger.error(String.format("ds数据(%s)集信息不符合要求:%s", ds.getTableName(), ExceptionUtils.getStackMessage(ex)));
        }

        boolean needSyncFile = ds.getRows().stream()
                .anyMatch(ii -> ii.getFieldValues()
                        .stream().anyMatch(ix -> ix.getIsFileValue()));

        DataSyncQueueEntity dataSyncQueueEntity =
                DataSyncQueueEntity.create(tableName,
                        this.jsonProvider.getJson(dataMeta),
                        ds.getRows().size(),
                        needSyncFile,
                        configVo.getFormSyncId(), formSyncConfig);

        FormSyncTargetConfigSnapshot targetConfigSnapshot = this.jsonProvider.getData(formSyncMetaEntity.getTargetMetaEntity().getConfig(),
                FormSyncTargetConfigSnapshot.class);

        if (ds.getRows().size() == 0) {
            dataSyncQueueEntity.changeDataSyncStatus(SyncProcessStatus.Done, "无任何数据需要进行同步!");
            dataSyncQueueEntity.changeFileSyncStatus(SyncProcessStatus.Done, "无任何文件信息，直接进行数据同步!");
        } else if (syncDataSetDTO.getDomainSuperviseDTO() == null || syncDataSetDTO.getDomainSuperviseDTO().getSupervise() == null) {
            if (syncDataSetDTO.getSyncTargetType() == SyncTargetType.SyncViaFTP) {
                dataSyncQueueEntity.changeDataSyncStatus(SyncProcessStatus.Done, "无监管归属信息，暂无需进行数据同步");
                dataSyncQueueEntity.changeFileSyncStatus(SyncProcessStatus.Done, "无监管归属信息，暂无需进行文件同步");
            } else {
                dataSyncQueueEntity.changeDataSyncStatus(SyncProcessStatus.Initialize, "直接通过数据API进行同步即可");
                dataSyncQueueEntity.changeFileSyncStatus(SyncProcessStatus.Done, "数据/文件API已经合并到同一个目标API");
            }
        } else if (targetConfigSnapshot.getTargetType() == SyncTargetType.SyncViaFTP) {
            /**
             * 同步数据的时候，如果也需要同步文件的话，则直接让数据和文件一块同步即可
             */
            if (!appConfigProperty.isSyncViaFtp(syncDataSetDTO.getDomainSuperviseDTO().getSupervise().getTargetApp())) {
                String desc = String.format("该数据(%s)不走摆渡服务代理模式",
                        syncDataSetDTO.getDomainSuperviseDTO().getSupervise().getTargetApp());
                dataSyncQueueEntity.changeDataSyncStatus(SyncProcessStatus.DoneViaFTP, desc);
                dataSyncQueueEntity.changeFileSyncStatus(SyncProcessStatus.DoneViaFTP, desc);
            } else {
                if (dataSyncQueueEntity.getNeedSyncFile() == BooleanStatus.TRUE &&
                        ds.getRows().stream().flatMap(ix -> ix.getFieldValues().stream()).anyMatch(ix -> ix.isValidFile())) {
                    dataSyncQueueEntity.changeDataSyncStatus(SyncProcessStatus.DoneViaFTP, "如果也有文件需要同步, 那么数据和文件将一起打包进行同步");
                    dataSyncQueueEntity.changeFileSyncStatus(SyncProcessStatus.SyncViaFTP, "等待(数据和文件)对接摆渡服务进行同步");
                } else {
                    dataSyncQueueEntity.changeDataSyncStatus(SyncProcessStatus.SyncViaFTP, "等待数据对接摆渡服务进行同步");
                    dataSyncQueueEntity.changeFileSyncStatus(SyncProcessStatus.DoneViaFTP, "无文件等待摆渡服务进行同步");
                }
            }
        }

        return dataSyncQueueEntity;
    }

    @Transactional
    protected void commitData2Db(Collection<FormSyncMetaEntity> formSyncMetaEntities, Collection<DataSyncQueueEntity> dataSyncQueueEntities) {
        if (dataSyncQueueEntities.size() > 0) {
            this.dataSyncQueueRepository.saveAll(dataSyncQueueEntities);
            dataSyncQueueEntities.clear();
        }

        if (formSyncMetaEntities.size() > 0) {
            this.formSyncMetaRepository.saveAll(formSyncMetaEntities);
            formSyncMetaEntities.clear();
        }
    }

    private FormSyncConfigDto getFormSyncConfigDto(FormSyncMetaEntity formSyncMetaEntity) {
        FormSyncConfigSnapshot syncConfigSnapshot = this.jsonProvider
                .getData(formSyncMetaEntity.getConfig(), FormSyncConfigSnapshot.class);

        FormSyncTargetConfigSnapshot targetConfigSnapshot = this.jsonProvider
                .getData(formSyncMetaEntity.getTargetMetaEntity().getConfig(), FormSyncTargetConfigSnapshot.class);
        FormSyncConfigDto configVo = FormSyncConfigDto.create(formSyncMetaEntity.getId(), syncConfigSnapshot.getMapKey(),
                targetConfigSnapshot,syncConfigSnapshot.getOtherTargetApp());

        return configVo;
    }


    private Collection<SyncDataSetDTO> splitDataSetDtos(FormSyncTargetConfigSnapshot targetConfigSnapshot, DataSetDto dataSetDto,FormSyncMetaEntity formSyncMetaEntity) {
        //无需根据监管归属地, 比如: 企业端到企业端的配置信息等
        if (targetConfigSnapshot.getTargetType() == SyncTargetType.Direct2V5) {
            DataSetDto dsDto =
                    DataSetDto.create(dataSetDto.getTableName(),
                            dataSetDto.getTotalCount(), dataSetDto.getColumns(), dataSetDto.getRows());
            dsDto.setSql(dataSetDto.getSql());

            return Collections.singleton(SyncDataSetDTO.create(targetConfigSnapshot.getTargetType(), dsDto, null));
        } else {
            Collection<SyncDataSetDTO> syncDataSetDTOS = null;
            Collection<String> domainIds =
                    dataSetDto.getRows().stream().map(ii -> ii.getDomainId())
                            .filter(ii -> StringUtils.hasLength(ii) && !"null".equalsIgnoreCase(ii))
                            .collect(Collectors.toList());
            Collection<DomainSuperviseDTO> domainSuperviseDTOS =
                    domainSuperviseService.getByDomainIds(domainIds);

            syncDataSetDTOS = domainSuperviseDTOS.stream().map(ii -> {
                Collection<DataSetDto.Row> rows = dataSetDto.getRows().stream()
                        .filter(ix -> {
                            if (!StringUtils.hasLength(ix.getDomainId()) || "null".equalsIgnoreCase(ix.getDomainId())) {
                                return !StringUtils.hasLength(ii.getDomainId()) || "null".equalsIgnoreCase(ii.getDomainId());
                            }

                            return ix.getDomainId().equalsIgnoreCase(ii.getDomainId());
                        }).collect(Collectors.toList());

                DataSetDto selectedDataSetDto = DataSetDto.create(dataSetDto.getTableName(),
                        dataSetDto.getTotalCount(), dataSetDto.getColumns(), rows);
                selectedDataSetDto.markInfo(dataSetDto.getSql(), dataSetDto.getFetchedModifiedTime(), dataSetDto.getExportedTime());
                return SyncDataSetDTO.create(targetConfigSnapshot.getTargetType(), selectedDataSetDto, ii);
            }).collect(Collectors.toList());

            //只有是通过摆渡服务的时候, 我们才读取归属都配置
            /**
             * 理论上来说，子站和内网都需要根据归属地才可以同步.
             * 但是目前第一版本，子站的同步直接通过同步功能来判断.
             */
            if (syncDataSetDTOS == null || syncDataSetDTOS.size() == 0) {
                DataSetDto dsDto =
                        DataSetDto.create(dataSetDto.getTableName(),
                                dataSetDto.getTotalCount(), dataSetDto.getColumns(), dataSetDto.getRows());

                return Collections.singleton(SyncDataSetDTO.create(targetConfigSnapshot.getTargetType(), dsDto, null));
            } else {
                //合并
                Collection<SyncDataSetDTO> combinedSyncs = new ArrayList<>();
                syncDataSetDTOS.forEach(sync -> {
                    SyncDataSetDTO selectedDataSetDto = null;
                    Optional<SyncDataSetDTO> selectedDataSetOptional = combinedSyncs.stream().filter(ii -> {
                        return ii.getSyncTargetType().equals(sync.getSyncTargetType()) &&
                                ii.getIdentityValue().equals(sync.getIdentityValue());
                    }).findFirst();

                    if (selectedDataSetOptional.isPresent()) {
                        selectedDataSetDto = selectedDataSetOptional.get();
                    } else {
                        selectedDataSetDto = SyncDataSetDTO.create(sync.getSyncTargetType(), sync.getDataSetDto(), sync.getDomainSuperviseDTO());
                        combinedSyncs.add(selectedDataSetDto);
                    }

                    for (DataSetDto.Row sync_row : sync.getDataSetDto().getRows().stream().collect(Collectors.toList())) {
                        if (!selectedDataSetDto.getDataSetDto().getRows().stream().anyMatch(ix -> ix.getId().equals(sync_row.getId()))) {
                            selectedDataSetDto.getDataSetDto().getRows().add(sync_row);
                        }
                    }

                    Collection<String> deletedIds = selectedDataSetDto.getDataSetDto().getDeletedIds();
                    if (deletedIds == null) {
                        deletedIds = new ArrayList<>();
                    }
                    if (sync.getDataSetDto().getDeletedIds() != null) {
                        deletedIds.addAll(sync.getDataSetDto().getDeletedIds());
                    }

                    selectedDataSetDto.getDataSetDto().setDeletedIds(deletedIds);
                    int totalCount = selectedDataSetDto.getDataSetDto().getTotalCount();

                    selectedDataSetDto.getDataSetDto().setTotalCount(totalCount + sync.getDataSetDto().getTotalCount());
                    selectedDataSetDto.markNote(String.format("合并:%s_%s", selectedDataSetDto.getKeyValue(), sync.getKeyValue()));
                });

                return combinedSyncs;
            }
        }
    }

    private static Map<Long, Collection<FormSyncMetaEntity>> cached_formSyncMetaEntities = new ConcurrentHashMap();

    protected Collection<FormSyncMetaEntity> getAllOnlineFormSyncMetaEntities() {
        Collection<FormSyncMetaEntity> selected_formSyncMetaEntities = null;
        for (Long expiredValue : cached_formSyncMetaEntities.keySet()) {
            if (expiredValue > Instant.now().getEpochSecond()) {
                selected_formSyncMetaEntities = cached_formSyncMetaEntities.get(expiredValue);
            }
        }

        if (selected_formSyncMetaEntities == null) {
            selected_formSyncMetaEntities = this.formSyncMetaRepository.findAllOnline();
            cached_formSyncMetaEntities.clear();
            Instant expiredInstant = Instant.now().minus(-10, ChronoUnit.MINUTES);
            Long expiredValueInSeconds = expiredInstant.getEpochSecond();
            cached_formSyncMetaEntities.put(expiredValueInSeconds, selected_formSyncMetaEntities);

            logger.error(String.format("%s: Collection<FormSyncMetaEntity>过期时间为: %s 后", Instant.now(),
                    Timestamp.from(expiredInstant)));
        }

        return selected_formSyncMetaEntities;
    }
}
