package cn.ibizlab.core.extensions.service;

import cn.ibizlab.core.analysis.domain.DABuild;
import cn.ibizlab.core.analysis.domain.DADimension;
import cn.ibizlab.core.analysis.domain.DAMetric;
import cn.ibizlab.core.analysis.service.IDABuildService;
import cn.ibizlab.core.analysis.service.IDAMetricService;
import cn.ibizlab.core.extensions.domain.AssembleModel;
import cn.ibizlab.core.lite.domain.MetaEntity;
import cn.ibizlab.core.lite.domain.MetaModel;
import cn.ibizlab.core.lite.domain.MetaRelationship;
import cn.ibizlab.core.lite.extensions.domain.EntityModel;
import cn.ibizlab.core.lite.extensions.domain.RelationshipModel;
import cn.ibizlab.core.lite.extensions.domain.SysModel;
import cn.ibizlab.core.lite.extensions.model.DataModel;
import cn.ibizlab.core.lite.extensions.model.Property;
import cn.ibizlab.core.lite.extensions.service.LiteModelService;
import cn.ibizlab.core.lite.service.IMetaEntityService;
import cn.ibizlab.core.lite.service.IMetaModelService;
import cn.ibizlab.core.lite.service.impl.MetaModelServiceImpl;
import cn.ibizlab.core.rule.domain.RuleItem;
import cn.ibizlab.core.rule.service.IRuleItemService;
import cn.ibizlab.util.domain.FileItem;
import cn.ibizlab.util.errors.InternalServerErrorException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.util.*;

import static cn.ibizlab.util.service.SimpleFileService.getExtensionName;

/**
 * 实体[模型] 自定义服务对象
 */
@Slf4j
@Primary
@Service("MetaModelExService")
public class MetaModelExService extends MetaModelServiceImpl {
    @Resource
    @Lazy
    private IMetaModelService metaModelService;

    @Resource
    @Lazy
    public IRuleItemService ruleItemService;

    @Resource
    @Lazy
    public IDABuildService idaBuildService;

    @Resource
    @Lazy
    public IDAMetricService idaMetricService;

    @Resource
    @Lazy
    private LiteModelService liteModelService;

    @Resource
    @Lazy
    private IMetaEntityService metaEntityService;

    @Value("${ibiz.filePath:/app/file/}")
    private String fileRoot;

    @Override
    protected Class currentModelClass() {
        return com.baomidou.mybatisplus.core.toolkit.ReflectionKit.getSuperClassGenericType(this.getClass().getSuperclass(), 1);
    }

    @Override
    @Transactional
    public MetaModel change(MetaModel et) {
        return super.change(et);
    }

    /**
     * [ExportFile:导出文件] 行为扩展：导出文件json格式
     *
     * @param et
     * @return
     */
    @Override
    @Transactional
    public MetaModel exportFile(MetaModel et) {
        return super.exportFile(et);
    }

    /**
     * [ImportFile:导入文件] 行为扩展：传入一个文件，将该文件转化成对象，根据对象执行表更新操作
     *
     * @param et
     * @return
     */
    @Override
    @Transactional
    public MetaModel importFile(MetaModel et) {
        JSONArray contentArray = JSONArray.parseArray(et.getName());
        // 上传数据取最新一条
        String json = contentArray.get(contentArray.size() > 0 ? contentArray.size() - 1 : 0).toString();
        Map etMaps = (Map) JSON.parse(json);
        File file = getUploadFile(etMaps.get("id").toString());
        List<AssembleModel> assembleModels = readJsonFile(file.toString());
        importFile(assembleModels);
        return super.importFile(et);
    }

