package cn.reghao.devops.web.mgr.app.db.query.impl;

import cn.reghao.devops.common.agent.app.dto.AppInfo;
import cn.reghao.devops.common.msg.constant.NodeStatus;
import cn.reghao.devops.web.mgr.app.db.repository.AppDeployingRepository;
import cn.reghao.devops.web.mgr.app.db.repository.config.AppDeployConfigRepository;
import cn.reghao.devops.web.mgr.app.db.repository.log.DeployLogRepository;
import cn.reghao.devops.web.mgr.app.model.po.AppBuilding;
import cn.reghao.devops.web.mgr.app.model.po.AppDeploying;
import cn.reghao.devops.web.mgr.app.model.po.AppDeployingNode;
import cn.reghao.devops.web.mgr.app.model.po.config.AppConfig;
import cn.reghao.devops.web.mgr.app.model.po.config.AppDeployConfig;
import cn.reghao.devops.web.mgr.app.db.query.AppDeployQuery;
import cn.reghao.devops.web.mgr.app.model.po.log.BuildLog;
import cn.reghao.devops.web.mgr.app.model.vo.AppDeployConfigVo;
import cn.reghao.devops.web.mgr.app.model.vo.AppDeployingVO;
import cn.reghao.devops.web.mgr.app.model.vo.AppRunning;
import cn.reghao.devops.web.mgr.app.model.vo.AppRunningVO;
import cn.reghao.devops.web.mgr.machine.db.query.MachineQuery;
import cn.reghao.devops.web.mgr.machine.model.po.MachineHost;
import cn.reghao.jutil.jdk.db.PageList;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2021-06-02 15:01:18
 */
@Service
public class AppDeployQueryImpl implements AppDeployQuery {
    private final AppDeployConfigRepository deployConfigRepository;
    private final AppDeployingRepository deployingRepository;
    private DeployLogRepository deployLogRepository;
    private final MachineQuery machineQuery;

    public AppDeployQueryImpl(AppDeployConfigRepository deployConfigRepository, AppDeployingRepository deployingRepository,
                              DeployLogRepository deployLogRepository, MachineQuery machineQuery) {
        this.deployConfigRepository = deployConfigRepository;
        this.deployingRepository = deployingRepository;
        this.deployLogRepository = deployLogRepository;
        this.machineQuery = machineQuery;
    }

    @Override
    public Page<AppDeployConfig> findByAppId(String appId, Pageable pageable) {
        Specification<AppDeployConfig> specification = (root, query, cb) -> {
            Join<AppDeployConfig, AppConfig> innerJoin = root.join("appConfig", JoinType.INNER);
            Predicate predicate = cb.equal(innerJoin.get("appId"), appId);
            return cb.and(predicate);
        };

        return deployConfigRepository.findAll(specification, pageable);
    }

    @Override
    public AppDeployConfig findByAppIdAndMachineId(String appId, String machineId) {
        Specification<AppDeployConfig> specification = (root, query, cb) -> {
            Join<AppDeployConfig, AppConfig> innerJoin = root.join("appConfig", JoinType.INNER);
            Predicate predicate = cb.equal(innerJoin.get("appId"), appId);

            Join<AppDeployConfig, MachineHost> innerJoin1 = root.join("machineHost", JoinType.INNER);
            Predicate predicate1 = cb.equal(innerJoin1.get("machineId"), machineId);

            Predicate[] arr = new Predicate[2];
            arr[0] = predicate;
            arr[1] = predicate1;
            return cb.and(arr);
        };

        List<AppDeployConfig> list = deployConfigRepository.findAll(specification);
        if (!list.isEmpty()) {
            // 若 list 不为空，则应该有且仅有一个元素
            return list.get(0);
        } else {
            return null;
        }
    }

