package cn.edu.whu.boot.xml.controller;

import cn.edu.whu.boot.allocation.entities.TaskUserRole;
import cn.edu.whu.boot.allocation.services.TaskUserRoleService;
import cn.edu.whu.boot.collection.service.IBaseService;
import cn.edu.whu.boot.common.domain.CommonConstant;
import cn.edu.whu.boot.common.domain.ResultMessage;
import cn.edu.whu.boot.common.utils.collection.EncryptUtils;
import cn.edu.whu.boot.common.utils.collection.PostgreSqlUtil;
import cn.edu.whu.boot.common.utils.xml.CheckTableDataUtil;
import cn.edu.whu.boot.common.utils.xml.JsonToElementUtil;
import cn.edu.whu.boot.common.utils.xml.XmlFileToJsonUtil;
import cn.edu.whu.boot.user.entities.User;
import cn.edu.whu.boot.user.services.UserService;
import cn.edu.whu.boot.xml.domain.Task;
import cn.edu.whu.boot.xml.domain.XmlEnity;
import cn.edu.whu.boot.xml.service.ITaskService;
import cn.edu.whu.boot.xml.service.IXmlEnityService;
import cn.edu.whu.boot.xml.xmlreader.XmlTemplateReader;
import cn.edu.whu.boot.xml.xmlreader.bean.Config;
import cn.edu.whu.boot.xml.xmlreader.bean.Field;
import cn.edu.whu.boot.xml.xmlreader.bean.Table;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.jdom.Document;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 对应网页端任务管理模块!!!
 * 本系统可以定制任务，所以每个任务是独立的，互相之间不会影响!!!
 */
@RestController
@RequestMapping("task")
public class TaskManagementController{

    private final ITaskService taskService;
    private final IXmlEnityService xmlEnityService;
    private final UserService userService;
    private final TaskUserRoleService taskUserRoleService;
    private final IBaseService baseService;

    /**
     * XML 文件的存储路径
     */
    @Value("${XmlFilePath}")
    private String xmlFilePath;

    @Autowired
    public TaskManagementController(ITaskService taskService, IXmlEnityService xmlEnityService, UserService userService,
                                    TaskUserRoleService taskUserRoleService, IBaseService baseService){
        this.taskService = taskService;
        this.xmlEnityService = xmlEnityService;
        this.userService = userService;
        this.taskUserRoleService = taskUserRoleService;
        this.baseService = baseService;
    }

    /**
     * 根据前端配置的内容，创建 Task!!!
     * String configString 解释参考 CommonConstant 类
     */
    @RequestMapping(value = "createTask", produces = "text/html;charset=UTF-8")
    public String createTask(String configString){
        JSONObject obj = JSON.parseObject(configString);
        String databaseName = obj.getString(CommonConstant.KEY);
        if(taskService.hasDatabaseName(databaseName.trim())){
            return ResultMessage.failed("数据库名已经存在，请更换!").toString();
        }
        String taskName = obj.getString(CommonConstant.NAME);
        String xmlConfigJsonString = obj.getString(CommonConstant.TASK_XML_CONFIG);
        String introduction = obj.getString(CommonConstant.TASK_INTRODUCTION);
        String uploadDeadLine = obj.getString(CommonConstant.TASK_UPLOADTIME);
        Integer userId = obj.getInteger(CommonConstant.TASK_USER_ID);
        Integer isAllocationOptional = obj.getInteger(CommonConstant.TASK_IS_ALLOCATION_OPTIONAL) == null ? 0 : obj.getInteger(CommonConstant.TASK_IS_ALLOCATION_OPTIONAL);
        assert userService.findById(userId).isPresent();
        User user = userService.findById(userId).get();

        // 将 xmlConfigJsonString 转换成 Document 对象
        ResultMessage genDocumentResultMessage = JsonToElementUtil.createTaskgenerateDocument(xmlConfigJsonString);
        if(!genDocumentResultMessage.isSuccess()){
            return genDocumentResultMessage.toString();
        }

        XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
        out.setFormat(out.getFormat().setEncoding("UTF-8"));
        SimpleDateFormat df = new SimpleDateFormat(CommonConstant.FILE_DATE_FORMAT_STRING);
        Date currentDate = new Date();

        // 将 Document 中内容写入到 XML 文件中
        String filePath = xmlFilePath + "/" + CommonConstant.XML_FILENAME_PREFIX + df.format(currentDate) + ".xml";
        File file = new File(filePath);
        Document document = (Document) genDocumentResultMessage.getData();
        try(FileOutputStream fileOutputStream = new FileOutputStream(file)){
            out.output(document, fileOutputStream);
        } catch(Exception e){
            e.printStackTrace();
        }

        // 创建 XmlEnity 对象
        XmlEnity xmlEnity = new XmlEnity();
        xmlEnity.setFileName(CommonConstant.XML_FILENAME_PREFIX + df.format(currentDate));
        xmlEnity.setFilePath(filePath);
        xmlEnity.setCreateTime(currentDate);
        try{
            xmlEnity.setMd5Code(EncryptUtils.getMd5ByFile(file));
        } catch(Exception e){
            e.printStackTrace();
        }
        xmlEnity.setModifyTime(currentDate);

        // 创建 Task 对象
        Task task = new Task();
        task.setTaskName(taskName);
        task.setOperator(user.getUsername());
        task.setCreateTime(new Date());
        task.setModifyTime(new Date());
        task.setDatabaseName(databaseName);
        task.setIntroduction(introduction);
        task.setIsAllocationOptional(isAllocationOptional);
        task.setIsReviewerRestricted(1);
        task.setUploadDeadline(uploadDeadLine);
        Config config = XmlTemplateReader.read(xmlEnity.getFilePath());
        config = appendFieldToTable(config);
        task.setConfig(JSON.toJSONString(config, SerializerFeature.DisableCircularReferenceDetect));
        taskService.insertTaskAndXmlEnity(task, xmlEnity);
        return ResultMessage.success(null).toString();
    }