    @Override
    @Transactional
    public boolean exportFileBatch(List<MetaModel> metaModels) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        String filePath = getAssembleString(metaModels);
        File file = getDownLoadFile(filePath);
        // 将请求头加上Content-Disposition，浏览器会自动识别为文件，并将其下载下来
        response.setHeader("Content-Disposition", "attachment;filename=" + getFileName(file.getName()));
        sendResponse(response, file);
        return true;
    }

    /**
     * 把模型所有需要的东西组装后打包导出
     *
     * @param et 所需打包的模型
     * @return
     */
    @Transactional
    public AssembleModel assemble(MetaModel et) {
        String modelId = et.getId();
        AssembleModel models = new AssembleModel();
        List<DAMetric> metrics = new ArrayList<>();
        LinkedHashSet<String> entities = new LinkedHashSet<>();

        //  查询模型
        MetaModel metaModel = metaModelService.get(et.getId());
        models.setMetaModel(metaModel);

        //  查询模型对应的N个规则
        for (RuleItem ruleItem : metaModel.getRuleitems()) {
            JSONObject fieldSet = JSONObject.parseObject(ruleItem.getFieldSet());
            if (!ObjectUtils.isEmpty(fieldSet)) {
                for (String entityName : fieldSet.keySet()) {
                    if (entityName.endsWith("entity")) {
                        entities.add(fieldSet.getString(entityName));
                    }
                }
            }
        }

        // 查询模型对应的N个(分析)构建
        if (!ObjectUtils.isEmpty(metaModel.getBuilds())) {
            for (DABuild build : metaModel.getBuilds()) {
                build = idaBuildService.get(build.getBuildId());
                // 维度中用到的实体
                if (!ObjectUtils.isEmpty(build.getDadimension())) {
                    for (DADimension dimension : build.getDadimension()) {
                        if (!ObjectUtils.isEmpty(dimension.getName())) {
                            entities.add(dimension.getName());
                        }
                    }
                }
                // 指标
                if (!ObjectUtils.isEmpty(build.getMetrics())) {
                    for (DAMetric metric : build.getMetrics()) {
                        JSONObject fieldSet = JSON.parseObject(metric.getFieldSet());
                        for (String entityName : fieldSet.keySet()) {
                            if (entityName.endsWith("entity")) {
                                entities.add(entityName);
                            }
                        }
                    }
                    metrics.addAll(build.getMetrics());
                }
            }
        }
        models.setEntityModels(getEntityModel(entities, modelId));
        models.setRuleItems(metaModel.getRuleitems());
        models.setDaBuilds(metaModel.getBuilds());
        models.setMetrics(metrics);
        return models;
    }

    /**
     * 传入一组实体模型名和模型id来返回实体模型集合
     *
     * @param entities 实体模型名组
     * @param modelId  模型id
     * @return 实体模型集合
     */
    private List<EntityModel> getEntityModel(LinkedHashSet<String> entities, String modelId) {
        List<EntityModel> entityModels = new ArrayList<>();
        DataModel model = liteModelService.getDataModel(modelId);
        if (model != null) {
            for (String entity : entities) {
                EntityModel entityModel = null;
                Property subModel = model.getObjectProperty(entity);
                if (subModel != null) {
                    entityModel = subModel.getEntityModel();
                } else {
                    if (model.getNestedDataModel(entity) != null) {
                        entityModel = model.getNestedDataModel(entity).getFactEntityModel();
                    }
                }
                if (entityModel != null) {
                    entityModels.add(entityModel);
                }
            }
        }
        return entityModels;
    }

    /**
     * 根据组装模型包集合来对数据进行导入
     *
     * @param assembleModels 组装模型包
     * @return
     */
    @Transactional
    public boolean importFile(List<AssembleModel> assembleModels) {
        for (AssembleModel assembleModel : assembleModels) {
            // 前置铺垫实体模型，防止外键冲突
            if (assembleModel.getEntityModels() != null) {
                List<MetaEntity> metaEntities = new ArrayList<>();
                assembleModel.getEntityModels().forEach(item->{
                    metaEntities.add(item.getEntity());
                });
                metaEntityService.saveBatch(metaEntities);
            }
            if (assembleModel.getMetaModel() != null) {
                metaModelService.save(assembleModel.getMetaModel());
            }
            if (assembleModel.getDaBuilds() != null) {
                idaBuildService.saveBatch(assembleModel.getDaBuilds());
            }
            if (assembleModel.getMetrics() != null) {
                idaMetricService.saveBatch(assembleModel.getMetrics());
            }
            if (assembleModel.getRuleItems() != null) {
                ruleItemService.saveBatch(assembleModel.getRuleItems());
            }
        }
        return true;
    }

    public FileItem uploadFile(MultipartFile multipartFile) {
        FileItem item = null;
        // 获取文件名
        String fileName = multipartFile.getOriginalFilename();
        // 获取文件后缀
        String extname = "." + getExtensionName(fileName);
        try {
            String fileId = DigestUtils.md5DigestAsHex(multipartFile.getInputStream());
            String fileFullPath = this.fileRoot + "ibizutil" + File.separator + File.separator + fileName;
            File file = new File(fileFullPath);
            File parent = new File(file.getParent());
            if (!parent.exists()) {
                parent.mkdirs();
            }
            FileCopyUtils.copy(multipartFile.getInputStream(), Files.newOutputStream(file.toPath()));
            item = new FileItem(fileId, fileName, fileId, fileName, (int) multipartFile.getSize(), extname);
        } catch (IOException e) {
            throw new InternalServerErrorException("文件上传失败，" + e);
        }
        return item;
    }

    protected String getFileName(String fileName) {
        try {
            return new String(fileName.getBytes("utf-8"), "iso8859-1");//防止中文乱码
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return fileName;
    }

    /**
     * 保存文件在本地后返回一个文件名
     *
     * @param jsonObject
     * @return
     */
    public String saveFile(String jsonObject) {
        String fileName = "PACKAGEMODEL_" + UUID.randomUUID().toString().replace("-", "").toUpperCase();
        String extname = ".json";
        try {
            String fileFullPath = this.fileRoot + "ibizutil" + File.separator + fileName + extname;
            File file = new File(fileFullPath);
            File parent = new File(file.getParent());
            if (!parent.exists()) {
                parent.mkdirs();
            }
            writeFile(fileFullPath, jsonObject);
        } catch (Exception e) {
            throw new InternalServerErrorException("文件上传失败，" + e);
        }
        return fileName + extname;
    }

    /**
     * 获取文件，导出时候使用
     * @param fileId
     * @return
     */
    public File getDownLoadFile(String fileId) {
        String dirPath = this.fileRoot + "ibizutil" + File.separator + fileId;
        File parent = new File(dirPath);
        if (parent.exists()) {
            return parent;
        }
        throw new InternalServerErrorException("文件未找到");
    }

    public File getUploadFile(String fileId) {
        String dirPath = this.fileRoot + "ibizutil" + File.separator + fileId;
        File parent = new File(dirPath);
        if (parent.exists() && parent.isDirectory() && parent.listFiles().length > 0) {
            return parent.listFiles()[0];
        }
        throw new InternalServerErrorException("文件未找到");
    }

    public static void writeFile(String filePath, String sets) throws IOException {
        FileWriter fw = new FileWriter(filePath);
        PrintWriter out = new PrintWriter(fw);
        out.write(sets);
        out.println();
        fw.close();
        out.close();
    }

    /**
     * 将文件读取的模型拼装成json，保存到磁盘
     *
     * @param model
     * @return
     */
    public String getAssembleString(List<MetaModel> model) {
        List<AssembleModel> temp = new ArrayList<>();
        for (MetaModel metaModel : model) {
            temp.add(assemble(metaModel));
        }
        String json = JSON.toJSONString(temp);
        String fileName = this.saveFile(json);
        return fileName;
    }

    /**
     * 发送文件响应给浏览器
     * @param response
     * @param file
     */
    protected void sendResponse(HttpServletResponse response, File file) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(file));
            bos = new BufferedOutputStream(response.getOutputStream());
            byte[] buff = new byte[2048];
            int bytesRead;
            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
                bos.write(buff, 0, bytesRead);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 去磁盘读取文件，并将其转化成组装模型对象
     * @param fileName
     * @return
     */
    public static List<AssembleModel> readJsonFile(String fileName) {
        String jsonStr = "";
        try {
            File jsonFile = new File(fileName);
            FileReader fileReader = new FileReader(jsonFile);
            Reader reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
            List<AssembleModel> assembleModel = JSONObject.parseArray(jsonStr, AssembleModel.class);
            return assembleModel;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
