package com.cpp.web.service.datafactory;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.extra.ssh.Sftp;
import com.cpp.web.domain.datafactory.SftpChannel;
import com.cpp.web.domain.datafactory.ParsingLog;
import com.cpp.web.domain.datafactory.ParsingType;
import com.cpp.web.domain.datafactory.dto.ParsingResultDto;
import com.cpp.web.domain.enums.AlarmEnum;
import com.cpp.web.domain.enums.DataSourcesEnum;
import com.cpp.web.domain.station.ElectricField;
import com.cpp.web.service.station.ElectricFieldService;
import com.cpp.web.utils.LogUtil;
import com.cpp.web.utils.ParsingFileUtil;
import com.cpp.web.utils.ZipUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Service;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;

/**
 * ftp文件解析主类
 *
 * @author tl
 * @date 2022-05-11 09:51:21
 */
@AllArgsConstructor
@Slf4j
@Service
public class SftpFileParsing {

    private final ParsingTypeService parsingTypeService;

    private final Map<String, ParsingInterface> parsingInterfaceMap;

    private final SftpChannelService sftpChannelService;

    private final ElectricFieldService electricFieldService;

    private final ParsingLogService parsingLogService;

    private final File PARSING_FILE_TEMP_DIR = ParsingFileUtil.checkGetPath(ParsingFileUtil.getSftpDownload() + File.separator + "temp");
    private final File PARSING_FILE_SUCCESS_DIR = ParsingFileUtil.checkGetPath(ParsingFileUtil.getSftpDownload() + File.separator + "success");
    private final File PARSING_FILE_FAIL_DIR = ParsingFileUtil.checkGetPath(ParsingFileUtil.getSftpDownload() + File.separator + "fail");

    private final String CHARSET_NAME = "UTF-8";
//    private AlarmLog alarmLog = AlarmLog.getInstance();
//    private final ThreadPoolTaskExecutor executor;

    /**
     * 解析文件得定时任务
     * 逻辑：先配置识别文件名称类型：识别文件名称关键字对应的文件类型
     * 有文件类型的配置才可以进入下一步
     * <p>
     * 查找各场站配置的ftp目录，去下载文件并识别文件类型执行对应的解析策略
     * <p>
     * 必要条件：1.配置文件识别类型。2.配置下载路径。3.配置解析公式
     *
     * @param
     * @return
     */
    public void parsingFile() {

        log.info("-----------------开始执行FTP文件解析任务----------------------");

        List<ParsingType> parsingTypes = parsingTypeService.list();

        if (parsingTypes.size() > 0) {

            List<SftpChannel> sftpChannels = sftpChannelService.list();

            List<ElectricField> electricFields = electricFieldService.list();
            for (SftpChannel sftpChannel : sftpChannels) {

                List<ElectricField> channelElectricFields = electricFields.stream().filter(e -> e.getSftpChanelId() != null && e.getSftpChanelId().equals(sftpChannel.getId())).collect(Collectors.toList());

                if (channelElectricFields.size() > 0) {
                    SftpConfig sftpConfig = createSftp(sftpChannel);
                    if (sftpConfig.getSftp() != null) {
                        parsingFile(parsingTypes, channelElectricFields, sftpConfig.getSftp());
                        sftpConfig.close();
                    }
                }
            }
        } else {
            log.info("未配置文件识别类型标识，无法进行下载解析");
        }

        log.info("-----------------执行FTP文件解析任务完成----------------------");
    }

