package com.microservices.pms.productLibrary.service.impl;

import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.ServletUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.html.EscapeUtil;
import com.microservices.pms.productLibrary.domain.PmsProductLibraryComponent;
import com.microservices.pms.productLibrary.domain.PmsProductLibraryRepositories;
import com.microservices.pms.productLibrary.domain.ProductLibraryRepositoriesFormatEnum;
import com.microservices.pms.productLibrary.domain.vo.*;
import com.microservices.pms.productLibrary.service.IPmsProductLibraryAsyncService;
import com.microservices.pms.productLibrary.service.IPmsProductLibraryComponentService;
import com.microservices.pms.productLibrary.service.IPmsProductLibraryRepositoriesService;
import com.microservices.pms.productLibrary.service.IPmsProductLibraryService;
import com.microservices.pms.utils.CustomExecutorFactory;
import com.microservices.pms.utils.NexusRequestHelper;
import com.microservices.pms.utils.NexusRequestUrl;
import com.microservices.pms.utils.PythonUtil;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.domain.SysFileBytes;
import com.microservices.system.api.utils.FeignUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author OTTO
 */
@Service
public class PmsProductLibraryAsyncServiceImpl implements IPmsProductLibraryAsyncService {

    private static final Logger log = LoggerFactory.getLogger(PmsProductLibraryAsyncServiceImpl.class);
    @Autowired
    private NexusRequestHelper nexusRequestHelper;
    @Autowired
    @Lazy
    private IPmsProductLibraryComponentService pmsProductLibraryComponentService;
    @Autowired
    @Lazy
    private IPmsProductLibraryRepositoriesService pmsProductLibraryRepositoriesService;
    @Autowired
    @Lazy
    private IPmsProductLibraryService pmsProductLibraryService;
    @Autowired
    private RemoteFileService remoteFileService;

    @Override
    public void syncNexusMavenRepoComponent(PmsProductLibraryRepositories repo, List<PmsProductLibraryComponent> pmsProductLibraryComponents) {
        CustomExecutorFactory.threadPoolExecutor.execute(() -> {
            // 遍历制品库制品文件列表，根据数据库制品文件记录获取对应的制品标识
            boolean isContinuationGet = true;
            NexusComponentAndAssetListVo<NexusAssetVo> assetListVo = new NexusComponentAndAssetListVo<>();
            while (isContinuationGet) {
                assetListVo = nexusRequestHelper.doRequestToJavaObject(NexusRequestUrl.GET_ASSETS(repo.getNexusRepositoriesName(), assetListVo.getContinuationToken()), NexusComponentAndAssetListVo.class, NexusAssetVo.class);
                List<NexusAssetVo> assetList = assetListVo.getItems();
                if (pmsProductLibraryComponents.stream().allMatch(x -> StringUtils.isNotEmpty(x.getIdentifier()))) {
                    break;
                }
                if (StringUtils.isEmpty(assetListVo.getContinuationToken())) {
                    isContinuationGet = false;
                }
                pmsProductLibraryComponents.forEach(component -> {
                    if (StringUtils.isEmpty(component.getIdentifier())) {
                        NexusAssetVo matchAssetVo = assetList.stream().filter(x -> x.getPath().equals(component.getPath())).findFirst().orElse(null);
                        if (matchAssetVo != null) {
                            component.setNexusMavenAssetVo(matchAssetVo);
                            pmsProductLibraryComponentService.updatePmsProductLibraryComponent(component);
                        }
                    }
                });
            }
        });
    }

