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

import cn.reghao.bnt.common.agent.app.dto.AppInfo;
import cn.reghao.bnt.common.msg.constant.NodeStatus;
import cn.reghao.bnt.web.devops.app.db.query.AppDeployQuery;
import cn.reghao.bnt.web.devops.app.db.repository.AppDeployingRepository;
import cn.reghao.bnt.web.devops.app.db.repository.config.AppDeployConfigRepository;
import cn.reghao.bnt.web.devops.app.db.repository.log.DeployLogRepository;
import cn.reghao.bnt.web.devops.app.model.po.AppDeploying;
import cn.reghao.bnt.web.devops.app.model.po.AppDeployingNode;
import cn.reghao.bnt.web.devops.app.model.po.config.AppConfig;
import cn.reghao.bnt.web.devops.app.model.po.config.AppDeployConfig;
import cn.reghao.bnt.web.devops.app.model.po.log.BuildLog;
import cn.reghao.bnt.web.devops.app.model.po.log.DeployLog;
import cn.reghao.bnt.web.devops.app.model.vo.AppBuildingVO;
import cn.reghao.bnt.web.devops.app.model.vo.AppDeployingNodeVO;
import cn.reghao.bnt.web.devops.app.model.vo.AppRunning;
import cn.reghao.bnt.web.devops.app.model.vo.AppRunningNode;
import cn.reghao.bnt.web.devops.machine.model.po.MachineHost;
import cn.reghao.bnt.web.devops.machine.service.MachineQuery;
import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
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 jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.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 int pageSize = 10;
    private final AppDeployConfigRepository deployConfigRepository;
    private final AppDeployingRepository deployingRepository;
    private final 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 List<AppDeployConfig> getByAppId(String appId) {
        PageRequest pageRequest = PageRequest.of(0, pageSize);
        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, pageRequest).getContent();
    }

    @Override
    public List<AppDeployingNodeVO> get(String appId) {
        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 AppDeployingNodeVO(appDeployingNode, buildLog, machineStatus);
        }).sorted(Comparator.comparing(AppDeployingNodeVO::getDeployTime).reversed()).collect(Collectors.toList());
    }

    @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 int countByApp(AppConfig appConfig) {
        return deployConfigRepository.countByAppConfig(appConfig);
    }

    @Override
    public AppDeployConfig getByAppIdAndMachineId(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 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<AppRunning> getAppRunningByPage(String env, String appType, Pageable pageable) {
        Page<AppDeploying> page = deployingRepository.findAllByAppConfig_EnvAndAppConfig_AppType(env, appType, pageable);
        List<AppRunning> 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();
            int totalRunning = deployingNodes.size();
            int totalStopped = totalDeployed-totalRunning;
            return new AppRunning(appId, appName, bindPorts, packagePath, totalDeployed, totalRunning, totalStopped);
        }).filter(Objects::nonNull).collect(Collectors.toList());

        return new PageImpl<>(list, pageable, page.getTotalElements());
    }

    @Override
    public List<AppRunningNode> 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 AppRunningNode(appDeployingNode, buildLog))
                /*.filter(AppDeploying::isDeployed)
                .sorted(Comparator.comparing(AppDeploying::getDeployTime).reversed())*/
                .collect(Collectors.toList());
    }

    @Deprecated
    public List<DeployLog> getByGroup() {
        Specification<DeployLog> specification = (root, query, cb) -> {
            query.groupBy(root.get("machineHost"));
            return cb.conjunction();
        };

        return deployLogRepository.findAll(specification);
    }

    @Override
    public int getAppRunningCountByBuildLogId(String buildLogId) {
        int totalRunning = 0;
        AppDeploying appDeploying = deployingRepository.findByBuildLog_buildLogId(buildLogId);
        if (appDeploying != null) {
            totalRunning = (int) appDeploying.getDeployingNodes().stream().filter(AppDeployingNode::getRunning).count();
        }
        return totalRunning;
    }
}
