package cn.edu.whu.boot.common.utils.xml;

import cn.edu.whu.boot.common.domain.CommonConstant;
import cn.edu.whu.boot.common.domain.ResultMessage;
import cn.edu.whu.boot.common.domain.StatusCode;
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 org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * 对前端传入的 Json 字符串中的 tables (参考 CommonConstant 类) 中配置内容进行核对，
 * 确保数据不出错之后再生成 XML 文件，但同时也要给提示哪里出错!
 */
public class CheckTableDataUtil{

    public static ResultMessage checkTables(JSONArray tables){
        if(!checkTableName(tables)){
            return ResultMessage.failed(StatusCode.FAILED_AND_SHOW.getStatus(), "表名存在相同");
        }
        if(!checkTablePrimaryKey(tables)){
            return ResultMessage.failed(StatusCode.FAILED_AND_SHOW.getStatus(), "表存在无主键");
        }
        if(!checkTableGeometry(tables)){
            return ResultMessage.failed(StatusCode.FAILED_AND_SHOW.getStatus(),
                                        "表存在多个空间字段,或无表存在空间字段");
        }
        if(!checkTableFieldsName(tables)){
            return ResultMessage.failed(StatusCode.FAILED_AND_SHOW.getStatus(),
                                        "表存在字段名为空，或字段名相同，或数据库中固有字段名" );
        }
//        if(!checkTableFieldType(tables)){
//            return ResultMessage.failed(StatusCode.FAILED_AND_SHOW.getStatus(), "表存在字段类型为空");
//        }
        if(!checkRootTableIdAndAssoTableParentId(tables)){
            return ResultMessage.failed(StatusCode.FAILED_AND_SHOW.getStatus(),
                                        "主键类型与关联表的外键字段类型不一致");
        }
        return  ResultMessage.success(null);
    }