    /**
     * 基于前端配置的内容和上传的 XML 文件创建 Task !
     * 相当于不用一点点配置，直接用 XML 文件来创建任务
     */
    @RequestMapping(value = "createTaskByXmlFile", produces = "text/html;charset=UTF-8")
    public String createTaskByXmlFile(String configString, @RequestParam(value = "file") MultipartFile file) throws Exception{
        JSONObject obj = JSON.parseObject(configString);
        String taskName = obj.getString(CommonConstant.NAME);
        String databaseName = obj.getString(CommonConstant.KEY);
        if(taskService.hasDatabaseName(databaseName)){
            return ResultMessage.failed("数据库名已经存在，请更换!").toString();
        }
        String introduction = obj.getString(CommonConstant.TASK_INTRODUCTION);
        String uploadDeadLine = obj.getString(CommonConstant.TASK_UPLOADTIME);
        Integer userId = obj.getInteger(CommonConstant.TASK_USER_ID);
        assert userService.findById(userId).isPresent();
        User user = userService.findById(userId).get();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
        Date currentDate = new Date();
        String filePath = xmlFilePath + "/" + CommonConstant.XML_FILENAME_PREFIX + df.format(currentDate) + ".xml";
        File xmlFile = new File(filePath);
        file.transferTo(xmlFile);

        // 获取xml文件中的objectTables然后进行检查
        String tablesString = XmlFileToJsonUtil.xmlReader(filePath);
        JSONObject tableObj = JSON.parseObject(tablesString);
        JSONArray objectTables = (JSONArray) tableObj.get(CommonConstant.TAG_TABLES);
        // 对传来的 objectTables 进行多项数据核对，确保数据不出错之后再生成xml文件，但同时也要给提示哪里出错
        ResultMessage checkMessage = CheckTableDataUtil.checkTables(objectTables);
        if(!checkMessage.isSuccess()){
            return checkMessage.toString();
        }

        // 将xml文件保存在数据库的xml表中
        XmlEnity xmlEnity = new XmlEnity();
        xmlEnity.setFileName(CommonConstant.XML_FILENAME_PREFIX + df.format(currentDate));
        xmlEnity.setFilePath(filePath);
        xmlEnity.setCreateTime(currentDate);
        xmlEnity.setMd5Code(EncryptUtils.getMd5ByFile(new File(filePath)));
        xmlEnity.setModifyTime(currentDate);

        Task task = new Task();
        task.setTaskName(taskName);
        task.setOperator(user.getUsername());
        task.setCreateTime(new Date());
        task.setModifyTime(new Date());
        task.setDatabaseName(databaseName);
        task.setXmlId(xmlEnity.getId());
        task.setIntroduction(introduction);
        task.setIsAllocationOptional(obj.getInteger(CommonConstant.TASK_IS_ALLOCATION_OPTIONAL) == null ? 0 : obj.getInteger(CommonConstant.TASK_IS_ALLOCATION_OPTIONAL));
        task.setUploadDeadline(uploadDeadLine);
        task.setIsReviewerRestricted(1);
        Config config = XmlTemplateReader.read(xmlEnity.getFilePath());
        config = appendFieldToTable(config);
        task.setConfig(JSON.toJSONString(config, SerializerFeature.DisableCircularReferenceDetect));
        taskService.insertTaskAndXmlEnity(task, xmlEnity);
        return ResultMessage.success(null).toString();
    }