    @Override
    public void asyncAddCommonAssetToProductRepo(PmsProductLibraryRepositories productRepo, PmsProductLibraryRepositories repo, List<NexusAssetResultDataVo> nexusAssetResultDataVoList, String directory) {
        CustomExecutorFactory.threadPoolExecutor.execute(() -> {
            pmsProductLibraryRepositoriesService.lockRepository(repo.getPmsEnterpriseIdentifier(), repo.getId());
            if (nexusAssetResultDataVoList == null || nexusAssetResultDataVoList.isEmpty()) {
                return;
            }
            List<String> componentIdList = nexusAssetResultDataVoList.stream().map(NexusAssetResultDataVo::getId).collect(Collectors.toList());
            pmsProductLibraryComponentService.lockComponentList(productRepo.getId(), componentIdList);
            try {
                for (NexusAssetResultDataVo nexusAssetResultDataVo : nexusAssetResultDataVoList) {
                    if (StringUtils.isEmpty(nexusAssetResultDataVo.getVersion())) {
                        nexusAssetResultDataVo.setVersion("");
                    }
                    List<AssetRawInputVo> assetRawInputVoList = new ArrayList<>();
                    AssetRawInputVo assetRawInputVo = new AssetRawInputVo();
                    assetRawInputVo.setAssetIdentifier(nexusAssetResultDataVo.getId());
                    assetRawInputVo.setFilename(nexusAssetResultDataVo.getFileName());
                    String customAttribute = null;
                    if (nexusAssetResultDataVo.getAttributes() != null && nexusAssetResultDataVo.getAttributes().containsKey("customAttribute")) {
                        customAttribute = nexusAssetResultDataVo.getAttributes().getJSONObject("customAttribute").toJSONString();
                    }
                    if (StringUtils.isNotEmpty(nexusAssetResultDataVo.getFileIdentifier())) {
                        assetRawInputVo.setFileIdentifier(nexusAssetResultDataVo.getFileIdentifier());
                    } else {
                        assetRawInputVo.setFileIdentifier("");
                        String nexusFileUrl = EscapeUtil.removeExtraSlashOfUrl(repo.getUrl() + "/" + nexusAssetResultDataVo.getName());
                        SysFileBytes fileBytes = nexusRequestHelper.getFileBytes(nexusFileUrl);
                        assetRawInputVo.setFileBytes(fileBytes);
                    }
                    assetRawInputVoList.add(assetRawInputVo);
                    insertRawComponentToProductRepo(productRepo, assetRawInputVoList, nexusAssetResultDataVo.getVersion(), customAttribute, directory);
                }
            } finally {
                pmsProductLibraryRepositoriesService.unlockRepository(repo.getPmsEnterpriseIdentifier(), repo.getId());
                pmsProductLibraryComponentService.unlockComponentList(productRepo.getId(), componentIdList);
            }
        });
    }

    @Override
    public void syncRepoToSnapshotRepo(PmsProductLibraryRepositories repo, PmsProductLibraryRepositories snapshotRepo) {
        CustomExecutorFactory.threadPoolExecutor.execute(() -> {
            try {
                pmsProductLibraryRepositoriesService.lockRepository(repo.getPmsEnterpriseIdentifier(), repo.getId());
                ProductLibraryRepositoriesFormatEnum formatEnum = ProductLibraryRepositoriesFormatEnum.getByKey(repo.getFormat());
                switch (formatEnum) {
                    case MAVEN: {
                        syncMavenRepoToSnapshotRepo(repo, snapshotRepo);
                        break;
                    }
                    case NPM: {
                        syncNpmRepoToSnapshotRepo(repo, snapshotRepo);
                        break;
                    }
                    case PYPI: {
                        syncPypiRepoToSnapshotRepo(repo, snapshotRepo);
                        break;
                    }
                    case RAW:
                    case GO:
                    case CONDA:
                    case XXXXX: {
                        syncRawRepoToSnapshotRepo(repo, snapshotRepo);
                        break;
                    }
                }
            } catch (Exception e) {
                log.error("同步制品库制品到快照库时失败(制品库id[{}],快照库id[{}]):{}", repo.getId(), snapshotRepo.getId(), e.getMessage());
            } finally {
                pmsProductLibraryRepositoriesService.unlockRepository(repo.getPmsEnterpriseIdentifier(), repo.getId());
            }

        });
    }

