package com.pdaaphm.system.service.impl;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.TimeoutException;

import com.pdaaphm.biz.domain.BaseResponse;
import com.pdaaphm.biz.utils.MatlabUtils;
import com.pdaaphm.common.config.PadaphmConfig;
import com.pdaaphm.common.constant.Constants;
import com.pdaaphm.common.utils.DateUtils;
import com.pdaaphm.common.utils.DictUtils;
import com.pdaaphm.common.utils.StringUtils;
import com.pdaaphm.common.utils.file.FileUtils;
import com.pdaaphm.common.utils.uuid.Seq;
import com.pdaaphm.system.domain.AlgConfig;
import com.pdaaphm.system.domain.Data;
import com.pdaaphm.system.service.IAlgConfigService;
import com.pdaaphm.system.service.IDataService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.pdaaphm.system.mapper.TDataProcessMapper;
import com.pdaaphm.system.domain.TDataProcess;
import com.pdaaphm.system.service.ITDataProcessService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
 * 数据处理Service业务层处理
 *
 * @author ruoyi
 * @date 2024-08-20
 */
@Service
public class TDataProcessServiceImpl implements ITDataProcessService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private TDataProcessMapper tDataProcessMapper;

    @Autowired
    IAlgConfigService algConfigService;

    @Autowired
    IDataService dataService;

    /**
     * 查询数据处理
     *
     * @param id 数据处理主键
     * @return 数据处理
     */
    @Override
    public TDataProcess selectTDataProcessById(Long id) {
        return tDataProcessMapper.selectTDataProcessById(id);
    }

    /**
     * 查询数据处理列表
     *
     * @param tDataProcess 数据处理
     * @return 数据处理
     */
    @Override
    public List<TDataProcess> selectTDataProcessList(TDataProcess tDataProcess) {
        return tDataProcessMapper.selectTDataProcessList(tDataProcess);
    }

    @Override
    public List<TDataProcess> selectPreprocessDataList(TDataProcess tDataProcess) {
        return tDataProcessMapper.selectPreprocessDataList(tDataProcess);
    }

    /**
     * 新增数据处理
     *
     * @param tDataProcess 数据处理
     * @return 结果
     */
    @Override
    public int insertTDataProcess(TDataProcess tDataProcess) {
        tDataProcess.setCreateTime(DateUtils.getNowDate());
        return tDataProcessMapper.insertTDataProcess(tDataProcess);
    }

    /**
     * 修改数据处理
     *
     * @param tDataProcess 数据处理
     * @return 结果
     */
    @Override
    public int updateTDataProcess(TDataProcess tDataProcess) {
        tDataProcess.setUpdateTime(DateUtils.getNowDate());
        return tDataProcessMapper.updateTDataProcess(tDataProcess);
    }

    /**
     * 批量删除数据处理
     *
     * @param ids 需要删除的数据处理主键
     * @return 结果
     */
    @Override
    public int deleteTDataProcessByIds(Long[] ids) {
        return tDataProcessMapper.deleteTDataProcessByIds(ids);
    }

    /**
     * 删除数据处理信息
     *
     * @param id 数据处理主键
     * @return 结果
     */
    @Override
    public int deleteTDataProcessById(Long id) {
        return tDataProcessMapper.deleteTDataProcessById(id);
    }

    @Override
    public List<TDataProcess> getDataProcessByType(String type) {
        TDataProcess dataProcess = new TDataProcess();
        dataProcess.setProcessType(type);
        return this.selectTDataProcessList(dataProcess);
    }

    @Override
    public String runDataProcess(Long id) {
        String errorMsg = "";
        TDataProcess dataProcess = this.selectTDataProcessById(id);
        if (dataProcess != null) {
            AlgConfig algConfig = algConfigService.selectAlgConfigById(dataProcess.getProcessAlgId());
            Data data = dataService.selectDataById(dataProcess.getProcessedDataId());
            String bizAlgType = DictUtils.getDictLabel("biz_alg_type", dataProcess.getProcessType());

            if (algConfig == null || data == null || bizAlgType == null) {
                logger.warn("无法查找到该DataProcess的数据，id:{},algConfig:{},data:{},bizAlgType:{}", id, algConfig, data, bizAlgType);
                errorMsg = "数据有残缺";
            } else {
                dataProcess.setProcessStatus("2");
                dataProcess.setStartTime(new Date());
                dataProcess.setEndTime(null);
                this.updateTDataProcess(dataProcess);
                // 异步调用
                ITDataProcessService proxy = applicationContext.getBean(ITDataProcessService.class);
                proxy.asyncRunDataProcess(dataProcess, algConfig, data, bizAlgType);
            }
        } else {
            logger.warn("无法查找到该DataProcess的数据，id:{}", id);
            errorMsg = "未找到该数据!";
        }
        return errorMsg;
    }

    @Async
    @Transactional
    public void asyncRunDataProcess(TDataProcess dataProcess, AlgConfig algConfig, Data data, String bizAlgType) {
        // 状态设置为处理中

        process(dataProcess, algConfig, data, bizAlgType);
    }

    private Map<String, String> processExeAndFixedPath(AlgConfig algConfig, Data data, String bizAlgType) {
        Map<String, String> returnMap = new HashMap<>(8);
        // 获取配置
        String inputUrl = algConfig.getInputUrl() + Constants.ALG_INPUT;
        try {
            // 复制输入
            String realDataPath = PadaphmConfig.getProfile() + StringUtils.substringAfter(data.getDataPath(), Constants.RESOURCE_PREFIX);
            copyFile(realDataPath, inputUrl);
            // 运行exe程序
            MatlabUtils matlabUtils = new MatlabUtils();
            matlabUtils.runMatExe(algConfig.getAlgUrl());
            String resultPath = StringUtils.substringAfter(algConfig.getOutUrl(), Constants.RESOURCE_PREFIX);
            // 复制输出
            // 复制csv
            if (checkFileExist(algConfig.getOutUrl() + Constants.ALG_OUTPUT_CSV)) {
                String targetCsvPath = StringUtils.format("{}/{}/{}_{}_{}.csv", PadaphmConfig.getUploadPath(), DateUtils.datePath(), getOriginaFileName(data.getDataPath()), bizAlgType, Seq.getId(Seq.uploadSeqType));
                copyFile(algConfig.getOutUrl() + Constants.ALG_OUTPUT_CSV, targetCsvPath);
                returnMap.put("resultPath", targetCsvPath);
            }
            // 复制jpg
            if (checkFileExist(algConfig.getOutUrl() + Constants.ALG_OUTPUT_JPG)) {
                String targetJpgPath = StringUtils.format("{}/{}/{}_{}_{}.jpg", PadaphmConfig.getUploadPath(), DateUtils.datePath(), getOriginaFileName(data.getDataPath()), bizAlgType, Seq.getId(Seq.uploadSeqType));
                copyFile(algConfig.getOutUrl() + Constants.ALG_OUTPUT_JPG, targetJpgPath);
                returnMap.put("resultImagePath", targetJpgPath);
            }
            // 读取txt
            if (checkFileExist(algConfig.getOutUrl() + Constants.ALG_OUTPUT_TXT)) {
                String msg = readSmallFileContent(algConfig.getOutUrl() + Constants.ALG_OUTPUT_TXT);
                returnMap.put("msg", msg);
            }
            returnMap.put("status", "200");
        } catch (Exception e) {
            logger.error("processExeAndFixedPath 运行报错", e);
            returnMap.put("status", "500");
        }
        return returnMap;
    }

    private static String readSmallFileContent(String resultPath) {
        File file = new File(resultPath);  // 文件路径

        StringBuilder content = new StringBuilder();
        try (Scanner scanner = new Scanner(file)) {
            while (scanner.hasNextLine()) {
                content.append(scanner.nextLine()).append(System.lineSeparator());  // 逐行读取
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return content.toString();
    }

    private void copyFile(String realDataPath, String inputUrl) {
        Path sourcePath = Paths.get(realDataPath);  // 源文件路径
        Path targetPath = Paths.get(inputUrl);  // 目标文件路径
        try {
            // 确保目标目录存在
            Path targetDir = targetPath.getParent();
            if (!Files.exists(targetDir)) {
                Files.createDirectories(targetDir);  // 如果目录不存在，创建目录
            }
            // 使用Files.copy()复制文件
            Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
            logger.info("文件已成功复制!source:{},target:{}",sourcePath, targetPath);
            System.out.println("文件已成功复制!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void process(TDataProcess dataProcess, AlgConfig algConfig, Data data, String bizAlgType) {
        Map<String, String> resultMap = null;
        if("否".equals(algConfig.getFixed())){
            resultMap = doRunDataProcess(algConfig, data, bizAlgType);
        } else {
            resultMap = processExeAndFixedPath(algConfig, data, bizAlgType);
        }
        if ("200".equals(resultMap.get("status"))) {
            // 成功场景
            // create new data
            Data newData = new Data();
            String resultPath = resultMap.get("resultPath");
            String resultImagePath = resultMap.get("resultImagePath");
            if (checkFileExist(resultPath)) {
                newData.setDataPath(resultPath.replaceFirst(PadaphmConfig.getProfile(), "/profile"));
            }
            if (checkFileExist(resultImagePath)) {
                newData.setImagePath(resultImagePath.replaceFirst(PadaphmConfig.getProfile(),"/profile"));
            }
            newData.setDataType((Integer.parseInt(dataProcess.getProcessType()) + 1) + "");
            newData.setDataName(FileUtils.getNameNotSuffix(resultPath));
            newData.setRemark(resultMap.get("msg"));
            dataService.insertData(newData);
            // update dataProcess
            dataProcess.setProcessStatus("3"); // 已完成
            dataProcess.setResultDataId(newData.getId()); // 已完成
            dataProcess.setEndTime(new Date());
            this.updateTDataProcess(dataProcess);
        } else {
            logger.error("dataProcess error,id:{},error:{}", dataProcess.getId(),resultMap.get("msg"));
            // 失败场景
            dataProcess.setProcessStatus("4"); // 失败
            dataProcess.setEndTime(new Date());
            dataProcess.setLog(resultMap.get("errorMsg"));
            this.updateTDataProcess(dataProcess);
        }
    }

    private Map<String, String> doRunDataProcess(AlgConfig algConfig, Data data, String bizAlgType) {

        String algUrl = algConfig.getAlgUrl();
        String dataPath = data.getDataPath();
        String originalFileName = getOriginaFileName(dataPath);
        // 文件名 原始文件名_类型_时间戳
        String resultPath = StringUtils.format("{}/{}/{}_{}_{}.csv", PadaphmConfig.getUploadPath(), DateUtils.datePath(), originalFileName, bizAlgType, Seq.getId(Seq.uploadSeqType));
        String resultImagePath = StringUtils.format("{}/{}/{}_{}_{}.jpeg", PadaphmConfig.getUploadPath(), DateUtils.datePath(), originalFileName, bizAlgType, Seq.getId(Seq.uploadSeqType));

        return sendHttp(algUrl, dataPath.replace("/profile",PadaphmConfig.getProfile()), resultPath, resultImagePath);


    }

    private String getOriginaFileName(String dataPath) {
        logger.info("doRunDataProcess.dataPath: {}", dataPath);
        String fileName = FileUtils.getNameNotSuffix(dataPath);
        logger.info("doRunDataProcess.fileName: {}", fileName);
        String originalFileName = fileName.split("_")[0];
        return originalFileName;
    }

    private boolean checkFileExist(String resultImagePath) {
        if(StringUtils.isEmpty(resultImagePath)){
            return false;
        }
        String realPath = resultImagePath;

        if (realPath.startsWith("/profile")) {
            realPath = realPath.replace("/profile", PadaphmConfig.getProfile());
        }

        Path path = Paths.get(realPath);
        return Files.exists(path);
    }

    private Map<String, String> sendHttp(String algUrl, String dataPath, String resultPath, String resultImagePath) {
        Map<String, String> resultMap = new HashMap<>(8);
        resultMap.put("resultPath", resultPath);
        resultMap.put("resultImagePath", resultImagePath);
        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setConnectionRequestTimeout(3000);
        httpRequestFactory.setConnectTimeout(3000);
        httpRequestFactory.setReadTimeout(3000);
        WebClient webClient = WebClient.create(algUrl);

        Map<String, String> requestDto = new HashMap<>(4);
        requestDto.put("file_url", dataPath);
        requestDto.put("result_file_path", resultPath);
        requestDto.put("result_img_path", resultImagePath);

        // send post
        Mono<BaseResponse> result = webClient.post() // 使用POST方法
//                .uri("/request")  // 指定URI
                .contentType(MediaType.APPLICATION_JSON)  // 设置Content-Type为application/json
                .bodyValue(requestDto)  // 设置请求body
                .retrieve()  // 执行请求
                .bodyToMono(BaseResponse.class)
                .onErrorResume(e -> {
                    logger.error("http request error!!!", e);
                    if (e instanceof TimeoutException) {
                        // 对超时错误进行处理
                        return Mono.just(BaseResponse.error("Timeout error"));
                    } else {
                        // 对其他错误进行处理
                        return Mono.just(BaseResponse.error(e.getMessage()));
                    }
                });  // 将响应体转换为String
        // get response
        BaseResponse res = result.block();
        if (res == null) {
            resultMap.put("status","500");
            resultMap.put("msg", "response is null");
            logger.error("response is null");
            return resultMap;
        }
        resultMap.put("status",res.getCode().toString());
        resultMap.put("msg",res.getMsg());
        return resultMap;
    }
}