    /**
     * 在任务没有创建数据库之前，可以对配置内容进行更改!!!
     */
    @RequestMapping(value = "updateTask", method = RequestMethod.POST)
    public String updateTask(Integer id, String configString){
        Task task = taskService.getTaskById(id);
        if(task == null || task.getHasCreateTables() == Task.HAS_CREATE_TABLES){
            return ResultMessage.failed( "该任务已经创建数据库!!!").toString();
        }
        task.setModifyTime(new Date());
        ResultMessage genDocumentResultMessage = JsonToElementUtil.generateDocument(configString);
        if(!genDocumentResultMessage.isSuccess()){
            return genDocumentResultMessage.toString();
        }
        XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
        out.setFormat(out.getFormat().setEncoding("UTF-8"));
        Integer xmlId = taskService.getXmlId(id);
        XmlEnity xmlEnity = xmlEnityService.getXmlEnityById(xmlId);
        String filePath = xmlEnity.getFilePath();
        File file = new File(filePath);
        Document document = (Document) genDocumentResultMessage.getData();
        try(FileOutputStream fileOutputStream = new FileOutputStream(file)){
            out.output(document, fileOutputStream);
        } catch(Exception e){
            e.printStackTrace();
        }

        // 将更新的xml文件在数据库的xml表中更新
        Date currentDate = new Date();
        try{
            xmlEnity.setMd5Code(EncryptUtils.getMd5ByFile(file));
        } catch(Exception e){
            e.printStackTrace();
        }
        xmlEnity.setModifyTime(currentDate);

        Config config = XmlTemplateReader.read(xmlEnity.getFilePath());
        config = appendFieldToTable(config);
        task.setConfig(JSON.toJSONString(config, SerializerFeature.DisableCircularReferenceDetect));
        taskService.updateTaskAndXmlEnity(task, xmlEnity);
        return ResultMessage.success(null).toString();
    }

    /**
     * 当 Task 创建了数据库之后，检查 Task 对应的 Xml 文件是否发生更改!!!
     */
    @RequestMapping(value = "checkTaskXmlMD5Code", method = RequestMethod.POST)
    public String checkTaskXmlMD5Code(Integer id){
        Task task = taskService.getTaskById(id);
        // 先检查该 Task 是否已经创建数据库
        if(task.getHasCreateTables() == 0){
            return ResultMessage.failed("未创建数据库").toString();
        }
        else{
            Integer xmlId = taskService.getXmlId(id);
            XmlEnity xmlEnity = xmlEnityService.getXmlEnityById(xmlId);
            // 再检查创建数据库时的 MD5 码与XML配置文件的MD5码是否一致
            if(task.getCreateTablesMd5code().equals(xmlEnity.getMd5Code())){
                return ResultMessage.failed("").toString();
            }
            else{
                String message = MessageFormat.format("{0} 任务的配置文件发生改动，请重新创建数据库!",
                                                      task.getTaskName());
                return ResultMessage.success(message).toString();
            }
        }
    }

    /**
     * 删除任务
     */
    @RequestMapping(value = "deleteTask", produces = "text/html;charset=UTF-8")
    public String deleteTask(Integer id){
        Task task = taskService.getTaskById(id);
        XmlEnity xmlEnity = xmlEnityService.getXmlEnityById(task.getXmlId());
        // 如果已经创建数据库，先将Task任务对应的数据库名(即schema名)更改
        if(task.getHasCreateTables() == Task.HAS_CREATE_TABLES){
            String oldSchemaName = task.getDatabaseName();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            String newSchemaName = "delete_" + oldSchemaName + "_" + simpleDateFormat.format(task.getCreateTime());
            String alterSchemaNameSql = MessageFormat.format("ALTER SCHEMA {0} RENAME TO {1}",
                                                             PostgreSqlUtil.transformString(oldSchemaName),
                                                             PostgreSqlUtil.transformString(newSchemaName));
            baseService.updateSqlString(alterSchemaNameSql);
        }
        // 把任务对应的xml文件也删除掉
        File file = new File(xmlEnity.getFilePath());
        if(file.exists()){
            file.delete();
        }
        taskService.deleteTaskAndXmlEnity(task);
        return ResultMessage.success(null).toString();
    }