    @Override
    public void asyncAddDockerComponentToProductRepo(PmsProductLibraryRepositories productRepo, PmsProductLibraryRepositories repo, List<NexusComponentResultDataVo> nexusComponentResultDataVoList, String directory) {
        CustomExecutorFactory.threadPoolExecutor.execute(() -> {
            pmsProductLibraryRepositoriesService.lockRepository(repo.getPmsEnterpriseIdentifier(), repo.getId());
            if (nexusComponentResultDataVoList == null || nexusComponentResultDataVoList.isEmpty()) {
                return;
            }
            List<String> componentIdList = nexusComponentResultDataVoList.stream().map(NexusComponentResultDataVo::getId).collect(Collectors.toList());
            pmsProductLibraryComponentService.lockComponentList(productRepo.getId(), componentIdList);
            try {
                // 解析docker库的映射端口
                RepoAttributesVo dockerRepoAttributes = repo.getRepoAttributes();
                Integer dockerHttpPort = dockerRepoAttributes.getDocker().getHttpPort();
                for (NexusComponentResultDataVo nexusComponentResultDataVo : nexusComponentResultDataVoList) {
                    List<AssetRawInputVo> assetRawInputVoList = new ArrayList<>();
                    AssetRawInputVo assetRawInputVo = new AssetRawInputVo();
                    String dockerImgName = nexusComponentResultDataVo.getName().replaceAll("/", "_").replaceAll("\\\\", "_");
                    String dockerImgFileName = String.format("%s_%s.tar", dockerImgName, nexusComponentResultDataVo.getVersion());
                    assetRawInputVo.setFilename(dockerImgFileName);
                    assetRawInputVo.setFileIdentifier("");
                    String dockerImgUrl = EscapeUtil.removeExtraSlashOfUrl(String.format("%s:%d/%s:%s", nexusRequestHelper.getNexusIp(), dockerHttpPort, nexusComponentResultDataVo.getName(), nexusComponentResultDataVo.getVersion()));
                    try {
                        InputStream fileInputStream = PythonUtil.pullDockerImage(dockerImgUrl, dockerImgFileName);
                        SysFileBytes sysFileBytes = new SysFileBytes();
                        sysFileBytes.setFileInputStream(fileInputStream);
                        sysFileBytes.setFileName(dockerImgFileName);
                        assetRawInputVo.setFileBytes(sysFileBytes);
                        assetRawInputVo.setAssetIdentifier(nexusComponentResultDataVo.getId());
                        assetRawInputVoList.add(assetRawInputVo);
                        insertRawComponentToProductRepo(productRepo, assetRawInputVoList, nexusComponentResultDataVo.getVersion(), null, directory);
                    } catch (IOException e) {
                        log.error("拉取Docker镜像失败：{}", e.getMessage());
                    }
                }
            } finally {
                pmsProductLibraryRepositoriesService.unlockRepository(repo.getPmsEnterpriseIdentifier(), repo.getId());
                pmsProductLibraryComponentService.unlockComponentList(productRepo.getId(), componentIdList);
            }
        });
    }

    @Override
    public void packagedProduct(PmsProductLibraryRepositories productRepo) {
        CustomExecutorFactory.threadPoolExecutor.execute(() -> {
            pmsProductLibraryRepositoriesService.lockRepository(productRepo.getPmsEnterpriseIdentifier(), productRepo.getId());
            try {
                List<PmsProductLibraryComponent> componentList =
                        pmsProductLibraryComponentService.selectPmsProductLibraryComponentListByDatabase(productRepo.getId());
                HashMap<String, String> packagedStructure = new HashMap<>();
                for (PmsProductLibraryComponent component : componentList) {
                    String fileSource = component.getFileIdentifier();
                    if (StringUtils.isEmpty(fileSource)) {
                        fileSource = EscapeUtil.removeExtraSlashOfUrl(productRepo.getUrl() + "/" + ServletUtils.urlEncodeExcludeSlashes(component.getPath()));
                    }
                    packagedStructure.put(component.getPath(), fileSource);
                }
                String fileIdentifier = FeignUtils.getReturnData(
                        remoteFileService.packagedFile(
                                packagedStructure,
                                String.format("product-%s-%s", productRepo.getAlias(), DateUtils.dateTimeNow()),
                                "pms",
                                productRepo.getPmsEnterpriseIdentifier() + "/productLibrary",
                                SecurityConstants.INNER)
                );
                productRepo.setPackagedFileIdentifier(fileIdentifier);
                pmsProductLibraryService.updatePmsProductLibrary(productRepo);
            } finally {
                pmsProductLibraryRepositoriesService.unlockRepository(productRepo.getPmsEnterpriseIdentifier(), productRepo.getId());
            }
        });
    }

