package com.yice.webadmin.app.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.pagehelper.Page;
import com.yice.common.core.base.dao.BaseDaoMapper;
import com.yice.common.core.base.service.BaseService;
import com.yice.common.core.constant.ErrorCodeEnum;
import com.yice.common.core.object.CallResult;
import com.yice.common.core.object.MyPageParam;
import com.yice.common.core.object.MyRelationParam;
import com.yice.common.core.object.ResponseResult;
import com.yice.common.core.util.MyModelUtil;
import com.yice.common.sequence.wrapper.IdGeneratorWrapper;
import com.yice.webadmin.app.config.PythonConfig;
import com.yice.webadmin.app.constant.DatasetConstant;
import com.yice.webadmin.app.dao.DatasetVersionMapper;
import com.yice.webadmin.app.data.DatasetData;
import com.yice.webadmin.app.model.DatasetDetail;
import com.yice.webadmin.app.model.DatasetManage;
import com.yice.webadmin.app.model.DatasetVersion;
import com.yice.webadmin.app.service.DatasetCleanService;
import com.yice.webadmin.app.service.DatasetDataService;
import com.yice.webadmin.app.service.DatasetManageService;
import com.yice.webadmin.app.service.DatasetVersionService;
import com.yice.webadmin.app.util.Sha1Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.List;

/**
 * 数据集版本数据操作服务类。
 *
 * @author linking
 * @date 2023-04-13
 */
@Slf4j
@Service("datasetVersionService")
public class DatasetVersionServiceImpl extends BaseService<DatasetVersion, Long> implements DatasetVersionService {

    @Autowired
    private DatasetVersionMapper datasetVersionMapper;
    @Autowired
    private DatasetCleanService datasetCleanService;

    @Autowired
    private DatasetManageService datasetManageService;
    @Autowired
    private IdGeneratorWrapper idGenerator;
    @Autowired
    private PythonConfig pythonConfig;
    @Autowired
    private DatasetDataService datasetDataService;

    /**
     * 返回当前Service的主表Mapper对象。
     *
     * @return 主表Mapper对象。
     */
    @Override
    protected BaseDaoMapper<DatasetVersion> mapper() {
        return datasetVersionMapper;
    }

    /**
     * 保存新增对象。
     *
     * @param datasetVersion 新增对象。
     * @return 返回新增对象。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public DatasetVersion saveNew(DatasetVersion datasetVersion) {
        DatasetManage reDatasetManage = this.datasetManageService.getById(datasetVersion.getDatasetId());
        DatasetVersion datasetVersionFilter = new DatasetVersion();
        datasetVersionFilter.setDatasetId(datasetVersion.getDatasetId());
        List<DatasetVersion> datasetVersionList = this.getDatasetVersionList(datasetVersionFilter, "dataset_version");
        Integer version = 1;
        if (datasetVersionList != null && datasetVersionList.size() != 0) {
            version = datasetVersionList.get(datasetVersionList.size() - 1).getDatasetVersion() + 1;
        }
        datasetVersion.setDatasetVersion(version);
        datasetVersion.setVersionName(reDatasetManage.getDatasetName() + "_V" + version);
        datasetVersion.setDatasetId(reDatasetManage.getDatasetId());
        datasetVersion.setCleanStatus(0);
        datasetVersion.setDataVolume(0L);
        datasetVersion.setEnhanceStatus(0);
        datasetVersion.setInputStatus(0);
        datasetVersion.setMarkProgress("0");
        datasetVersion.setOutputStatus(0);
        datasetVersion.setReleaseStatus(0);
        datasetVersion.setTemplate(reDatasetManage.getTemplate());
        datasetVersion.setDimensionType(reDatasetManage.getDimensionType());

        insertDatasetVersion(datasetVersion);
        return datasetVersion;
    }

    /**
     * 添加数据对象
     *
     * @param datasetVersion 保存对象
     */
    private void insertDatasetVersion(DatasetVersion datasetVersion) {
        try {
            //先进行数据填充
            this.buildDefaultValue(datasetVersion);
            //进行存储、写入以及更新配置等操作
            DatasetVersion dataset = dealWithWriteAndSave(datasetVersion.getDatasetId(),
                    datasetVersion.getHisVersion(),datasetVersion.getVersionId(),
                    datasetVersion.getVersionName());
            if(null != dataset) {
                datasetVersion.setDataVolume(dataset.getDataVolume());
                datasetVersion.setFileUrl(dataset.getFileUrl());
                datasetVersion.setInputStatus(dataset.getInputStatus());
            }
            //写入数据库中
            datasetVersionMapper.insert(datasetVersion);
        } catch (IOException e) {
            log.error("Failed to save new data database [" + datasetVersion.getVersionName() + " ].", e);
        }
    }

