package com.bcxin.autodownloadupload.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson.JSONObject;
import com.bcxin.autodownloadupload.common.utils.BcxinFtpClient;
import com.bcxin.autodownloadupload.common.utils.DatabaseUtil;
import com.bcxin.autodownloadupload.common.utils.FileUtils;
import com.bcxin.autodownloadupload.common.utils.FtpUtil;
import com.bcxin.autodownloadupload.configs.PushConfig;
import com.bcxin.autodownloadupload.dtos.FerryReceiveTaskPushResult;
import com.bcxin.autodownloadupload.entity.PushRecord;
import com.bcxin.autodownloadupload.service.PushDataService;
import com.bcxin.autodownloadupload.service.PushRecordService;
import com.bcxin.autodownloadupload.service.PushRecordSqlLogService;
import com.bcxin.autodownloadupload.service.PushRecordUploadLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * description: 推送数据服务
 * author: linchunpeng
 * date:  2023-04-19 9:26
 */
@Slf4j
@Service
public class PushDataServiceImpl implements PushDataService {

    @Autowired
    private PushConfig pushConfig;

    @Autowired
    private PushRecordService pushRecordService;

    @Autowired
    private PushRecordSqlLogService pushRecordSqlLogService;

    @Autowired
    private PushRecordUploadLogService pushRecordUploadLogService;

    @Value("${spring.shardingsphere.datasource.baiduutil.url}")
    private String url;

    @Value("${spring.shardingsphere.datasource.baiduutil.username}")
    private String username;

    @Value("${spring.shardingsphere.datasource.baiduutil.password}")
    private String password;

    /**
     * description: 推送文件，执行自动上传、自动执行sql
     * author: linchunpeng
     * date:  2023-04-23 17:48
     */
    @Override
    @Async(value = "taskExecutor")
    public void pushDataAsync(String requestId, boolean isNeedUnzip) {
        pushData(requestId, isNeedUnzip);
    }

    /**
     * description: 推送文件，执行自动上传、自动执行sql
     * author: linchunpeng
     * date:  2023-04-23 17:48
     */
    @Override
    public PushRecord pushData(String requestId, boolean isNeedUnzip) {
        log.info("====================================开始自动推送文件，requestId：{}=====================================", requestId);
        //解压摆渡zip文件
        PushRecord pushRecord = this.unzipBaiduFile(requestId, isNeedUnzip);
        log.info("生成推送记录{}", JSONObject.toJSONString(pushRecord));
        if ("success".equals(pushRecord.getUnzipResult())) {
            //执行sql脚本文件
            this.executeSqlFiles(pushRecord);
            //上传文件
            this.uploadFiles(pushRecord);
            pushRecordService.updateById(pushRecord);
            //确认文件是否上传成功
//            this.checkFilesSuccess(pushRecord);
//            FileUtil.del(pushRecord.getUnzipPath());
//            log.info("推送完成，删除文件夹：{}", pushRecord.getUnzipPath());
        }
        log.info("====================================推送文件结束=====================================");
        return pushRecord;
    }

    /**
     * description: 解压摆渡zip文件
     * author: linchunpeng
     * date:  2023-04-23 13:30
     */
    private PushRecord unzipBaiduFile(String requestId, boolean isNeedUnzip) {
        if (isNeedUnzip) {
            log.info("解压摆渡zip文件");
            //读取文件
            String filePath = String.format(pushConfig.getFilePath(), requestId);
            File zipFile = new File(filePath);
            if (!zipFile.exists()) {
                //zip文件不存在
                return pushRecordService.createRecord(requestId, filePath, null, "zip文件不存在");
            }
            //解压文件
            File unzip = ZipUtil.unzip(zipFile);
            if (!unzip.exists()) {
                //zip解压失败
                return pushRecordService.createRecord(requestId, filePath, null, "zip解压失败");
            }
            log.info("解压结束");
            //新增一条日志
            return pushRecordService.createRecord(requestId, filePath, unzip.getAbsolutePath(), "success");
        } else {
            log.info("不用解压摆渡文件");
            //读取文件
            String filePath = String.format(pushConfig.getFilePath(), requestId);
            filePath = filePath.substring(0, filePath.length() - 4);
            File folder = new File(filePath);
            if (!folder.exists()) {
                //文件夹不存在
                return pushRecordService.createRecord(requestId, filePath, null, "文件夹不存在");
            }
            //新增一条日志
            return pushRecordService.createRecord(requestId, filePath, filePath, "success");
        }
    }

