package cd.casic.module.execute.docker.service.impl;

import cd.casic.ci.commons.properties.TargetFileUploadProperties;
import cd.casic.framework.commons.exception.ServerException;
import cd.casic.framework.commons.exception.util.ServiceExceptionUtil;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
import cd.casic.module.execute.docker.DockerClientFactory;
import cd.casic.module.execute.docker.dao.DockerImageDao;
import cd.casic.module.execute.docker.dataobject.dto.DockerImageDo;
import cd.casic.module.execute.docker.dataobject.model.DockerImage;
import cd.casic.module.execute.docker.dataobject.vo.DockerImagePageReqVO;
import cd.casic.module.execute.docker.service.IImageService;
import cd.casic.module.machine.contants.MachineErrorCodeConstants;
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
import cd.casic.module.machine.dal.mysql.MachineInfoMapper;
import cd.casic.module.machine.dal.mysql.SecretKeyMapper;
import cd.casic.module.machine.enums.AuthenticationType;
import cd.casic.module.machine.utils.CryptogramUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.extra.ftp.FtpConfig;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.extra.ssh.Sftp;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.InspectImageResponse;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.PruneType;
import com.jcraft.jsch.Session;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @description: 镜像的服务类
 * @author: mianbin
 * @date: 2025/5/26 15:06
 * @version: 1.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ImageService implements IImageService {

    private final DockerClientFactory dockerClientFactory;

    @Resource
    private DockerImageDao dockerImageDao;

    @Resource
    private MachineInfoMapper machineInfoMapper;

    @Resource
    private TargetFileUploadProperties fileUploadProperties;

    @Resource
    private SecretKeyMapper secretKeyMapper;

    @Override
    public List<Image> list(@Nonnull String clientId) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        return dockerClient.listImagesCmd().exec();
    }

    @Override
    public InspectImageResponse inspect(@Nonnull String clientId, @Nonnull String imageId) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        return dockerClient.inspectImageCmd(imageId).exec();
    }

    @Override
    public void tag(@Nonnull String clientId, String imageId, String newTag) {
        InspectImageResponse inspect = inspect(clientId, imageId);
        if (inspect == null) {
            throw new RuntimeException("操作失败，镜像不存在");
        }
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        dockerClient.tagImageCmd(imageId, inspect.getRepoTags().get(0), newTag).exec();
    }

    @Override
    public void remove(@Nonnull String clientId, String imageId, Boolean force) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        dockerClient.removeImageCmd(imageId).withForce(force).exec();
    }

    @Override
    public void export(@Nonnull String clientId, String imageId) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        /*这个api我要查下，不生效也无所，感觉场景很少*/
    }

    /*经过测试，只有local 模式下面生效*/
    @Override
    public void importByFile(@Nonnull String clientId, String file) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        /*test 测试下，这个不知道生效不*/
        dockerClient.loadImageCmd(FileUtil.getInputStream(file)).exec();
    }

    /*经过测试，只有local 模式下面生效*/
    @Override
    public void importByTar(@Nonnull String clientId, String file) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        /*test 测试下，这个不知道生效不*/
        dockerClient.loadImageCmd(FileUtil.getInputStream(file)).exec();
    }

    @Override
    public boolean exist(@Nonnull String clientId, String imageId) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        try {
            final InspectImageResponse inspectImage = dockerClient.inspectImageCmd(imageId).exec();
            return inspectImage != null;
        } catch (NotFoundException e) {
            return false;
        }
    }

    @Override
    public void pruneImage(String clientId) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        dockerClient.pruneCmd(PruneType.IMAGES).exec();
    }

    @Override
    public Boolean save(@Nonnull String clientId, String imageId, String outputPath) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        /*imageid 如 nginx:latest */
        try (OutputStream fos = new FileOutputStream(outputPath);
             SaveImageCmd saveImageCmd = dockerClient.saveImageCmd(imageId)) {
            // 执行命令并将输出写入文件流
            InputStream exec = saveImageCmd.exec();
            IoUtil.copy(exec, fos);
            log.info("镜像已成功保存到: " + outputPath);
            return Boolean.TRUE;
        } catch (IOException e) {
            log.error("保存镜像时发生 IO 错误: " + e.getMessage());
            return Boolean.FALSE;
        }
    }

    @Override
    public Boolean pushImage(@NotNull String clientId, String imageId) {
        DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
        try {
            dockerClient.pushImageCmd(imageId).exec(null);
        } catch (Exception e) {
            log.error("推送镜像失败: " + e.getMessage());
            return false;
        }
        return true;
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int localImageUpload(DockerImage dockerImage) {
        DockerImageDo imageDo = new DockerImageDo();
        BeanUtil.copyProperties(dockerImage,imageDo);
        return dockerImageDao.insert(imageDo);
    }

    @Override
    public PageResult<DockerImageDo> getLocalImagePage(DockerImagePageReqVO pageVO) {
        LambdaQueryWrapperX<DockerImageDo> queryWrapperX = new LambdaQueryWrapperX<>();
        PageResult<DockerImageDo> page = dockerImageDao.selectPage(pageVO, queryWrapperX.likeIfPresent(DockerImageDo::getName, pageVO.getName()).orderByDesc(DockerImageDo::getCreateTime));
        return page;
    }

    /**
     * 先下载后上传到目标主机 先确定目标主机是否可连接在继续
     * @param imageId
     * @param machineId
     * @return
     */
    @Override
    public Boolean localImagePush(String imageId, String machineId) {
        if (Objects.isNull(imageId) || Objects.isNull(machineId)) {
            throw new ServerException(MachineErrorCodeConstants.MACHINE_INFO_TAG_NULL);
        }
        // 1 建立连接 区分密码还是秘钥
        MachineInfoDO machineInfoDO = machineInfoMapper.selectById(machineId);
        FtpConfig targetFtp = FtpConfig.create().setHost(machineInfoDO.getHostIp()).setPort(machineInfoDO.getSshPort()).setUser(machineInfoDO.getUsername()).setPassword(CryptogramUtil.doDecrypt(machineInfoDO.getPassword()));
        if (AuthenticationType.of(machineInfoDO.getAuthenticationType()).equals(AuthenticationType.SECRET_KEY)) {
            SecretKeyDO secretKeyDO = secretKeyMapper.selectById(machineInfoDO.getSecretKeyId());
            targetFtp.setSystemKey(CryptogramUtil.doDecrypt(secretKeyDO.getPrivateKey()));
            targetFtp.setPassword(CryptogramUtil.doDecrypt(secretKeyDO.getPassword()));
        }
        // 2 拿到目标主机连接
        Sftp uploadSftp = getSftp(targetFtp);

        //远程下载
        DockerImageDo imageDo = dockerImageDao.selectById(imageId);
        Sftp sftp = getSftp(FtpConfig.create().setHost(fileUploadProperties.getRemoteHost()).setPort(fileUploadProperties.getRemotePort()).setUser(fileUploadProperties.getUsername()).setPassword(fileUploadProperties.getPassword()));
        String srcPath = imageDo.getPath();
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        sftp.download(srcPath, byteOut); //远程下载到中间缓存区
        sftp.close();
        //远程上传
        String suffix = FileUtil.getSuffix(srcPath);
        String fileName = imageDo.getName() + "." + suffix;
        ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(byteOut.toByteArray()); //转换
        String romPath = "/home/image/" + UUID.randomUUID()+"/";
        uploadSftp.mkDirs(romPath);//递归创建
        uploadSftp.upload(romPath, fileName, arrayInputStream);
        uploadSftp.close();
        return true;
    }

    /**
     *
     * 获取Sftp连接
     * @param config 连接配置
     * @return
     */
    private Sftp getSftp(FtpConfig config) {
        Session session;
        Integer timeOut = 5000;
        try {
            if (Objects.nonNull(config.getSystemKey())) {
                session = JschUtil.openSession(config.getHost(), config.getPort(), config.getUser(), config.getSystemKey().getBytes(StandardCharsets.UTF_8), config.getPassword().getBytes(), timeOut);
            } else {
                session = JschUtil.openSession(config.getHost(), config.getPort(), config.getUser(), config.getPassword(), timeOut);
            }
        } catch (Exception e) {
            log.error("与主机IP:{} 建立SSH连接失败", config.getHost());
            throw ServiceExceptionUtil.invalidParamException("部署失败，请检测主机: {} 是否可用",config.getHost());
        }
        return new Sftp(session);
    }
}