    /**
     * sftp下载文件并解析文件的具体步骤
     *
     * @param parsingTypes          解析类型
     * @param channelElectricFields 某通道绑定的场站
     * @param sftp                  sftp
     */
    private void parsingFile(
            List<ParsingType> parsingTypes,
            List<ElectricField> channelElectricFields,
            Sftp sftp
    ) {
        List<ParsingLog> parsingLogs = new ArrayList<>();
        for (ParsingType parsingType : parsingTypes) {
//            executor.execute(new Runnable() {
//                @Override
//                public void run() {
            ParsingInterface parsingInterface = parsingInterfaceMap.get(parsingType.getFileType().name() + "Parsing");
            parsingInterface.activationParsingConf();//初始化解析配置
            for (ElectricField electricField : channelElectricFields) {
                String ftpUrl = electricField.getSftpUrl();
                List<String> fileNames = sftp.ls(ftpUrl).stream().filter(f -> f.contains(parsingType.getFileName())).collect(Collectors.toList());
                if (fileNames.size() > 0) {
                    for (String fileName : fileNames) {
                        try {
                            String sftpFileNameDir = ftpUrl + "/" + fileName;
                            String destFileNameDir = PARSING_FILE_TEMP_DIR.getPath() + File.separator + fileName;
                            //下载文件到临时目录
                            sftp.get(sftpFileNameDir, destFileNameDir);
                            File file = FileUtils.getFile(PARSING_FILE_TEMP_DIR, fileName);
                            ParsingLog parsingLog = new ParsingLog();
                            Date now = new Date();
                            parsingLog.setParsingTime(now);
                            parsingLog.setFileName(fileName);
                            ParsingResultDto parsingResultDto = parsingInterface.parsing(file, electricField.getStationCode());
                            parsingLog.setParsingDescribe(parsingResultDto.getMessage());
                            parsingLog.setFileType(parsingType.getFileType());
                            parsingLog.setDataSources(DataSourcesEnum.E1);
                            parsingLog.setStationCode(electricField.getStationCode());
                            if (parsingResultDto.getStatus().equals("fail")) {
                                try {
                                    File failFileDir = new File(PARSING_FILE_FAIL_DIR.getPath() + File.separator + DateFormatUtils.format(now, "yyyy-MM-dd") + File.separator + electricField.getStationCode() + File.separator + parsingType.getFileType().name());
                                    File failFile = new File(failFileDir.getPath() + File.separator + fileName);
                                    if (failFile.exists()) {
                                        failFile.delete();
                                        log.error("已有过解析失败文件，错误文件将覆盖！场站编号：{}，文件名称：{}", electricField.getStationCode(), fileName);
                                    }
                                    ParsingFileUtil.move(file.getAbsolutePath(), failFileDir.getAbsolutePath(), true);
                                } catch (IOException e) {
                                    log.error("解析文件失败后文件移动失败！场站编号：{}，文件名称：{}", electricField.getStationCode(), fileName, e);
                                    e.printStackTrace();
                                }
                                file.delete();//失败删除本地文件，等待下次下载
                                parsingLog.setParsingFileStatus("失败");
                                LogUtil.info(DataSourcesEnum.E3, AlarmEnum.E4, LogUtil.format("无法解析场站端文件：{}", fileName), electricField.getStationCode());
                            } else {
                                sftp.delFile(ftpUrl + "/" + fileName);//成功删除ftp上的文件
                                try {
                                    File successFileDir = new File(PARSING_FILE_SUCCESS_DIR.getPath() + File.separator + DateFormatUtils.format(now, "yyyy-MM-dd") + File.separator + electricField.getStationCode() + File.separator + parsingType.getFileType().name());
                                    File successFile = new File(successFileDir.getPath() + File.separator + fileName);
                                    if (successFile.exists()) {
                                        successFile.delete();
                                        log.error("已有过解析成功文件，成功文件将覆盖！场站编号：{}，文件名称：{}", electricField.getStationCode(), fileName);
                                    }
                                    ParsingFileUtil.move(file.getAbsolutePath(), successFileDir.getAbsolutePath(), true);
                                } catch (IOException e) {
                                    log.error("解析文件成功后文件移动失败！场站编号：{}，文件名称：{}", electricField.getStationCode(), fileName, e);
                                    e.printStackTrace();
                                }
                                file.delete();//失败删除本地文件，等待下次下载
                                parsingLog.setParsingFileStatus("成功");
                            }
                            parsingLogs.add(parsingLog);
                        } catch (Exception e) {
                            LogUtil.info(DataSourcesEnum.E3, AlarmEnum.E4, LogUtil.format("无法解析场站端文件：{}", fileName), electricField.getStationCode());
                            log.error("下载并解析文件{}时异常", fileName, e);
                        }

                    }
                }
            }
//                }
//            });

        }
        parsingLogService.saveBatch(parsingLogs);
    }