    /**
     * description: 上传文件
     * author: linchunpeng
     * date:  2023-04-23 14:20
     */
    private void uploadFiles(PushRecord pushRecord) {
        log.info("开始上传文件");
        pushRecord.setUploadStartTime(new Date());
        //根据内外网类型，上传文件
        if ("in".equals(pushConfig.getInternet().getType())) {
            this.uploadToIn(pushRecord);
        } else {
            this.uploadToOut(pushRecord);
        }
        pushRecord.setUploadEndTime(new Date());
        log.info("上传文件完成");
    }

    /**
     * description: 上传文件到内网
     * author: linchunpeng
     * date:  2023-04-23 14:21
     */
    private void uploadToIn(PushRecord pushRecord) {
        //上传到内网
        log.info("上传到内网ftp");
        //ftp上传
        PushConfig.FtpConfig ftpConfig = pushConfig.getInternet().getIn().getFtp();
        log.info("ftp配置：{}", ftpConfig);
        BcxinFtpClient ftpClient = new BcxinFtpClient();
        try{
            //链接ftp服务器
            boolean conn = ftpClient.connect(ftpConfig.getIp(), ftpConfig.getUsername(), ftpConfig.getPassword(), ftpConfig.getPort());
            if(!conn){
                pushRecord.setUploadResult("ftp连接失败！");
                log.info("ftp连接失败");
            } else {
                log.info("ftp连接成功");
                int successCount = 0;
                List<String> uploadResultList = new ArrayList<>();
                //获取解压出来的所有文件
                List<File> fileList = FileUtil.loopFiles(new File(pushRecord.getUploadsPath()));
                if (CollectionUtils.isNotEmpty(fileList)) {
                    pushRecord.setUploadFileCount(fileList.size());
                    for (File file : fileList) {
                        //文件的路径
                        String absolutePath = file.getAbsolutePath();
                        absolutePath = absolutePath.replaceAll("\\\\", "/");
                        absolutePath = absolutePath.substring(absolutePath.indexOf("/uploads"));
                        //ftp目标文件
                        String ftpFile = ftpConfig.getPath() + absolutePath;
                        ftpFile = ftpClient.getEncodingName(ftpFile);
                        log.info("上传文件到ftp，本地文件路径：{}， ftp文件路径：{}", absolutePath, ftpFile);
                        //上传
                        boolean result = FtpUtil.bcxUploadFile(ftpClient, ftpFile, new FileInputStream(file));
                        String uploadResult = "";
                        //上传结果
                        if (result) {
//                            uploadResult = String.format("success，文件：%s，上传ftp成功！", absolutePath);
                            successCount++;
                        } else {
                            uploadResult = String.format("fail，文件：%s，上传ftp失败！", absolutePath);
                            log.info(uploadResult);
                            uploadResultList.add(uploadResult);
                        }
                    }
                }
                pushRecord.setUploadSuccessFileCount(successCount);
                pushRecord.setUploadResult(uploadResultList.size() > 0 ? String.join("\r\n", uploadResultList) : "all-success");
                log.info("上传ftp完成");
            }
        } catch (Exception e){
            pushRecord.setUploadResult(e.getMessage());
            log.info("ftp上传失败，{}", e.getMessage(), e);
        } finally {
            try {
                ftpClient.disconnect();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * description: 上传文件到外网
     * author: linchunpeng
     * date:  2023-04-23 14:22
     */
    private void uploadToOut(PushRecord pushRecord) {
        //上传到外网
        //复制file-path下的uploads文件夹到 save-path下的
        log.info("上传文件到外网");
        String srcPath = pushRecord.getUploadsPath();
        if (FileUtil.exist(srcPath)) {
            String savePath = pushConfig.getInternet().getOut().getSavePath();
            log.info("复制文件，从：{}，复制到：{}", srcPath, savePath);
            FileUtil.copy(srcPath, savePath, true);
            pushRecord.setUploadResult(String.format("复制文件，从：%s，复制到：%s", srcPath, savePath));
        } else {
            log.info("摆渡包不存在附件，无需复制");
            pushRecord.setUploadResult("摆渡包不存在附件，无需复制");
        }
    }

    /**
     * description: 确认文件是否上传成功，并记录日志
     * author: linchunpeng
     * date:  2023-04-23 14:22
     */
    private void checkFilesSuccess(PushRecord pushRecord) {
        //复制完成，读取txt文件，遍历下载的每条url看是否都能访问成功
        log.info("等待10分钟，确认文件是否上传成功，并记录日志");
        try {
            Thread.sleep(600000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("10分钟已过，开始确认");
        File downloadFile = new File(pushRecord.getDownloadTxtPath());
        if (FileUtil.exist(downloadFile)) {
            String checkErrorFile = pushRecord.getExecuteErrorTxtPath();
            List<String> errorList = new ArrayList<>();
            errorList.add("附件上传异常列表见以下列出的附件链接，复制所有附件链接到旧摆渡工具进行附件补偿：");
            List<String> urlList = FileUtil.readUtf8Lines(downloadFile);
            for (String fileUrl : urlList) {
                try {
                    //访问地址，需要做内外网转换
                    String visitFileUrl = null;
                    if (fileUrl.contains("/uploads/")) {
                        visitFileUrl = pushConfig.getInternet().getServerUrl().concat(fileUrl.substring(fileUrl.indexOf("/uploads/")));
                    } else {
                        String tempUrl = fileUrl.substring(8).replace("/upload/", "/");
                        visitFileUrl = pushConfig.getInternet().getServerUrl().concat("/uploads").concat(tempUrl.substring(tempUrl.indexOf("/")));
                    }
                    //获取访问结果
                    boolean result = FileUtils.checkFileHutool(visitFileUrl);
                    //新增确认日志
                    if (result) {
                        pushRecordUploadLogService.createLog(pushRecord.getRequestId(), pushRecord.getId(), null, fileUrl, visitFileUrl, result);
                    } else {
                        errorList.add(fileUrl);
                    }
                } catch (Exception e) {
                    log.error("确认文件是否上传成功出错，{}", e.getMessage(), e);
                }
            }
            log.info("第一次确认完成");
            if (errorList.size() > 1) {
                log.info("有验证错误的文件url，等待5分钟后，再次验证");
                try {
                    Thread.sleep(300000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("5分钟已过，开始第二次确认");
                for (int i = errorList.size() -1; i > 0; i--) {
                    try {
                        String fileUrl = errorList.get(i);
                        //访问地址，需要做内外网转换
                        String visitFileUrl = null;
                        if (fileUrl.contains("/uploads/")) {
                            visitFileUrl = pushConfig.getInternet().getServerUrl().concat(fileUrl.substring(fileUrl.indexOf("/uploads/")));
                        } else {
                            String tempUrl = fileUrl.substring(8).replace("/upload/", "/");
                            visitFileUrl = pushConfig.getInternet().getServerUrl().concat("/uploads").concat(tempUrl.substring(tempUrl.indexOf("/")));
                        }
                        boolean result = FileUtils.checkFile(visitFileUrl);
                        //错误的上次没有记录日志，这次记录
                        pushRecordUploadLogService.createLog(pushRecord.getRequestId(), pushRecord.getId(), null, fileUrl, visitFileUrl, result);
                        if (result) {
                            //如果访问正常，移除错误列表
                            errorList.remove(i);
                        }
                    } catch (Exception e) {
                        log.error("第二次确认文件是否上传成功出错，{}", e.getMessage(), e);
                    }
                }
                log.info("第二次确认完成");
            }
            //访问错误的写入文件
            FileUtil.appendUtf8Lines(errorList, checkErrorFile);
        }
        log.info("确认完成");
    }


    /**
     * description: 执行sql脚本文件
     * author: linchunpeng
     * date:  2023-04-24 14:50
     */
    private void executeSqlFiles(PushRecord pushRecord) {
        //执行sql文件
        log.info("开始执行sql文件列表");
        pushRecord.setSqlStartTime(new Date());
        try {
            List<String> fileList = FileUtil.listFileNames(pushRecord.geSqlPath());
            if (CollectionUtils.isNotEmpty(fileList)) {
                //处理执行顺序
                this.moveToFirst(fileList, "__DXZvc8mnEmDqMARhK7Gsync_SENDSMSTOPOLICE___DXZvc8mnEmDqMARhK7G.sql");
                this.moveToFirst(fileList, "__DXZvc8mnEmDqMARhK7Gsync_bg_user_result___DXZvc8mnEmDqMARhK7G.sql");
                this.moveToFirst(fileList, "__Qlqia54jXSOKQxX0rofv_tlk_backgroundreasons___Qlqia54jXSOKQxX0rof.sql");
                this.moveToFirst(fileList, "tenant-appsync_bg_user_view_tenant-app.sql");
                this.moveToFirst(fileList, "tenant-appcompanyinfocollect.employees_tenant-app.sql");

                Connection connection =  DatabaseUtil.getConnection(url.concat("&jdbcCompliantTruncation=false"), username, password);
                boolean isWindows = "out-dev".equals(pushConfig.getEnvir());
                try {
                    int sqlFileCount = 0;
                    int sqlSuccessFileCount = 0;
                    List<String> sqlResultList = new ArrayList<>();
                    String sqlErrorFile = pushRecord.getExecuteErrorTxtPath();
                    List<String> errorList = new ArrayList<>();
                    errorList.add("");
                    errorList.add("sql脚本执行异常见以下列出的脚本文件，根据以下错误信息反馈给开发人员：");
                    //遍历文件列表
                    for (String sqlFile : fileList) {
                        sqlFileCount++;
                        //读取文件内容
                        LineIterator lineIterator = null;
                        try {
                            Date startTime = new Date();
                            log.info("开始执行sql文件：{}", sqlFile);
                            String sqlResult = DatabaseUtil.execCommand(isWindows, pushConfig.getDbIp(), pushConfig.getDbPort(), pushConfig.getDbUsername(),
                                    pushConfig.getDbPassword(), pushRecord.geSqlPath().concat(File.separator).concat(sqlFile));
                            Date endTime = new Date();
                            log.info("执行结果：{}", sqlResult);
                            String execResult = "success";
                            if (sqlResult.contains("ERROR")) {
                                execResult = sqlResult.substring(sqlResult.indexOf("ERROR"));
                                log.error("fail，文件：{}，执行失败，原因：{}", sqlFile, execResult);
                                sqlResultList.add(String.format("fail，文件：%s，执行失败，原因：%s", sqlFile, execResult));
                                errorList.add(String.format("文件：%s，执行失败，原因：%s", sqlFile, execResult));
                            } else {
                                sqlResultList.add(String.format("success，文件：%s，执行成功！", sqlFile));
                                log.info("success，文件：{}，执行成功！", sqlFile);
                                sqlSuccessFileCount++;
                            }
                            //记录执行日志
                            pushRecordSqlLogService.createLog(pushRecord.getRequestId(), pushRecord.getId(), sqlFile, 0, startTime, endTime, execResult);
                        } catch (Exception e) {
                            e.printStackTrace();
                            String eMessage = e.getMessage();
                            log.error("fail，文件：{}，执行失败，原因：{}", sqlFile, eMessage);
                            //记录执行日志
                            pushRecordSqlLogService.createLog(pushRecord.getRequestId(), pushRecord.getId(), sqlFile, 0, null, null, eMessage);
                            sqlResultList.add(String.format("fail，文件：%s，执行失败，原因：%s", sqlFile, eMessage));
                            errorList.add(String.format("文件：%s，执行失败，原因：%s", sqlFile, eMessage));
                        } finally {
                            IOUtils.closeQuietly(lineIterator);
                        }
                    }
                    pushRecord.setSqlFileCount(sqlFileCount);
                    pushRecord.setSqlSuccessFileCount(sqlSuccessFileCount);
                    pushRecord.setSqlResult(String.join("\r\n", sqlResultList));
                    //执行错误的写入文件
                    FileUtil.appendUtf8Lines(errorList, sqlErrorFile);
                } catch (Exception e) {
                    log.error("执行sql文件异常，{}", e.getMessage(), e);
                } finally {
                    try {
                        if (connection != null) {
                            connection.close();
                        }
                    } catch (SQLException e) {
                        log.error("执行sql文件异常，{}", e.getMessage(), e);
                    }
                }
            }
        } catch (Exception e) {
            log.error("执行sql文件异常，{}", e.getMessage(), e);
        }
        pushRecord.setSqlEndTime(new Date());
        log.info("执行sql文件结束");
    }

    private void moveToFirst(List<String> list, String element) {
        if (list.remove(element)) {
            list.add(0, element);
        }
    }

    /**
     * description：查询自动摆渡推送结果
     * author：linchunpeng
     * date：2024/6/17
     */
    @Override
    public FerryReceiveTaskPushResult queryAutoFerryPushResult(String requestId) {
        log.info("===================================查询自动摆渡推送结果，查询requestId：{}=======================================", requestId);
        FerryReceiveTaskPushResult ferryReceiveTaskPushResult = new FerryReceiveTaskPushResult();
        if (StringUtils.isNotBlank(requestId)) {
            ferryReceiveTaskPushResult.setRequestId(requestId);
            PushRecord pushRecord = pushRecordService.getByRequestId(requestId);
            if (pushRecord != null && pushRecord.getSqlEndTime() != null) {
                String receiveResult = String.format("本次摆渡情况：需要执行的sql文件数量：%s，执行成功的sql文件数量：%s，需要上传的文件数量：%s，上传成功的文件数量：%s",
                        pushRecord.getSqlFileCount() != null ? pushRecord.getSqlFileCount() : "没记录",
                        pushRecord.getSqlSuccessFileCount() != null ? pushRecord.getSqlSuccessFileCount() : "没记录",
                        pushRecord.getUploadFileCount() != null ? pushRecord.getUploadFileCount() : "没记录",
                        pushRecord.getUploadSuccessFileCount() != null ? pushRecord.getUploadSuccessFileCount() : "没记录");
                ferryReceiveTaskPushResult.setReceiveResult(receiveResult);
                ferryReceiveTaskPushResult.setTaskStatus(10);
            }
        }
        return ferryReceiveTaskPushResult;
    }

}
