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

import cn.hutool.http.HttpResponse;
import com.bcxin.backend.core.AppConfigProperty;
import com.bcxin.backend.core.components.StorageProvider;
import com.bcxin.backend.core.exceptions.SaasBadException;
import com.bcxin.backend.core.exceptions.SaasExceptionAbstract;
import com.bcxin.backend.core.exceptions.SaasIgnoredException;
import com.bcxin.backend.core.utils.ExceptionUtils;
import com.bcxin.backend.domain.SimpleLocalLockComponent;
import com.bcxin.backend.domain.configs.SyncLimiter;
import com.bcxin.backend.domain.enums.SyncType;
import com.bcxin.backend.domain.models.SuperviseDTO;
import com.bcxin.backend.domain.repositories.DomainSuperviseRepository;
import com.bcxin.backend.domain.syncs.components.HttpRequestProvider;
import com.bcxin.backend.domain.syncs.services.CleanQueuesCacheService;
import com.bcxin.backend.domain.system.configs.SyncLimiterConfig;
import com.bcxin.backend.domain.syncs.dtos.*;
import com.bcxin.backend.domain.syncs.repositories.DataSyncQueueRepository;
import com.bcxin.backend.domain.syncs.services.DataSyncQueueService;
import com.bcxin.runtime.domain.constants.FieldNames;
import com.bcxin.runtime.domain.snapshoots.FormSyncTargetConfigSnapshot;
import com.bcxin.runtime.domain.syncs.dtos.DataSetDto;
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.apache.http.client.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service("FtpDataSyncQueueServiceImpl")
public class FtpDataSyncQueueServiceImpl extends DataSyncQueueServiceBase implements DataSyncQueueService {
    @Autowired
    private SyncLimiterConfig syncLimiterConfig;
    @Autowired
    private CleanQueuesCacheService cleanQueuesCacheService;

    private static Map<String, SyncLimiter> syncLimiterMap = new ConcurrentHashMap();

    public FtpDataSyncQueueServiceImpl(DataSyncQueueRepository dataSyncQueueRepository,
                                       JsonProvider jsonProvider, HttpRequestProvider httpRequestProvider,
                                       AppConfigProperty appConfigProperty, StorageProvider storageProvider,
                                       DomainSuperviseRepository domainSuperviseRepository,
                                       SimpleLocalLockComponent simpleLocalLockComponent) {
        super(dataSyncQueueRepository, jsonProvider, httpRequestProvider, appConfigProperty, storageProvider,
                domainSuperviseRepository, simpleLocalLockComponent);
    }

    @Override
    public void dispatchData() {
        this.dispatch(SyncType.DataViaFtp);
    }

    @Override
    public void dispatchFile() {
        this.dispatch(SyncType.FileViaFtp);
    }