    /**
     * 进行存储、写入以及更新配置等操作
     * @param datasetId 数据集标识
     * @param hisVersion 历史标识
     * @param versionId 版本标识
     * @param datasetName 数据集名称
     */
    private DatasetVersion dealWithWriteAndSave(Long datasetId, Integer hisVersion,
                                                Long versionId, String datasetName) throws IOException {
        DatasetVersion datasetVersion = null;
        if (null != hisVersion && hisVersion > 0) {
            DatasetVersion filter = new DatasetVersion();
            filter.setDatasetVersion(hisVersion);
            filter.setDatasetId(datasetId);
            datasetVersion = this.getOne(filter);

            if (null != datasetVersion && StringUtils.isNotBlank(datasetVersion.getFileUrl())) {
                String fileUrl = datasetVersion.getFileUrl();
                //获取新的json存放地址
                String newFileName = this.extractFileName(fileUrl) + datasetName + ".json";
                //写入到新的json文件格式中
                byte[] bytes = writeAndSaveFileDetail(newFileName, fileUrl, versionId);
                //再存储数据集配置文件
                this.saveDatasetInfo(datasetName);
                //重新赋值参数
                datasetVersion.setFileUrl(newFileName);
                datasetVersion.setInputStatus(DatasetConstant.STATUS_PUBLISHED);
                datasetVersion.setDataVolume(Long.valueOf(JSON.parseArray(new String(bytes, StandardCharsets.UTF_8)).size()));
            } else {
                datasetVersion.setInputStatus(DatasetConstant.STATUS_UNPUBLISHED);
            }
        }
        return datasetVersion;
    }

    /**
     * 存储到python数据集中并进行更新操作
     * @param versionName 版本名称
     * @throws IOException 异常操作
     */
    @Override
    public void saveDatasetInfo(String versionName) throws IOException {
        File file = new File(pythonConfig.getDatasetFileBaseDir(), pythonConfig.getDatasetInfo());
        if (!file.exists()) {
            file.createNewFile();
        }
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        JsonNode rootNode = objectMapper.readTree(file);
        if (rootNode == null || rootNode.isNull() || rootNode.size() < 1) {
            rootNode = objectMapper.createObjectNode();
        }
        //此处应该要做键值映射关系，目前没做，后面需要修改
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("file_name", versionName + ".json");
        jsonObject.put("file_sha1", Sha1Util.getSha1(versionName));
        ((ObjectNode) rootNode).set(versionName, objectMapper.readTree(jsonObject.toJSONString()));
        objectMapper.writeValue(new File(pythonConfig.getDatasetFileBaseDir(), pythonConfig.getDatasetInfo()), rootNode);
    }

    /**
     * 截取最后一个斜杠后的部分
     * @param fileName 文件名称
     * @return 返回截取后的字符串
     */
    private String extractFileName(String fileName) {
        int lastSlashIndex = fileName.lastIndexOf('/');
        if (lastSlashIndex != -1) {
            fileName = fileName.substring(0, lastSlashIndex + 1);
        }
        return fileName;
    }

    /**
     * 文件目录进行复制并写入到Mongodb数据库中
     * @param fullName 新文件名称
     * @param versionId 版本标识
     * @param originFileName 原始文件名
     * @return 字节编码
     */
    private byte[] writeAndSaveFileDetail(String fullName,String originFileName,Long versionId) throws IOException{
        byte[] bytes = null;
        try{
            bytes = readFileToBytes(originFileName);
            Path path = Paths.get(fullName);
            // 如果没有files文件夹，则创建
            if (!Files.isWritable(path)) {
                Files.createDirectories(Paths.get(pythonConfig.getDatasetFileBaseDir()));
            }
            // 文件写入指定路径
            Files.write(path, bytes);
            // 写入到mongodb中
            writeDatasetFile(bytes,fullName,versionId);
        } catch (IOException e) {
            log.error("Failed to write imported file [" + fullName + " ].", e);
            throw e;
        }
        return bytes;
    }

    /**
     * 读取地址内容并转成bytes数据
     * @param filePath 文件地址
     * @return byte数组
     * @throws IOException 异常
     */
    private static byte[] readFileToBytes(String filePath) throws IOException {
        // 使用Paths获取Path对象
        Path path = Paths.get(filePath);
        // 使用Files类的readAllBytes方法读取文件内容为byte[]
        byte[] fileBytes = Files.readAllBytes(path);
        return fileBytes;
    }