    /**
     * 安卓端传入 userId，查询 userId 所在的所有任务
     */
    @RequestMapping(value = "selectTaskForUserId", produces = "text/html;charset=UTF-8")
    public String selectAllTaskForUserId(Integer userId){
        List<Task> result = new ArrayList<>();
        // 先获取不需要分配的任务
        List<Task> allTasks = taskService.getAllTask();
        allTasks.forEach(task -> {
            if(task.getIsAllocationOptional() == Task.WITHOUT_ALLOCATION){
                result.add(task);
            }
        });
        // 再获取需要分配，并且用户在该任务中的任务
        List<TaskUserRole> taskUserRoles = taskUserRoleService.findByUserId(userId);
        if(taskUserRoles != null && taskUserRoles.size() != 0){
            for(TaskUserRole taskUserRole : taskUserRoles){
                result.add(taskService.getTaskById(taskUserRole.getTaskId()));
            }

        }
        // 生成移动端需要的返回结果
        if(result.size() != 0){
            return ResultMessage.success(getTaskInfo(result)).toString();
        }
        else{
            return ResultMessage.success(null).toString();
        }
    }

    /**
     * 网页端任务管理模块获取任务相关信息，有任务名称、数据库名、创建时间、修改时间和操作人员
     */
    @RequestMapping(value = "selectAll", produces = "text/html;charset=UTF-8")
    public String selectAllTaskInfo(){
        List<Task> taskList = taskService.getAllTask();
        String result = getTaskInfo(taskList);
        return ResultMessage.success(result).toString();
    }

    /**
     * 网页端决策管理模块获取任务相关信息，有任务名称、数据库名、任务下所有根表名
     */
    @RequestMapping(value = "selectAllWithTableName", produces = "text/html;charset=UTF-8")
    public String selectAllTaskInfoWithTableName(){
        List<Task> taskList = taskService.getAllTask();
        List<Map<String, Object>> resultList = new ArrayList<>();

        for(Task task : taskList){
            List<Map<String, String>> tableKeyAndNameList = new ArrayList<>();
            Map<String, Table> rootTables = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getRootTables();
            rootTables.forEach((key, value) -> {
                Map<String, String> tableKeyAndNameMap = new HashMap<>();
                tableKeyAndNameMap.put("tableKey", key);
                tableKeyAndNameMap.put("tableName", value.getName());
                tableKeyAndNameList.add(tableKeyAndNameMap);
            });
            Map<String, Object> taskMap = new HashMap<>();
            taskMap.put("id", task.getId());
            taskMap.put("taskName", task.getTaskName());
            taskMap.put("databaseName", task.getDatabaseName());
            taskMap.put("tableNames", tableKeyAndNameList);
            resultList.add(taskMap);
        }
        return ResultMessage.success(resultList).toString();
    }