    /**
     * 创建sftp 并解析
     *
     * @param sftpChannel sftp通道信息
     * @return sftp控制
     */
    public SftpConfig createSftp(
            SftpChannel sftpChannel
    ) {
        // SFTP方式上报
        SftpConfig sftpConfig = new SftpConfig();

        try {
            sftpConfig.setSshSession(sftpConfig.getJsch().getSession(sftpChannel.getUsername(), sftpChannel.getIp(), sftpChannel.getPort()));
            sftpConfig.getSshSession().setPassword(sftpChannel.getPassword());
            sftpConfig.getSshSession().setTimeout(30000);
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            sftpConfig.getSshSession().setConfig(sshConfig);
            log.info("sftp开始连接...");
            sftpConfig.getSshSession().connect();
            sftpConfig.setChannel((ChannelSftp) sftpConfig.getSshSession().openChannel("sftp"));
            sftpConfig.getChannel().connect();
            Class cl = ChannelSftp.class;
            Field f = cl.getDeclaredField("server_version");
            f.setAccessible(true);
            f.set(sftpConfig.getChannel(), 2);
            sftpConfig.getChannel().setFilenameEncoding(CHARSET_NAME);
            sftpConfig.setSftp(new Sftp(sftpConfig.getChannel(), CharsetUtil.CHARSET_UTF_8));
        } catch (Exception e) {
            log.error("sftp连接异常：" + e);
            if (sftpConfig.getSftp() != null) {
                sftpConfig.close();
            }
        }
        return sftpConfig;
    }


    /**
     * 压缩前一日解析文件并上传至sftp服务器，本地不保留
     */
    public void uploadZipFile() {
        log.info("-----------------开始压缩前一日解析文件并上传至sftp服务器解析任务----------------------");
        List<SftpChannel> sftpChannels = sftpChannelService.list();

        List<ElectricField> electricFields = electricFieldService.list();

        String dirYesToday = DateFormatUtils.format(new Date(System.currentTimeMillis() - 86400000L), "yyyy-MM-dd");
        for (SftpChannel sftpChannel : sftpChannels) {
            List<ElectricField> channelElectricFields = electricFields.stream().filter(e -> e.getSftpChanelId() != null && e.getSftpChanelId().equals(sftpChannel.getId())).collect(Collectors.toList());
            if (channelElectricFields.size() > 0) {
                SftpConfig sftpConfig = createSftp(sftpChannel);
                if (sftpConfig.getSftp() != null) {
                    String failSrc = PARSING_FILE_FAIL_DIR.getPath() + File.separator + dirYesToday;
                    String successSrc = PARSING_FILE_SUCCESS_DIR.getPath() + File.separator + dirYesToday;
                    for (ElectricField channelElectricField : channelElectricFields) {
                        File failFileDir = new File(failSrc + File.separator + channelElectricField.getStationCode());
                        if (failFileDir.exists()) {
                            String zipFileName = dirYesToday + "_" + channelElectricField.getStationCode() + "_FAIL.zip";
                            String zipPath = PARSING_FILE_FAIL_DIR.getPath() + File.separator + zipFileName;
                            if (ZipUtil.zip(failFileDir.getAbsolutePath(), zipPath) && new File(zipPath).exists()) {
                                upload(sftpConfig.getSftp(), channelElectricField.getSftpBackupUrl(), zipPath, zipFileName);
                            }
                        }
                        File successFileDir = new File(successSrc + File.separator + channelElectricField.getStationCode());
                        if (successFileDir.exists()) {
                            String zipFileName = dirYesToday + "_" + channelElectricField.getStationCode() + "_SUCCESS.zip";
                            String zipPath = PARSING_FILE_SUCCESS_DIR.getPath() + File.separator + zipFileName;
                            if (ZipUtil.zip(successFileDir.getAbsolutePath(), zipPath) && new File(zipPath).exists()) {
                                upload(sftpConfig.getSftp(), channelElectricField.getSftpBackupUrl(), zipPath, zipFileName);
                            }
                        }
                    }
                    sftpConfig.close();
                }
            }
        }
        log.info("-----------------结束压缩前一日解析文件并上传至sftp服务器解析任务----------------------");
    }

    /**
     * sftp 上传文件
     *
     * @param sftp
     * @param destPathName 目标路径
     * @param srcName      源路径
     */
    private void upload(
            Sftp sftp,
            String destPathName,
            String srcName,
            String fileName
    ) {
        try (FileInputStream fis = new FileInputStream(srcName)) {
            File file = new File(srcName);
            sftp.getClient().put(fis, destPathName + "/" + fileName);
            fis.close();
            Files.deleteIfExists(file.toPath());
        } catch (Exception e) {
            log.error("上传备份文件异常", e);
        }
    }


    /**
     * 一个临时的sftp控制类
     */
    @Data
    public class SftpConfig {
        private JSch jsch = new JSch();
        private ChannelSftp channel = null;
        private Session sshSession = null;
        private Sftp sftp = null;

        public void close() {
            if (this.sftp != null) {
                log.info("sftp客户端关闭");
                JschUtil.closeAll();
                sftp.close();
                channel.quit();
                channel.disconnect();
                sshSession.disconnect();
            }
        }
    }
}
