package com.yihu.hos.broker.services.camel;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.DeleteResult;
import com.yihu.hos.broker.configurations.MongoConfiguration;
import com.yihu.hos.core.constants.CoreConstant;
import com.yihu.hos.core.datatype.ClassFileUtil;
import com.yihu.hos.core.datatype.StringUtil;
import com.yihu.hos.core.encrypt.DES;
import com.yihu.hos.web.framework.constant.ServiceFlowConstant;
import com.yihu.hos.web.framework.model.Result;
import com.yihu.hos.web.framework.model.bo.ServiceFlow;
import com.yihu.hos.web.framework.util.GridFSUtil;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;

/**
 * 对于Broker来说，Camel路由的加载只有这样的几个过程。
 * 文件相关
 * 1.Java， Class文件下载
 * 2.Java文件编译
 * 3.Class文件上传（有java编译生成的class的文件更新）
 * 4.Java，Class删除文件
 * Camel相关
 * 1.增加服务流程
 * 2.删除服务流程
 * 3.修改服务流程
 * 3.1增加Router，Processor
 * 3.2减少Router，Processor
 * 4.启动服务流程
 * 5.停止服务流程
 * <p>
 * Created by lingfeng on 2016/8/4.
 */
@Service
public class ESBCamelService {
    private static Logger logger = LogManager.getLogger(ESBCamelService.class);
    @Autowired
    private MongoConfiguration mongoConfig;
    private String dbName = "upload";
    private String serviceFlow = "serviceFlow";
    private String configuration = "configuration";
    @Autowired
    private ObjectMapper objectMapper;
    @Value("${hos.esb.rest-url}")
    private String centerUrl;