    private void syncMavenRepoToSnapshotRepo(PmsProductLibraryRepositories repo, PmsProductLibraryRepositories snapshotRepo) {
        List<PmsProductLibraryComponent> pmsProductLibraryComponentList = pmsProductLibraryComponentService.selectPmsProductLibraryComponentListByDatabase(repo.getId());
        for (PmsProductLibraryComponent pmsProductLibraryComponent : pmsProductLibraryComponentList) {
            ComponentMavenInputVo componentMavenInputVo = pmsProductLibraryComponent.toComponentMavenInputVo(snapshotRepo);
            List<AssetMavenInputVo> assetMavenInputVoList = getAssetMavenInputVoList(repo, pmsProductLibraryComponent, componentMavenInputVo);
            if (assetMavenInputVoList != null) {
                componentMavenInputVo.setAssetList(assetMavenInputVoList);
                pmsProductLibraryComponentService.insertMavenComponent(componentMavenInputVo);
            }
        }
    }

    private void syncNpmRepoToSnapshotRepo(PmsProductLibraryRepositories repo, PmsProductLibraryRepositories snapshotRepo) {
        List<NexusAssetVo> assetList = getAllAssetByNexus(repo);
        for (NexusAssetVo asset : assetList) {
            if (asset.getNpm() == null) {
                continue;
            }
            ComponentInputVo componentInputVo = new ComponentInputVo();
            componentInputVo.setRepoName(snapshotRepo.getName());
            componentInputVo.setSnapshotName(snapshotRepo.getSnapshotName());
            componentInputVo.setEnterpriseIdentifier(snapshotRepo.getPmsEnterpriseIdentifier());
            componentInputVo.setFileIdentifier("");
            String nexusFileUrl = EscapeUtil.removeExtraSlashOfUrl(repo.getUrl() + "/" + asset.getPath());
            SysFileBytes fileBytes = nexusRequestHelper.getFileBytes(nexusFileUrl);
            if (fileBytes == null) {
                continue;
            }
            componentInputVo.setFileBytes(fileBytes);
            pmsProductLibraryComponentService.insertNpmComponent(componentInputVo);
        }
    }

    private void syncPypiRepoToSnapshotRepo(PmsProductLibraryRepositories repo, PmsProductLibraryRepositories snapshotRepo) {
        List<NexusAssetVo> assetList = getAllAssetByNexus(repo);
        for (NexusAssetVo asset : assetList) {
            if (asset.getPypi() == null) {
                continue;
            }
            ComponentInputVo componentInputVo = new ComponentInputVo();
            componentInputVo.setRepoName(snapshotRepo.getName());
            componentInputVo.setSnapshotName(snapshotRepo.getSnapshotName());
            componentInputVo.setEnterpriseIdentifier(snapshotRepo.getPmsEnterpriseIdentifier());
            componentInputVo.setFileIdentifier("");
            String nexusFileUrl = EscapeUtil.removeExtraSlashOfUrl(repo.getUrl() + "/" + asset.getPath());
            SysFileBytes fileBytes = nexusRequestHelper.getFileBytes(nexusFileUrl);
            if (fileBytes == null) {
                continue;
            }
            componentInputVo.setFileBytes(fileBytes);
            pmsProductLibraryComponentService.insertPypiComponent(componentInputVo);
        }
    }

    private void syncRawRepoToSnapshotRepo(PmsProductLibraryRepositories repo, PmsProductLibraryRepositories snapshotRepo) {
        List<PmsProductLibraryComponent> pmsProductLibraryComponentList = pmsProductLibraryComponentService.selectPmsProductLibraryComponentListByDatabase(repo.getId());
        for (PmsProductLibraryComponent pmsProductLibraryComponent : pmsProductLibraryComponentList) {
            ComponentRawInputVo componentRawInputVo = pmsProductLibraryComponent.toComponentRawInputVo(snapshotRepo);
            List<AssetRawInputVo> assetRawInputVoList = getAssetRawInputVoList(repo, pmsProductLibraryComponent, componentRawInputVo);
            if (assetRawInputVoList != null) {
                componentRawInputVo.setAssetList(assetRawInputVoList);
                pmsProductLibraryComponentService.insertRawComponent(componentRawInputVo);
            }
        }
    }