    @Override
    public List<AppInfo> getAppsByMachineId(String machineId) {
        return deployingRepository.findAll().stream()
                .filter(appDeploying -> {
                    MachineHost machineHost = appDeploying.getDeployingNodes().get(0).getDeployConfig().getMachineHost();
                    return machineHost.getMachineId().equals(machineId);
                })
                .map(appDeploying -> {
                    AppDeployConfig deployConfig = appDeploying.getDeployingNodes().get(0).getDeployConfig();
                    String packType = deployConfig.getAppConfig().getPackerConfig().getType();
                    String appId = deployConfig.getAppConfig().getAppId();
                    return new AppInfo(packType, appId);
                })
                .collect(Collectors.toList());
    }

    @Override
    public List<AppDeployingVO> get(String appId, AppBuilding appBuilding) {
        AppDeploying appDeploying = deployingRepository.findByAppConfig_AppId(appId);
        if (appDeploying == null) {
            return Collections.emptyList();
        }

        BuildLog buildLog = appDeploying.getBuildLog();
        return appDeploying.getDeployingNodes().stream().map(appDeployingNode -> {
            MachineHost machineHost = appDeployingNode.getDeployConfig().getMachineHost();
            String machineStatus = machineQuery.isAgentOnline(machineHost.getMachineId())
                    ? NodeStatus.Online.name() : NodeStatus.Offline.name();
            return new AppDeployingVO(appDeployingNode, buildLog, machineStatus);
        }).sorted(Comparator.comparing(AppDeployingVO::getDeployTime).reversed()).collect(Collectors.toList());
    }

    @Override
    public boolean isDeploying(String appId, String machineId) {
        AppDeploying appDeploying = deployingRepository.findByAppConfig_AppId(appId);
        if (appDeploying != null) {
            for (AppDeployingNode deployingNode : appDeploying.getDeployingNodes()) {
                String machineId1 = deployingNode.getDeployConfig().getMachineHost().getMachineId();
                if (machineId.equals(machineId1)) {
                    return deployingNode.isOnDeploying();
                }
            }
        }

        return false;
    }

    @Override
    public Page<AppDeployConfigVo> indexPage(String appId, PageRequest pageRequest) {
        return findByAppId(appId, pageRequest).map(AppDeployConfigVo::new);
    }

    @Override
    public PageList<AppRunningVO> getAppRunningByPage(String env, String appType, int pageNumber, int pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNumber-1, pageSize);
        Page<AppDeploying> page = deployingRepository.findAllByAppConfig_EnvAndAppConfig_AppType(env, appType, pageRequest);
        List<AppRunningVO> list = page.getContent().stream().map(appDeploying -> {
            List<AppDeployingNode> deployingNodes = appDeploying.getDeployingNodes();
            if (deployingNodes.isEmpty()) {
                return null;
            }

            AppConfig appConfig = deployingNodes.get(0).getDeployConfig().getAppConfig();
            String appId = appConfig.getAppId();
            String appName = appConfig.getAppName();
            String bindPorts = appConfig.getBindPorts();

            String packagePath = "N/A";
            BuildLog buildLog = appDeploying.getBuildLog();
            if (buildLog != null) {
                packagePath = buildLog.getPackagePath();
            }

            int totalDeployed = deployingNodes.size();
            return new AppRunningVO(appId, appName, bindPorts, packagePath, totalDeployed);
        }).filter(Objects::nonNull).collect(Collectors.toList());
        return PageList.pageList(pageNumber, pageSize, (int) page.getTotalElements(), list);
    }

    @Override
    public List<AppRunning> getAppRunning(String appId) {
        AppDeploying appDeploying = deployingRepository.findByAppConfig_AppId(appId);
        if (appDeploying == null) {
            return Collections.emptyList();
        }

        BuildLog buildLog = appDeploying.getBuildLog();
        return appDeploying.getDeployingNodes().stream()
                .map(appDeployingNode -> new AppRunning(appDeployingNode, buildLog))
                /*.filter(AppDeploying::isDeployed)
                .sorted(Comparator.comparing(AppDeploying::getDeployTime).reversed())*/
                .collect(Collectors.toList());
    }
}