    public Result onServiceFlowAdd(String msg) {
        try {
            ServiceFlowValid serviceFlowValid = new ServiceFlowValid(msg).invoke();
            if (serviceFlowValid.is()) return Result.error("必要的入参数据不正确，请检查！");

            ServiceFlow serviceFlow = serviceFlowValid.getServiceFlow();
            ArrayList<ServiceFlow.HandleFile> handleFiles = serviceFlowValid.getHandleFiles();
            for (ServiceFlow.HandleFile handleFile : handleFiles) {
                boolean created;
                if ("java".equals(handleFile.getFileType())) {
                    logger.debug("generate class file...");
                    created = this.generateClassFile(handleFile);
                } else {
                    logger.debug("create class file");
                    created = this.createClassFile(handleFile);
                }

                if (!created) {
                    logger.debug("create class file failed");
                    return Result.error("服务流程增加失败！");
                }
            }

            logger.debug("add camel router" + serviceFlow.getRouteCode());
            this.addRouter(serviceFlow.getHandleFiles());

            logger.debug("start camel router," + serviceFlow.getRouteCode());
            SystemCamelContext.startRouter(serviceFlow.getRouteCode());

            logger.debug("start camel router success," + serviceFlow.getRouteCode());
            return Result.error("服务流程增加成功！");
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e);
            return Result.error("服务流程增加失败！");
        }
    }

    /**
     * 当外界组件通知一个新的processor处理器被定义时，该事件被触发。
     */
    public Result onServiceFlowModifyAdd(String msg) {
        try {
            ServiceFlowValid serviceFlowValid = new ServiceFlowValid(msg).invoke();
            if (serviceFlowValid.is()) return Result.error("必要的入参数据不正确，请检查！");

            ServiceFlow serviceFlow = serviceFlowValid.getServiceFlow();
            ServiceFlow.HandleFile handleFile = serviceFlowValid.getHandleFile();
            boolean created;
            if ("java".equals(handleFile.getFileType())) {
                created = this.generateClassFile(handleFile);
            } else {
                created = this.createClassFile(handleFile);
            }
            if (!created) {
                return Result.error("服务流程变更增加失败！");
            }

            SystemCamelContext.stopRouter(serviceFlow.getRouteCode());
            SystemCamelContext.removeRouter(serviceFlow.getRouteCode());
            this.addRouter(serviceFlowValid.getHandleFiles());
            SystemCamelContext.startRouter(serviceFlow.getRouteCode());

            return Result.error("服务流程变更增加成功！");
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e);
            return Result.error("服务流程变更增加失败！");
        }
    }

    /**
     * 当外界组件通知一个已有的RouteDefine路由定义被删除时，该事件被触发。
     */
    public Result onServiceFlowModifyReduce(String msg) {
        try {
            ServiceFlowValid serviceFlowValid = new ServiceFlowValid(msg).invoke();
            if (serviceFlowValid.is()) return Result.error("必要的入参数据不正确，请检查！");

            ServiceFlow serviceFlow = serviceFlowValid.getServiceFlow();
            ServiceFlow.HandleFile handleFile = serviceFlowValid.getHandleFile();
            String routeCode = serviceFlow.getRouteCode();
            SystemCamelContext.getContext().stopRoute(routeCode);
            SystemCamelContext.getContext().removeRoute(routeCode);
            this.deleteClassFile(handleFile);
            this.addRouter(serviceFlowValid.getHandleFiles());
            SystemCamelContext.startRouter(serviceFlow.getRouteCode());

            return Result.success("服务流程变更减少成功！");
        } catch (Exception e) {
            return Result.error("服务流程变更减少失败！");
        }
    }

    public Result onServiceFlowDelete(String msg) {
        try {
            ServiceFlowValid serviceFlowValid = new ServiceFlowValid(msg).invoke();
            if (serviceFlowValid.is()) return Result.error("必要的入参数据不正确，请检查！");

            ServiceFlow serviceFlow = serviceFlowValid.getServiceFlow();
            ArrayList<ServiceFlow.HandleFile> handleFiles = serviceFlowValid.getHandleFiles();
            if (handleFiles != null) {
                for (ServiceFlow.HandleFile handleFile : handleFiles) {
                    String routeCode = serviceFlow.getRouteCode();
                    SystemCamelContext.getContext().stopRoute(routeCode);
                    SystemCamelContext.getContext().removeRoute(routeCode);
                    this.deleteClassFile(handleFile);  //清理文件？
                    this.deleteServiceFlow(handleFile.getRouteCode());//删除mongo中serviceFlow信息
                    //TODO 删除mongodb中文件
                }
            }
            return Result.success("服务流程删除成功！");
        } catch (Exception e) {
            return Result.error("服务流程删除失败！");
        }
    }

    /**
     * 启动路由时，该事件被触发。
     */
    public Result onServiceFlowStart(String msg) {
        try {
            ServiceFlowValid serviceFlowValid = new ServiceFlowValid(msg).invoke();
            if (serviceFlowValid.is()) return Result.error("必要的入参数据不正确，请检查！");

            ServiceFlow serviceFlow = serviceFlowValid.getServiceFlow();
            String routeCode = serviceFlow.getRouteCode();
            ArrayList<ServiceFlow.HandleFile> handleFiles = serviceFlowValid.getHandleFiles();
            for (ServiceFlow.HandleFile handleFile : handleFiles) {
                boolean created;
                if ("java".equals(handleFile.getFileType())) {
                    created = this.generateClassFile(handleFile);
                } else {
                    created = this.createClassFile(handleFile);
                }
                if (!created) {
                    return Result.error("服务流程启动失败！");
                }
            }

            this.addRouter(handleFiles);

            SystemCamelContext.startRouter(serviceFlow.getRouteCode());

            return Result.success("服务流程启动成功！");
        } catch (Exception e) {
            logger.error(e.getMessage());
            return Result.error("服务流程启动失败！");
        }
    }

    /**
     * 停止路由时，该事件被触发。
     */
    public Result onServiceFlowStop(String msg) {
        try {
            ServiceFlowValid serviceFlowValid = new ServiceFlowValid(msg).invoke();
            if (serviceFlowValid.is()) return Result.error("必要的入参数据不正确，请检查！");

            ServiceFlow serviceFlow = serviceFlowValid.getServiceFlow();
            String routeCode = serviceFlow.getRouteCode();
            SystemCamelContext.getContext().stopRoute(routeCode);
            return Result.success("停止路由成功！");
        } catch (Exception e) {
            return Result.error("停止路由失败！");
        }
    }

    private boolean createClassFile(ServiceFlow.HandleFile handleFile) throws Exception {
        if (handleFile == null) {
            return false;
        }
        SystemCamelContext.putClassMapping(handleFile.getRouteCode(), handleFile.getPackageName(), handleFile.getUsage(), handleFile.getClassName());
        URL resource = SystemCamelContext.getResource(this);
        FileOutputStream outputStream = ClassFileUtil.createFile(resource, handleFile.getPackageName(), handleFile.getClassName(), ClassFileUtil.CLASS_FILE);

        String fileName = DES.decrypt(handleFile.getFilePath(), DES.COMMON_PASSWORD);
        MongoDatabase db = mongoConfig.mongoClient().getDatabase(dbName);

        boolean read = GridFSUtil.readFile(db, outputStream, fileName);
        if (!read) {
            logger.error("not mongo file, fileName:" + fileName);
        }

        return read;
    }

    private boolean generateClassFile(ServiceFlow.HandleFile handleFile) throws Exception {
        if (handleFile == null) {
            return false;
        }
        SystemCamelContext.putClassMapping(handleFile.getRouteCode(), handleFile.getPackageName(), handleFile.getUsage(), handleFile.getClassName());
        URL resource = SystemCamelContext.getResource(this);
//        FileOutputStream outputStream = ClassFileUtil.createFile(resource, handleFile.getPackageName(), handleFile.getClassName(), ClassFileUtil.JAVA_FILE);
//        String fileName = DES.decrypt(handleFile.getFilePath(), DES.COMMON_PASSWORD);
//        MongoDatabase db = mongoConfig.mongoClient().getDatabase(dbName);
//
//        boolean read = GridFSUtil.readFileContent(db, outputStream, fileName);
//        if (!read) {
//            logger.error("not mongo file, fileName:" + fileName);
//            return false;
//        }

        //从中心下载java文件
        String downUrl = centerUrl + "/tenant/down/" + handleFile.getFilePath();
        String sourcePath =  ClassFileUtil.downFile(downUrl, resource, handleFile.getPackageName(), handleFile.getClassName(), ClassFileUtil.JAVA_FILE);

        if (sourcePath == null) {
            return false;
        }

        logger.info(sourcePath);
        boolean succ = CamelCompiler.compile(sourcePath, resource.getPath());
        if (succ) {
            //编译成功后将class文件上传至mongodb；文件名为类名+ routeCode
            String packagePath = StringUtil.replaceStrAll(handleFile.getPackageName(), ".", "/");
            String classPath =  resource.getPath()+ packagePath + "/" + handleFile.getClassName() + ClassFileUtil.CLASS_FILE;
            GridFSUtil.uploadFile(classPath, handleFile.getClassName()+handleFile.getRouteCode() + ClassFileUtil.CLASS_FILE,null);
        }
        return succ;
    }

    private void deleteClassFile(ServiceFlow.HandleFile handleFile) {
//        String className = SystemCamelContext.getClassMapping(handleFile.getRouteCode(), handleFile.getClassName(), handleFile.getUsage());
//        if (StringUtil.isEmpty(className)) {
//            return;
//        }

        String packagePath = StringUtil.replaceStrAll(handleFile.getPackageName(), ".", "/");
        String classPath = ClassLoader.getSystemResource("").getPath() + "/" + packagePath + "/" + handleFile.getClassName() + ".class";
        String javaPath = ClassLoader.getSystemResource("").getPath() + "/" + packagePath + "/" + handleFile.getClassName() + ".java";

        ClassFileUtil.deleteClassfile(classPath);
        ClassFileUtil.deleteClassfile(javaPath);
        // 完成
        logger.info("===================" + handleFile.getPackageName() + CoreConstant.DOT + handleFile.getClassName() + ".class 删除过程结束");
    }

    /**
     * 删除 serviceFlow记录
     *
     * @param routeCode
     */
    private void deleteServiceFlow(String routeCode) {
        try {
            MongoDatabase db = mongoConfig.mongoClient().getDatabase(configuration);
            MongoCollection<Document> collection = db.getCollection(serviceFlow);
            DeleteResult result = collection.deleteMany(Filters.eq("routeCode", routeCode));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void addRouter(ArrayList<ServiceFlow.HandleFile> handleFiles) throws Exception {
        ArrayList<ServiceFlow.HandleFile> routerFiles = new ArrayList<>();
        handleFiles.forEach(handleFile -> {
            try {
                if (handleFile.getUsage().equals(ServiceFlowConstant.FLOW_TYPE_ROUTE)) {
                    String className = SystemCamelContext.getClassMapping(handleFile.getRouteCode(), handleFile.getClassName(), handleFile.getUsage());
                    SystemCamelContext.getQueue().put(className);
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
                e.printStackTrace();
            }
        });

        routerFiles.forEach(handleFile -> {
            try {

            } catch (Exception e) {
                logger.error(e.getMessage());
                e.printStackTrace();
            }
        });


    }

    private class ServiceFlowValid {
        private boolean myResult;
        private String msg;
        private ServiceFlow serviceFlow;
        private ServiceFlow.HandleFile handleFile;
        private ArrayList<ServiceFlow.HandleFile> handleFiles;

        public ServiceFlowValid(String msg) {
            this.msg = msg;
        }

        public ArrayList<ServiceFlow.HandleFile> getHandleFiles() {
            return handleFiles;
        }

        public ServiceFlow getServiceFlow() {
            return serviceFlow;
        }

        public ServiceFlow.HandleFile getHandleFile() {
            return handleFile;
        }

        public ServiceFlowValid invoke() throws IOException {
            serviceFlow = objectMapper.readValue(msg, ServiceFlow.class);
            handleFiles = serviceFlow.getHandleFiles();
            if (handleFiles == null || handleFiles.size() == 0) {
                logger.error("必要的入参数据不正确，请检查！");
                myResult = true;
                return this;
            }

            handleFile = handleFiles.get(0);

            if (handleFile == null || StringUtil.isEmpty(serviceFlow.getRouteCode()) || StringUtil.isEmpty(handleFile.getPackageName())
                    || StringUtil.isEmpty(handleFile.getPackageName()) || StringUtil.isEmpty(handleFile.getFilePath())) {
                logger.error("必要的入参数据不正确，请检查！");
                myResult = true;
                return this;
            }
            myResult = false;
            return this;
        }

        boolean is() {
            return myResult;
        }
    }
}