    /**
     * 检查 tables 中是否存在表名相同的情况，注意是数据库中的表名，并非显示在前端的表名!!!
     */
    private static boolean checkTableName(JSONArray tables){
        Set<String> nameSet = new HashSet<>();
        for(Object obj : tables){
            JSONObject jsonObj = (JSONObject) obj;
            String name = jsonObj.get(CommonConstant.KEY).toString();
            if(StringUtils.isEmpty(name)){
                return false;
            }
            else{
                if(!nameSet.contains(name)){
                    nameSet.add(name);
                }
                else{
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 检查 tables 中每个表是否选择了主键，且主键是短整型且自增
     */
    private static boolean checkTablePrimaryKey(JSONArray tables){
        for(Object obj : tables){
            JSONObject tableObj = (JSONObject) obj;
            JSONArray fieldsArray = (JSONArray) tableObj.get(CommonConstant.TABLES_FIELDS);
            boolean hasPrimaryKey = false;
            for(Object fieldObj : fieldsArray){
                JSONObject jsonObj = (JSONObject) fieldObj;
                String identity = jsonObj.getString(CommonConstant.FIELD_IDENTITY);
                if(identity != null && "true".equals(identity)){
                    hasPrimaryKey = true;
                }
            }
            if(!hasPrimaryKey){
                return false;
            }
        }
        return true;
    }

    /**
     * 检查 tables 中每个表的 fields 中是否存在字段名为空或字段名，以及特殊字段名情况
     */
    private static boolean checkTableFieldsName(JSONArray tables){
        for(Object obj : tables){
            JSONObject tableObj = (JSONObject) obj;
            JSONArray fieldsArray = (JSONArray) tableObj.get(CommonConstant.TABLES_FIELDS);
            Set<String> nameSet = new HashSet<>();
            for(Object fieldObj : fieldsArray){
                JSONObject jsonObj = (JSONObject) fieldObj;
                String name = jsonObj.get(CommonConstant.KEY).toString();
                name = name == null ? "" : name.toLowerCase();
                if(CommonConstant.FIX_FIELD_SET.contains(name)){
                    return false;
                }
                else{
                    if(!nameSet.contains(name)){
                        nameSet.add(name);
                    }
                    else{
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 检查 tables 中至少存在一个表，该表中有一个空间对象字段!
     */
    private static boolean checkTableGeometry(JSONArray tables){
        boolean hasGeometryTable = false;
        for(Object obj : tables){
            int geometryCount = 0;
            JSONObject tableObj = (JSONObject) obj;
            JSONArray fieldsArray = (JSONArray) tableObj.get(CommonConstant.TABLES_FIELDS);
            for(Object fieldObj : fieldsArray){
                JSONObject fieldJsonObj = (JSONObject) fieldObj;
                if("true".equals(fieldJsonObj.getString(CommonConstant.GEOMETRY))){
                    geometryCount++;
                }
            }
            if(geometryCount > 1){
                return false;
            }
            else if(geometryCount == 1){
                hasGeometryTable = true;
            }
        }
        return hasGeometryTable;
    }

    /**
     * 从 JSONArray tables 中获取指定 String tableName 的 JSONObject tableObject
     */
    private static JSONObject getTableObjectFromJsonArray(JSONArray tables, String tableName){
        for(Object obj : tables){
            JSONObject tableObj = (JSONObject) obj;
            if(tableObj.getString(CommonConstant.KEY).equals(tableName)){
                return tableObj;
            }
        }
        return null;
    }

    /**
     * 从 JSONArray fields 中获取 identity = true 或者 parentID = true 字段的 dbType 对应的值!!!
     * 因此这里的 key 参数为 identity 或者 parentID
     */
    private static String getFieldDBTypeFromJsonArray(JSONArray fields, String key){
        for(Object fieldObj : fields){
            JSONObject fieldJsonObj = (JSONObject) fieldObj;
            if("true".equals(fieldJsonObj.getString(key))){
                return fieldJsonObj.getString(CommonConstant.FIELD_DB_TYPE).toLowerCase();
            }
        }
        return null;
    }

    /**
     * 检查 tables 中的表是否存在字段类型为空的情况
     */
    private static boolean checkTableFieldType(JSONArray tablesArray){
        for (Object obj : tablesArray) {
            JSONObject tableObj = (JSONObject) obj;
            JSONArray fieldsArray = (JSONArray) tableObj.get(CommonConstant.TABLES_FIELDS);

            for (Object fieldObj : fieldsArray) {
                JSONObject fieldJsonObj = (JSONObject) fieldObj;

                String controlType = fieldJsonObj.getString(CommonConstant.FIELD_TYPE);
                // 跳过不需要入库的字段类型
                if ("camera".equals(controlType) || "divider".equals(controlType)) {
                    continue;
                }

                // 如果字段是 collection 或 associate 字段，跳过类型检查
                String collection = fieldJsonObj.getString(CommonConstant.FIELD_COLLECTION);
                String associate = fieldJsonObj.getString(CommonConstant.FIELD_ASSOCIATE);
                if ((collection != null && !collection.trim().isEmpty()) ||
                        (associate != null && !associate.trim().isEmpty())) {
                    continue;
                }

                // dbType 为空时，说明是无效字段
                String fieldType = fieldJsonObj.getString(CommonConstant.FIELD_DB_TYPE);
                if (fieldType == null || fieldType.trim().isEmpty()) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 检查根表的主键 id 数据库类型与关联表的 parentId 为 true 的字段数据库类型是否一致
     */
    private static boolean checkRootTableIdAndAssoTableParentId(JSONArray tables){
        // 若某根表存在关联表，则放在该Map中，key表示根表名，value表示关联表名
        Map<String, List<String>> rootTableHasAssoTableMap = new HashMap<>();

        for(Object obj : tables){
            String hasGeometryTableName = null;
            List<String> assoAndCollTableNameList = new ArrayList<>();
            JSONObject tableObj = (JSONObject) obj;
            JSONArray fieldsArray = (JSONArray) tableObj.get(CommonConstant.TABLES_FIELDS);
            for(Object fieldObj : fieldsArray){
                JSONObject fieldJsonObj = (JSONObject) fieldObj;
                if("true".equals(fieldJsonObj.getString(CommonConstant.GEOMETRY))){
                    hasGeometryTableName = tableObj.getString(CommonConstant.KEY);
                }
                if(!"".equals(fieldJsonObj.getString(CommonConstant.FIELD_COLLECTION))){
                    assoAndCollTableNameList.add(fieldJsonObj.getString(CommonConstant.FIELD_COLLECTION));
                }
                if(!"".equals(fieldJsonObj.getString(CommonConstant.FIELD_ASSOCIATE))){
                    assoAndCollTableNameList.add(fieldJsonObj.getString(CommonConstant.FIELD_ASSOCIATE));
                }
            }
            if(hasGeometryTableName != null && assoAndCollTableNameList.size() != 0){
                rootTableHasAssoTableMap.put(hasGeometryTableName, assoAndCollTableNameList);
            }
        }
        for(Map.Entry<String, List<String>> entry : rootTableHasAssoTableMap.entrySet()){
            JSONObject rootTableObject = getTableObjectFromJsonArray(tables, entry.getKey());
            assert rootTableObject != null;
            JSONArray fieldsArray = (JSONArray) rootTableObject.get(CommonConstant.TABLES_FIELDS);
            String rootTableIdDBType = getFieldDBTypeFromJsonArray(fieldsArray, CommonConstant.FIELD_IDENTITY);
            for(String assoAndCollTableName : entry.getValue()){
                JSONObject assoAndCollTableObject = getTableObjectFromJsonArray(tables, assoAndCollTableName);
                assert assoAndCollTableObject != null;
                JSONArray assoAndCollTablefieldsArray = (JSONArray) assoAndCollTableObject.get(CommonConstant.TABLES_FIELDS);
                String assoAndCollTableParentDBType = getFieldDBTypeFromJsonArray(assoAndCollTablefieldsArray, CommonConstant.FIELD_PARENT_ID);
                if(!rootTableIdDBType.equals(assoAndCollTableParentDBType)){
                    return false;
                }
            }
        }
        return true;
    }


    /**
     * 检查Table中是否有Table中是否具有关联表包括associate和collection,另外提供单独获取associate或collection的方法
     *
     * @param tableName 表名
     * @param configString config的字符串，便于后面获取关联表Table
     * @return 结果Map<String   ,   String>中key为关联表名即associate或collection值， value是该关联表
     * 的parentId为true的字段注意区别上面的hasAssociateField和hasCollectionField方法
     */
    public static Map<String, String> getAssoAndCollField(String tableName, String configString){
        Map<String, String> result = new HashMap<>();
        Table table = JSON.toJavaObject(JSON.parseObject(configString), Config.class).getTables().get(tableName);
        Map<String, String> associateResult = getAssociateField(table, configString);
        Map<String, String> collectionResult = getCollectionField(table, configString);
        result.putAll(associateResult);
        result.putAll(collectionResult);
        return result;
    }

    public static Map<String, String> getAssociateField(Table table, String configString){
        Map<String, String> result = new HashMap<>();
        Map<String, Field> fieldsMap = table.getFields();
        JSONObject configJsonObject = JSON.parseObject(configString);
        Config config = JSON.toJavaObject(configJsonObject, Config.class);
        Map<String, Table> tables = config.getTables();
        for(Map.Entry<String, Field> entry : fieldsMap.entrySet()){
            if(entry.getValue().getAssociate() != null && !entry.getValue().getAssociate().equals("")){
                Table assoAndCollTable = tables.get(entry.getKey());
                Map<String, Field> assoAndCollFields = assoAndCollTable.getFields();
                for(Map.Entry<String, Field> assoAndCollEntry : assoAndCollFields.entrySet()){
                    if(assoAndCollEntry.getValue().isParentID()){
                        result.put(entry.getKey(), assoAndCollEntry.getKey());
                    }
                }
            }
        }
        return result;
    }

    public static Map<String, String> getCollectionField(Table table, String configString){
        Map<String, String> result = new HashMap<>();
        Map<String, Field> fieldsMap = table.getFields();
        JSONObject configJsonObject = JSON.parseObject(configString);
        Config config = JSON.toJavaObject(configJsonObject, Config.class);
        Map<String, Table> tables = config.getTables();
        for(Map.Entry<String, Field> entry : fieldsMap.entrySet()){
            if(entry.getValue().getCollection() != null && !entry.getValue().getCollection().equals("")){
                Table assoAndCollTable = tables.get(entry.getKey());
                Map<String, Field> assoAndCollFields = assoAndCollTable.getFields();
                for(Map.Entry<String, Field> assoAndCollEntry : assoAndCollFields.entrySet()){
                    if(assoAndCollEntry.getValue().isParentID()){
                        result.put(entry.getKey(), assoAndCollEntry.getKey());
                    }
                }
            }
        }
        return result;
    }





    /**
     * 获取一个table的geometry为true的字段的字段名
     */
    public static String getGeometryFieldName(Table table){
        Map<String, Field> fields = table.getFields();
        for(Map.Entry<String, Field> fieldEntry : fields.entrySet()){
            if(fieldEntry.getValue().isGeometry()){
                return fieldEntry.getValue().getKey();
            }
        }
        return null;
    }

    /**
     * 获取一个table中所有数据库类型为字符串类型的字段名，根据网页端xml设计的原则，即dbType为text
     */
    public static List<String> getTextFieldName(Table table){
        List<String> result = new ArrayList<>();
        Map<String, Field> fields = table.getFields();
        fields.forEach((key, value) -> {
            if(value.getDbType() != null && value.getDbType().toLowerCase().equals(CommonConstant.TEXT)){
                result.add(key);
            }
        });
        return result;
    }
}