    private List<AssetRawInputVo> getAssetRawInputVoList(PmsProductLibraryRepositories repo, PmsProductLibraryComponent pmsProductLibraryComponent, ComponentRawInputVo componentRawInputVo) {
        List<AssetRawInputVo> assetRawInputVoList = componentRawInputVo.getAssetList();
        for (AssetRawInputVo assetRawInputVo : assetRawInputVoList) {
            if (StringUtils.isEmpty(assetRawInputVo.getFileIdentifier())) {
                assetRawInputVo.setFileIdentifier("");
                String nexusFileUrl = EscapeUtil.removeExtraSlashOfUrl(repo.getUrl() + "/" + pmsProductLibraryComponent.getPath());
                SysFileBytes fileBytes = nexusRequestHelper.getFileBytes(nexusFileUrl);
                if (fileBytes == null) {
                    return null;
                }
                assetRawInputVo.setFileBytes(fileBytes);
            }
        }
        return assetRawInputVoList;
    }

    private List<AssetMavenInputVo> getAssetMavenInputVoList(PmsProductLibraryRepositories repo, PmsProductLibraryComponent pmsProductLibraryComponent, ComponentMavenInputVo componentMavenInputVo) {
        List<AssetMavenInputVo> assetMavenInputVoList = componentMavenInputVo.getAssetList();
        for (AssetMavenInputVo assetMavenInputVo : assetMavenInputVoList) {
            if (StringUtils.isEmpty(assetMavenInputVo.getFileIdentifier())) {
                assetMavenInputVo.setFileIdentifier("");
                String nexusFileUrl = EscapeUtil.removeExtraSlashOfUrl(repo.getUrl() + "/" + pmsProductLibraryComponent.getPath());
                SysFileBytes fileBytes = nexusRequestHelper.getFileBytes(nexusFileUrl);
                if (fileBytes == null) {
                    return null;
                }
                assetMavenInputVo.setFileBytes(fileBytes);
            }
        }
        return assetMavenInputVoList;
    }

    private void insertRawComponentToProductRepo(PmsProductLibraryRepositories productRepo, List<AssetRawInputVo> assetRawInputVoList, String version, String customAttribute, String directory) {
        ComponentRawInputVo componentRawInputVo = new ComponentRawInputVo();
        componentRawInputVo.setRepoName(productRepo.getName());
        componentRawInputVo.setEnterpriseIdentifier(productRepo.getPmsEnterpriseIdentifier());
        componentRawInputVo.setFormat(ProductLibraryRepositoriesFormatEnum.PRODUCT.getKey());
        componentRawInputVo.setVersion(version);
        componentRawInputVo.setAttributes(customAttribute);
        componentRawInputVo.setAssetList(assetRawInputVoList);
        componentRawInputVo.setDirectory(directory);
        pmsProductLibraryComponentService.insertRawComponent(componentRawInputVo);
    }

    private List<NexusAssetVo> getAllAssetByNexus(PmsProductLibraryRepositories repo) {
        List<NexusAssetVo> assetList = new ArrayList<>();
        // 遍历制品库制品文件列表，根据数据库制品文件记录获取对应的制品标识
        boolean isContinuationGet = true;
        NexusComponentAndAssetListVo<NexusAssetVo> assetListVo = new NexusComponentAndAssetListVo<>();
        while (isContinuationGet) {
            assetListVo = nexusRequestHelper.doRequestToJavaObject(NexusRequestUrl.GET_ASSETS(repo.getNexusRepositoriesName(), assetListVo.getContinuationToken()), NexusComponentAndAssetListVo.class, NexusAssetVo.class);
            assetList.addAll(assetListVo.getItems());
            if (StringUtils.isEmpty(assetListVo.getContinuationToken())) {
                isContinuationGet = false;
            }
        }
        return assetList;
    }
}