    /**
     * 写入到mongodb中
     * @param bytes 字节
     * @param originalFilename 导入文件名称
     * @param versionId 版本标识
     */
    @Override
    public void writeDatasetFile(byte[] bytes, String originalFilename, Long versionId) {
        try {
            // 或者指定字符集进行转换,替换"UTF-8"为你想要使用的字符集
            String result = new String(bytes, StandardCharsets.UTF_8);
            //先删除数据集
            datasetDataService.delete(versionId);
            //保存到mongodb中
            datasetDataService.save(new DatasetData(null, versionId, result, new Date(), null));
        }catch (Exception ex) {
            log.error("Failed to write mongodb database [" + originalFilename + " ].", ex);
        }
    }

    /**
     * 利用数据库的insertList语法，批量插入对象列表。
     *
     * @param datasetVersionList 新增对象列表。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveNewBatch(List<DatasetVersion> datasetVersionList) {
        if (CollUtil.isNotEmpty(datasetVersionList)) {
            datasetVersionList.forEach(this::buildDefaultValue);
            datasetVersionMapper.insertList(datasetVersionList);
        }
    }

    /**
     * 更新数据对象。
     *
     * @param datasetVersion         更新的对象。
     * @param originalDatasetVersion 原有数据对象。
     * @return 成功返回true，否则false。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean update(DatasetVersion datasetVersion, DatasetVersion originalDatasetVersion) {
        MyModelUtil.fillCommonsForUpdate(datasetVersion, originalDatasetVersion);
        // 这里重点提示，在执行主表数据更新之前，如果有哪些字段不支持修改操作，请用原有数据对象字段替换当前数据字段。
        UpdateWrapper<DatasetVersion> uw = this.createUpdateQueryForNullValue(datasetVersion, datasetVersion.getVersionId());
        return datasetVersionMapper.update(datasetVersion, uw) == 1;
    }

    /**
     * 更新数据对象。
     *
     * @param datasetVersion         更新的对象。
     * @return 成功返回true，否则false。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean update(DatasetVersion datasetVersion) {
        return datasetVersionMapper.updateById(datasetVersion) > 0;
    }

    /**
     * 写入json格式路径
     * @param datasetVersion
     * @throws IOException 异常
     */
    public boolean saveDatasetJsonPath(DatasetVersion datasetVersion) throws IOException{
        //导入时不需要写入到json文件中
        String versionName = datasetVersion.getVersionName();
        //先存储文件
        String fullName = pythonConfig.getDatasetFileBaseDir() + versionName + ".json";
        this.doDealTaskHandler(datasetVersion.getVersionId(), versionName, fullName);
        //再存储数据集配置文件
        this.saveDatasetInfo(versionName);
        DatasetVersion filter = new DatasetVersion();
        filter.setVersionId(datasetVersion.getVersionId());
        filter.setFileUrl(fullName);
        filter.setReleaseStatus(DatasetConstant.STATUS_PUBLISHED);
        return this.updateById(filter);
    }

    /**
     * ==============================
     * ===总数据清洗过程===
     * 1、分页处理每页10000条数据
     * 2、更新Mongodb数据库中的数据
     * 3、更新json存储地址的数据集数据
     * 4、更新版本数据集状态
     * ==============================
     * 处理数据集
     * @param datasetId 清洗任务id
     * @param versionName 数据集名称
     * @return 清洗列表
     */
    private void doDealTaskHandler(Long datasetId, String versionName,String fileUrl) {
        try {
            Integer index = 0;
            Long count = datasetDataService.count(datasetId);
            if (count > 0) {
                int pageSize = DatasetConstant.MAX_SIZE;
                int totalPages = (int) Math.ceil((double) count / pageSize);
                MyPageParam param;
                for (int i = 1; i <= totalPages; i++) {
                    param = new MyPageParam();
                    param.setPageNum(i);
                    param.setPageSize(pageSize);
                    List<DatasetData> dataList = datasetDataService.list(datasetId, param);

                    //写入到数据集中
                    if(CollUtil.isNotEmpty(dataList)) {
                        datasetCleanService.appendDataListToFile(fileUrl ,dataList, i);
                    }
                }

                //解析文件去掉多余的数据，比如文件里面最后一个，多加了一个",",缺少符号[]
                if(index > 0){
                    datasetCleanService.readJsonAppendSymbol(fileUrl);
                }

                //删除为空的数据集数据
                this.datasetDataService.deleteByData(datasetId);
            }
        } catch (Exception ex) {
            log.error("deal with task handler is error:" , ex);
        }
    }