    /**
     * 网页端决策管理模块获取已经创建数据库的任务
     */
    @RequestMapping(value = "selectTaskHasCreated", produces = "text/html;charset=UTF-8")
    public String selectTaskHasCreateTable(){
        List<Task> taskList = taskService.getTaskHasCreateTable();
        SimpleDateFormat df = new SimpleDateFormat(CommonConstant.DATE_FORMAT_STRING);
        List<Map<String,Object>> list = new ArrayList<>();
        for(Task task : taskList){
            Map<String,Object> map = new HashMap<>();
            List<TaskUserRole> taskUserRoleList = taskUserRoleService.findByTaskId(task.getId());
            String allocated = "false";
            if(taskUserRoleList != null && taskUserRoleList.size() != 0){
                allocated = "true";
            }
            // 获取 Task 任务下根表中数据，如果任务中有数据则为核查任务，任务中没有数据为采集任务
            Long dataCount = 0L;
            Set<String> tableNameSet = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getRootTables().keySet();
            for(String tableName : tableNameSet){
                dataCount += baseService.getDataCount(
                        PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), tableName,
                                                            false, false));
            }
            map.put("id", task.getId());
            map.put("taskName", task.getTaskName());
            map.put("databaseName", task.getDatabaseName());
            map.put("operator", task.getOperator());
            map.put("createTime", df.format(task.getCreateTime()));
            map.put("allocated", allocated);
            map.put("introduction",  task.getIntroduction());
            map.put("taskType", dataCount == 0 ? 0 : 1);
            map.put("modifyTime", df.format(task.getModifyTime()));
            map.put("isAllocationOptional", task.getIsAllocationOptional());
            map.put("isReviewerRestricted", task.getIsReviewerRestricted());
            list.add(map);
        }
        Map<String,Object> result = new HashMap<>();
        result.put("data", list);
        return new ResultMessage<>(true, "获取已经创建数据库的任务数据成功", JSON.toJSONString(result)).toString();
    }

    private String getTaskInfo(List<Task> tasks){
        SimpleDateFormat df = new SimpleDateFormat(CommonConstant.DATE_FORMAT_STRING);
        List<Map<String,Object>> list = new ArrayList<>();
        for(Task task : tasks){
            Map<String,Object> map = new HashMap<>();
            map.put("id", task.getId());
            map.put("taskName", task.getTaskName());
            map.put("databaseName", task.getDatabaseName());
            map.put("operator", task.getOperator());
            map.put("createTime", df.format(task.getCreateTime()));
            map.put("hasCreateTables", task.getHasCreateTables());
            map.put("introduction",  task.getIntroduction());
            map.put("isAllocationOptional", task.getIsAllocationOptional());
            map.put("isReviewerRestricted", task.getIsReviewerRestricted());
            map.put("modifyTime", df.format(task.getModifyTime()));
            list.add(map);
        }
        Map<String,Object> result = new HashMap<>();
        result.put("data", list);
        return JSON.toJSONString(result);
    }

    /**
     * 向 Config 的 Table 中追加固定字段，若为根表增加 collector、reviewer、create_time、update_time、
     * status、reason、version字段，非根表增加 version 字段!
     * 具体含义参考 CommonConstant 类
     */
    private Config appendFieldToTable(Config config){
        if(config != null){
            Map<String, Table> tables = config.getTables();
            for(Map.Entry<String, Table> tableEntry : tables.entrySet()){
                Map<String, Field> fields = tableEntry.getValue().getFields();
                if(tableEntry.getValue().isRoot()){
                    Field collectorField = new Field();
                    collectorField.setKey(CommonConstant.FIX_FIELD_COLLECTOR);
                    collectorField.setDbType(CommonConstant.INTEGER);
                    fields.put(CommonConstant.FIX_FIELD_COLLECTOR, collectorField);

                    Field reviewerField = new Field();
                    reviewerField.setKey(CommonConstant.FIX_FIELD_REVIEWER);
                    reviewerField.setDbType(CommonConstant.INTEGER);
                    fields.put(CommonConstant.FIX_FIELD_REVIEWER, reviewerField);

                    Field createTimeField = new Field();
                    createTimeField.setKey(CommonConstant.FIX_FIELD_CREATE_TIME);
                    createTimeField.setDbType(CommonConstant.DATE);
                    fields.put(CommonConstant.FIX_FIELD_CREATE_TIME, createTimeField);

                    Field updateTimeField = new Field();
                    updateTimeField.setKey(CommonConstant.FIX_FIELD_UPDATE_TIME);
                    updateTimeField.setDbType(CommonConstant.DATE);
                    fields.put(CommonConstant.FIX_FIELD_UPDATE_TIME, updateTimeField);

                    Field statusField = new Field();
                    statusField.setKey(CommonConstant.FIX_FIELD_STATUS);
                    statusField.setDbType(CommonConstant.INTEGER);
                    fields.put(CommonConstant.FIX_FIELD_STATUS, statusField);

                    Field reasonField = new Field();
                    reasonField.setKey(CommonConstant.FIX_FIELD_REASON);
                    reasonField.setDbType(CommonConstant.STRING);
                    fields.put(CommonConstant.FIX_FIELD_REASON, reasonField);
                }
                Field versionField = new Field();
                versionField.setKey(CommonConstant.FIX_FIELD_VERSION);
                versionField.setDbType(CommonConstant.INTEGER);
                fields.put(CommonConstant.FIX_FIELD_VERSION, versionField);
                tableEntry.getValue().setFields(fields);
            }
        }
        return config;
    }

    @RequestMapping("/setReviewerMode")
    public String setIsReviewerUnrestricted(@RequestParam int taskId, @RequestParam int isReviewerRestricted) {
        taskService.setReviewerMode(taskId, isReviewerRestricted);
        return new ResultMessage<>(true, "设置成功").toString();
    }
}