    /**
     * 获取摆渡对接的参数信息
     *
     * @param dataSyncParameter
     * @return
     * @throws UnsupportedEncodingException
     */
    protected FtpDataImportRequestDto createFtpDataImportRequest(
            SyncDataSetDTO syncDataSetDTO,
            boolean dispatchFile,
            DataSyncParameter dataSyncParameter) {
        StringBuilder trackingLog = new StringBuilder("开始执行createFtpDataImportRequest;");

        try {
            DataSyncQueueEntity data = dataSyncParameter.getDataSyncQueueEntity();
            trackingLog.append("syncDataSetDTO.getDataSetDto();");

            DataSetDto dataSetDto = syncDataSetDTO.getDataSetDto();
            trackingLog.append("DbDataImportRequestDto.DataSet.create();");
            DbDataImportRequestDto.DataSet dbDataSet = DbDataImportRequestDto.DataSet
                    .create(data.getId(), dataSyncParameter.getMapKey(),
                            dataSetDto.getRows(), dataSetDto.getDeletedIds());
            trackingLog.append("Done.DbDataImportRequestDto.DataSet.create();");
            if (dbDataSet.getDataRecords() == null) {
                throw new SaasBadException("dbDataSet.getDataRecords()对象为空无效!");
            }

            if (syncDataSetDTO.getDomainSuperviseDTO() == null) {
                throw new SaasIgnoredException("无归属地信息, 因此，无法确定推送到哪里(syncDataSetDTO.getDomainSuperviseDTO())");
            }

            if (syncDataSetDTO.getDomainSuperviseDTO().getSupervise() == null) {
                throw new SaasIgnoredException("无归属地信息, 因此，无法确定推送到哪里(syncDataSetDTO.getDomainSuperviseDTO().getSupervise())");
            }

            trackingLog.append("syncDataSetDTO.getDomainSuperviseDTO().getSupervise();");
            SuperviseDTO supervise = syncDataSetDTO.getDomainSuperviseDTO().getSupervise();
            FtpDataImportRequestDto requestDto = getFtpDataImportRequest(supervise, dataSyncParameter);

            trackingLog.append("Done.FtpDataImportRequestDto.create;");
            DbDataImportRequestDto requestPlayData = DbDataImportRequestDto.create(
                    data.getData(),
                    Collections.singleton(dbDataSet));

            String fileUrls = null;
            Map<String, String> pathMap = null;
            if (dispatchFile) {
                fileUrls = dbDataSet.getDataRecords().stream().flatMap(ii -> ii.getFieldValues().stream())
                        .filter(ii -> ii.getIsFileValue() && ii.getFileUrls() != null && ii.getFileUrls().size() > 0)
                        .flatMap(ii -> ii.getFileUrls().stream()).filter(ii -> ii != null)
                        .distinct()
                        .collect(Collectors.joining(","));
                pathMap = dbDataSet.getDataRecords().stream().flatMap(ii -> ii.getFieldValues().stream())
                        .filter(ii -> ii.getIsFileValue() && ii.getFileUrls() != null && ii.getFileUrls().size() > 0)
                        .flatMap(ii -> ii.getPathOptions().stream()).filter(ii -> ii != null)
                        .collect(Collectors.toMap(FieldNames.PathOption::getOriginalPath, FieldNames.PathOption::getPath,(v1,v2)->v1));
            }

            String jsonPlayData = this.jsonProvider.getJson(requestPlayData);
            trackingLog.append("requestDto.assign(dispatchFile, jsonPlayData);");
            requestDto.assign(dispatchFile, jsonPlayData, fileUrls,pathMap);

            return requestDto;
        } catch (Exception ex) {
            if (ex instanceof SaasExceptionAbstract) {
                throw ex;
            }

            throw new SaasBadException(String.format("createFtpDataImportRequest系统异常:%s", trackingLog), ex);
        }
    }

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

    private SyncDataMeta saveFtpRequestData(FtpDataImportRequestDto dataImportRequestDto) {
        String path = String.format("/uploads/sync/response/ftp/%s/%s-%s.txt",
                simpleDateFormat.format(new Date()),
                dataImportRequestDto.getFileType(), UUID.randomUUID());

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

        return SyncDataMeta.create(path, content.length, realPath, Collections.EMPTY_LIST);
    }

    @Override
    protected void executeSyncDataCore(DataSyncParameter dataSyncParameter) {
        this.executeSync(false, dataSyncParameter);
    }

    @Override
    protected void executeSyncFileCore(DataSyncParameter dataSyncParameter) {
        this.executeSync(true, dataSyncParameter);
    }

    @Override
    protected boolean isMatchExecuteCondition(boolean isDispatchFile, DataSyncQueueEntity data) {
        return ((data.getStatus() == SyncProcessStatus.SyncViaFTP && !isDispatchFile) ||
                (data.getFileSyncStatus() == SyncProcessStatus.SyncViaFTP && isDispatchFile)) &&
                super.isMatchExecuteCondition(isDispatchFile, data);
    }