    /**
     * 删除指定数据。
     *
     * @param versionId 主键Id。
     * @return 成功返回true，否则false。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean remove(Long versionId) {
        if (datasetVersionMapper.deleteById(versionId) == 0) {
            return false;
        }
        //删除数据集
        datasetDataService.delete(versionId);
        // datasetDetailService.removeByVersionId(versionId);
        return true;
    }

    private void updateDatasetInfoFile(String filePath, String keyToRemove) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        File jsonFile = new File(filePath);
        // 读取JSON文件
        JsonNode rootNode = objectMapper.readTree(jsonFile);
        // 删除指定键的对象
        if (rootNode.isObject()) {
            ObjectNode objectNode = (ObjectNode) rootNode;
            objectNode.remove(keyToRemove);
        } else {
            log.error("Root node is not an object.");
        }
        // 将更新后的JSON写回文件
        objectMapper.writeValue(jsonFile, rootNode);
    }

    /**
     * 当前服务的支持表为从表，根据主表的关联Id，删除一对多的从表数据。
     *
     * @param datasetId 从表关联字段。
     * @return 删除数量。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int removeByDatasetId(Long datasetId) {
        DatasetVersion deletedObject = new DatasetVersion();
        deletedObject.setDatasetId(datasetId);
        List<DatasetVersion> datasetVersions = this.getDatasetVersionList(deletedObject, "");
        for (DatasetVersion datasetVersion : datasetVersions) {
            if (datasetVersion.getReleaseStatus() == 0) {
                datasetDataService.delete(datasetVersion.getVersionId());
            }
        }
        return datasetVersionMapper.delete(new QueryWrapper<>(deletedObject));
    }

    /**
     * 获取单表查询结果。由于没有关联数据查询，因此在仅仅获取单表数据的场景下，效率更高。
     * 如果需要同时获取关联数据，请移步(getDatasetVersionListWithRelation)方法。
     *
     * @param filter  过滤对象。
     * @param orderBy 排序参数。
     * @return 查询结果集。
     */
    @Override
    public List<DatasetVersion> getDatasetVersionList(DatasetVersion filter, String orderBy) {
        return datasetVersionMapper.getDatasetVersionList(filter, orderBy);
    }

    /**
     * 获取主表的查询结果，以及主表关联的字典数据和一对一从表数据，以及一对一从表的字典数据。
     * 该查询会涉及到一对一从表的关联过滤，或一对多从表的嵌套关联过滤，因此性能不如单表过滤。
     * 如果仅仅需要获取主表数据，请移步(getDatasetVersionList)，以便获取更好的查询性能。
     *
     * @param filter              主表过滤对象。
     * @param datasetDetailFilter 一对一从表过滤对象。
     * @param orderBy             排序参数。
     * @return 查询结果集。
     */
    @Override
    public List<DatasetVersion> getDatasetVersionListWithRelation(DatasetVersion filter, DatasetDetail datasetDetailFilter, String orderBy) {
        List<DatasetVersion> resultList =
                datasetVersionMapper.getDatasetVersionListEx(filter, datasetDetailFilter, orderBy);
        // 在缺省生成的代码中，如果查询结果resultList不是Page对象，说明没有分页，那么就很可能是数据导出接口调用了当前方法。
        // 为了避免一次性的大量数据关联，规避因此而造成的系统运行性能冲击，这里手动进行了分批次读取，开发者可按需修改该值。
        int batchSize = resultList instanceof Page ? 0 : 1000;
        this.buildRelationForDataList(resultList, MyRelationParam.normal(), batchSize);
        return resultList;
    }

/*    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean remove(DatasetVersion originalDatasetVersion) {
        Long versionId = originalDatasetVersion.getVersionId();
        try {
            this.updateDatasetInfoFile(pythonConfig.getDatasetFileBaseDir() + pythonConfig.getDatasetInfo(),originalDatasetVersion.getVersionName());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //删除文件路径
        FileUtil.del(originalDatasetVersion.getFileUrl());
        //删除数据集
        datasetDataService.delete(versionId);
        if (datasetVersionMapper.deleteById(versionId) == 0) {
            return false;
        }
        return true;
    }*/

    /**
     * 根据最新对象和原有对象的数据对比，判断关联的字典数据和多对一主表数据是否都是合法数据。
     *
     * @param datasetVersion         最新数据对象。
     * @param originalDatasetVersion 原有数据对象。
     * @return 数据全部正确返回true，否则false。
     */
    @Override
    public CallResult verifyRelatedData(DatasetVersion datasetVersion, DatasetVersion originalDatasetVersion) {
        String errorMessageFormat = "数据验证失败，关联的%s并不存在，请刷新后重试！";
        //这里是一对多的验证
        if (this.needToVerify(datasetVersion, originalDatasetVersion, DatasetVersion::getDatasetId)
                && !datasetManageService.existId(datasetVersion.getDatasetId())) {
            return CallResult.error(String.format(errorMessageFormat, "数据集ID"));
        }
        return CallResult.ok();
    }

    private DatasetVersion buildDefaultValue(DatasetVersion datasetVersion) {
        if (datasetVersion.getVersionId() == null) {
            datasetVersion.setVersionId(idGenerator.nextLongId());
        }
        MyModelUtil.fillCommonsForInsert(datasetVersion);
        return datasetVersion;
    }
}