    private void executeSync(boolean dispatchFile, DataSyncParameter dataSyncParameter) {
        DataSyncQueueEntity data = dataSyncParameter.getDataSyncQueueEntity();
        StringBuilder sb = new StringBuilder();
        try {
            FormSyncTargetConfigSnapshot formSyncTargetConfig = dataSyncParameter.getFormSyncTargetConfig();
            sb.append("开始获取this.jsonProvider.getData(getSyncRealData(data), DataSetDto.class)");
            SyncDataSetDTO syncDataSetDTO = this.jsonProvider.getData(getSyncRealData(data), SyncDataSetDTO.class);
            //SyncDataSetDTO syncDataSetDTO = this.jsonProvider.getData(aa, SyncDataSetDTO.class);
            FtpDataImportRequestDto requestDto = null;
            try {
                sb.append("；开始获取DbDataImportRequestDto.create(Collections.singleton(dbDataSet))");
                try {
                    requestDto = createFtpDataImportRequest(syncDataSetDTO, dispatchFile, dataSyncParameter);
                    if (dispatchFile && !requestDto.isNeedFile2Execute()) {
                        data.changeFileSyncStatus(SyncProcessStatus.Done, String.format("无效参数,无需执行"));
                        return;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    sb.append(String.format("创建对象异常:%s", this.jsonProvider.getJson(syncDataSetDTO)));
                    throw new SaasBadException(String.format("createFtpDataImportRequest发生异常:%s", ExceptionUtils.getStackMessage(ex)));
                }
                sb.append("；SyncDataResponse syncDataResponse = SyncDataResponse.create(");
                String ftpUrl = requestDto.getRequestUrl(formSyncTargetConfig);
                SyncLimiterConfig.Limiter limiter = null;
                if (!CollectionUtils.isEmpty(this.syncLimiterConfig.getReplenishRate()) && !CollectionUtils.isEmpty(this.syncLimiterConfig.getReplenishRate().get(requestDto.getRegionId()))) {
                    Collection<SyncLimiterConfig.Limiter> limiters = this.syncLimiterConfig.getReplenishRate().get(requestDto.getRegionId());
                    //匹配各省份外网摆渡服务地址
                    List<SyncLimiterConfig.Limiter> list = new ArrayList<SyncLimiterConfig.Limiter>(limiters);
                    ftpUrl = list.get(0).getFtpUrl();
                }
                SyncLimiter syncLimiter = null;
                if (syncLimiterMap.get(ftpUrl) == null) {
                    int limitMax = this.syncLimiterConfig.getDefaultMax();
                    if (limiter != null) {
                        limitMax = limiter.getMax();
                    }
                    syncLimiter = new SyncLimiter();
                    syncLimiter.setMaxCount(limitMax);
                    syncLimiter.setInterval(this.syncLimiterConfig.getInterval());
                    syncLimiterMap.put(ftpUrl, syncLimiter);
                } else {
                    syncLimiter = syncLimiterMap.get(ftpUrl);
                }
                if (!syncLimiter.get()) {
                    logger.error("当前时间段内调用摆渡服务url={},已经达到最大调用次数{}，等待下一个时间段内再执行", ftpUrl, syncLimiter.getMaxCount());
                    return;
                }

                SyncDataResponse syncDataResponse = SyncDataResponse.create(
                        ftpUrl,
                        formSyncTargetConfig.getHeaders(),
                        String.format("暂不存储请求参数:(isSyncViaFtp=%s;targetApp=%s;targetServiceUrl=%s;configUrl=%s)",
                                requestDto.isSyncViaFtp(), requestDto.getTargetApp(), requestDto.getTargetServiceUrl(),
                                formSyncTargetConfig.getUrl())
                        //this.jsonProvider.getJson(saveRequestData(requestDto))
                        //this.jsonProvider.getJson(saveFtpRequestData(requestDto))
                        //this.jsonProvider.getJson(requestDto)
                );
                String requestBody = this.jsonProvider.getJson(requestDto);
                String dateKey = DateUtils.formatDate(new Date(), "yyyy-MM-dd");
                if (!CollectionUtils.isEmpty(cleanQueuesCacheService.getQueuesIds().get(dateKey)) && cleanQueuesCacheService.getQueuesIds().get(dateKey).containsKey(data.getId())) {
                    String msgId = cleanQueuesCacheService.getQueuesIds().get(dateKey).get(data.getId());
                    data.assignThirdParty(dispatchFile, SyncProcessStatus.DoneViaFTP, msgId, "调用摆渡服务成功");
                    logger.error("queues表id为{}已调用摆渡服务成功,消息id为{}，无需重复调用", data.getId(), msgId);
                    return;
                }
                logger.error("queues表id为{}，开始调用摆渡服务{}", data.getId(), ftpUrl);
                HttpResponse result = executePostRequest(syncDataResponse.getRequestUrl(),
                        syncDataResponse.getRequestHeaders(),
                        requestBody);
                if (result.getStatus() == 200) {
                    syncLimiter.set();
                }
                syncDataResponse.assign(result.getStatus(), result.body());
                String syncResponseBody = null;
                if (dispatchFile) {
                    syncDataResponse.assignOriginalRequestBody(requestBody);
                    syncResponseBody = this.jsonProvider.getJson(syncDataResponse);
                } else {
                    syncResponseBody = this.jsonProvider.getJson(syncDataResponse);
                }

                try {
                    FtpDataImportResponseDto responseDto = this.jsonProvider.getData(syncDataResponse.getBody(), FtpDataImportResponseDto.class);
                    if (StringUtils.hasLength(syncDataResponse.getBody()) && syncDataResponse.getBody().length() > 795) {
                        data.setShortResult(syncDataResponse.getBody().substring(0, 795));
                    } else {
                        data.setShortResult(syncDataResponse.getBody());
                    }
                    logger.error("queues表id为{}调用摆渡服务响应结果为{}", data.getId(), data.getShortResult());
                    if (result.getStatus() >= 200 && responseDto != null && responseDto.IsSucceed()) {
                        data.assignThirdParty(dispatchFile, SyncProcessStatus.DoneViaFTP, responseDto.getData(), syncResponseBody);
                        if (!StringUtils.isEmpty(data.getThirdPartyReferenceId())) {
                            Map<String, String> queuesIdsMap = cleanQueuesCacheService.getQueuesIds().get(dateKey);
                            if (CollectionUtils.isEmpty(queuesIdsMap)) {
                                queuesIdsMap = new HashMap<String, String>();
                            }
                            queuesIdsMap.put(data.getId(), data.getThirdPartyReferenceId());
                            cleanQueuesCacheService.getQueuesIds().put(dateKey, queuesIdsMap);
                        }
                    } else {
                        syncDataResponse.assignOriginalRequestBody(requestBody);
                        syncResponseBody = this.jsonProvider.getJson(syncDataResponse);

                        data.assignThirdParty(dispatchFile, SyncProcessStatus.ErrorViaFTP,
                                responseDto == null ? "EMPTY" : responseDto.getData(),
                                syncResponseBody);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    SyncDataResponse.ProcessMsg processMsg =
                            SyncDataResponse.ProcessMsg.create(ExceptionUtils.getStackMessage(ex),
                                    syncDataResponse.getBody());
                    syncDataResponse.assignProcessedMsg(this.jsonProvider.getJson(processMsg));
                    syncResponseBody = this.jsonProvider.getJson(syncDataResponse);

                    if (!dispatchFile) {
                        data.changeDataSyncStatus(SyncProcessStatus.ErrorViaFTP, syncResponseBody);
                    } else {
                        data.changeFileSyncStatus(SyncProcessStatus.ErrorViaFTP, syncResponseBody);
                    }
                }

                sb.append(String.format("%s:异常: DataSyncQueueServiceImpl.dispatchData:url=%s; 响应状态=%s; 详细= %s",
                        new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()),
                        ftpUrl,
                        result.getStatus(), data.getResult()));
                this.printBusinessMsg(sb.toString());
            } catch (SaasIgnoredException cEx) {
                cEx.printStackTrace();
                String domainId = null;
                Optional<String> firstDomainIdOptional = syncDataSetDTO.getDataSetDto().getRows().stream()
                        .map(ix -> ix.getDomainId())
                        .filter(ii -> StringUtils.hasLength(ii))
                        .findFirst();
                if (firstDomainIdOptional.isPresent()) {
                    domainId = firstDomainIdOptional.get();
                }

                if (!dispatchFile) {
                    data.changeDataSyncStatus(
                            SyncProcessStatus.DoneViaFTP, String.format("异常: %s: 企业(%s), 明细: %s; 具体:%s",
                                    cEx.getMessage(),
                                    domainId,
                                    sb,
                                    ExceptionUtils.getStackMessage(cEx)));
                } else {
                    data.changeFileSyncStatus(
                            SyncProcessStatus.DoneViaFTP, String.format("异常: %s: 企业(%s), 明细: %s; 具体:%s",
                                    cEx.getMessage(),
                                    domainId,
                                    sb,
                                    ExceptionUtils.getStackMessage(cEx)));
                }
            } catch (Exception cEx) {
                cEx.printStackTrace();
                String domainId = String.format("【无找到企业归属地信息(%s)】", data.getName());
                if (syncDataSetDTO.getDomainSuperviseDTO() != null) {
                    domainId = String.format("【无找到企业(%s)归属地信息(%s)】",
                            syncDataSetDTO.getDomainSuperviseDTO().getDomainId(),
                            data.getName());
                }
                if (!dispatchFile) {
                    data.changeDataSyncStatus(SyncProcessStatus.ErrorViaFTP, String.format("摆渡同步异常: 企业(%s), 明细: %s, 具体：%s",
                            domainId, sb, ExceptionUtils.getStackMessage(cEx)));
                } else {
                    data.changeFileSyncStatus(SyncProcessStatus.ErrorViaFTP, String.format("摆渡同步异常: 企业(%s), 明细: %s, 具体：%s",
                            domainId, sb, ExceptionUtils.getStackMessage(cEx)));
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            String msg = String.format("%s:异常: DataSyncQueueServiceImpl.dispatchData:【跟踪：%s】%s; 详细: %s",
                    new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()),
                    sb,
                    dataSyncParameter.getDataSyncQueueEntity().getId(),
                    ExceptionUtils.getStackMessage(ex));
            throw new SaasBadException(msg, ex);
        }
    }

    private FtpDataImportRequestDto getFtpDataImportRequest(SuperviseDTO supervise, DataSyncParameter dataSyncParameter) {
        StringBuilder trackingLog = new StringBuilder();
        String regionId = supervise.getRegionId();
        String srcApp = supervise.getSrcApp();//data.getItemValueAsString("systemURL");
        //监管内网地址
        String targetApp = supervise.getTargetApp();//data.getItemValueAsString("systemURLNw");
        if (dataSyncParameter.getOtherTargetApp() != null && dataSyncParameter.getOtherTargetApp() != "") {
            targetApp = dataSyncParameter.getOtherTargetApp();
        }
        //监管内网接收接口地址
        String targetServiceUrl = supervise.getTargetServiceUrl();//data.getItemValueAsString("targetServiceUrl");
        //监管外网回调地址
        String callbackUrl = supervise.getCallbackUrl();//data.getItemValueAsString("callbackUrl");
        trackingLog.append(String.format("已经有srcApp信息:%s;", srcApp));
        if (!StringUtils.hasLength(srcApp)) {
            throw new SaasBadException("监管归属地SrcApp无效");
        }

        /**
         * 临时固定, 上线调整后替换掉
         */
        boolean isSyncViaFtp = appConfigProperty.isSyncViaFtp(targetApp);
        trackingLog.append(String.format("original.srcApp=%s;", srcApp));
        if (isSyncViaFtp) {
            if (srcApp.contains("113.246.57.21:7002") || srcApp.contains("v5gaout.pre.baibaodun.cn")) {
                srcApp = "http://119.3.247.211/obpm";
            } else if (srcApp.contains("117.78.35.63:8060")) {
                srcApp = "http://117.78.35.63:8080/obpm";
            }
        }

        targetServiceUrl = "/v2/sync/ftp/data-import";
        callbackUrl = String.format("%s/v2/sync/ftp/callback", srcApp);
        String outRequsetDirKey = storageProvider.getDir();
        FtpDataImportRequestDto requestDto =
                FtpDataImportRequestDto.create(regionId, srcApp, targetApp, targetServiceUrl, callbackUrl, isSyncViaFtp, outRequsetDirKey);

        return requestDto;
    }
}
