/**
 * 配置文件校验规则
 */
const glob = require('glob');
const os = require('os')
const { MySQLDataType, MongodbDataType, requireFields, filterType, systemField, systemForeign } = require('../config/dataType.js');
const { systemTable } = require('../config/common')
const { getModelFilters, toHump, deepAssign } = require('./helper');
const chalk = require('chalk');

function validatorConf(root, model = '', arg) {
    console.log(chalk.yellow(`Please wait while verifying the configuration file...`))
    // 配置文件是否成功通过校验
    let result = true
    const modelConfigRoot = `${root}/modelConf`;
    const absoluteRoot = os.platform() === 'win32' ? modelConfigRoot.replace(/\\/ig, '/') : modelConfigRoot;
    const modelFiles = model == '' ? '*_model.js' : `${model}_model.js`
    const models = glob.sync(`${modelConfigRoot}/**/${modelFiles}`); // 模型配置文件
    const allModels = glob.sync(`${modelConfigRoot}/**/*_model.js`);
    // 模型名称数组
    const modelNames = allModels.map(f => {
        return f.replace(absoluteRoot, '').replace('/', '').replace(/\_|model/g, '').replace('.js', '').toLowerCase();
    })

    // 模型配置文件校验
    if (models.length == 0) {
        result = false
        throw Error(`\n 没有在${modelConfigRoot}中找到${model == '' ? '任何' : model}模型配置文件！\n 
    No model configuration files were found in ${modelConfigRoot}\n`);
    }

    /**
     *  主键合法性校验 模型名称，模型字段，数据类型，多条件筛选
     *  模型默认值
     */
    models.forEach(async (f) => {
        const model = require(f);
        const filters = getModelFilters(model)
        const fields = model.fields;
        const fieldsArr = Object.keys(fields)
        const foreign = model.foreign
        const uniques = model.uniques
        const extendActions = model.extendActions


        // 去掉控制器绝对路径部分，去掉控制器_model后缀，生成一个模型名称
        const modelName = f.replace(absoluteRoot, '').replace('/', '').replace(/\_|model/g, '').replace('.js', '').toLowerCase();

        // 验证模型名称
        if (systemField.includes(modelName)) {
            throw Error(`模型【${modelName}】错误：【${modelName}】模型名称与系统标识符${JSON.stringify(systemField)}冲突`)
        }
        // 检查是否有不能填写的字段
        Object.keys(fields).forEach(item => {
            if (systemField.includes(item)) {
                result = false
                throw Error(`模型【${modelName}】错误：模型 出现了不允许定义的字段 [${item}]`);
            }
            // 字段必须使用下划线命名法
            if (item.match(/^.*[A-Z]+.*$/)) {
                throw Error(`模型【${modelName}】错误：模型中的字段必须采用下划线命名，请检查字段${item}`)
            }
        });

        // 自动插入用户标识必须设置 userMap属性
        if (model.createInsertUser) {
            console.log(chalk.blue(`模型${modelName}配置了createInsertUser属性将在创建的时候自动向用户表插入一条记录`))
            if (!model.createInsertUser.userMap) {
                throw Error(`模型【${modelName}】错误：配置了 createInsertUser 属性的模型必须添加createInsertUser.userMap属性`)
            }
            if (!model.fields.uid) {
                throw Error(`模型【${modelName}】错误：配置了 createInsertUser 属性的模型必须在fields里面添加uid字段`) 
            }
        }

        
        // 自动插入用户自动模型必须设置userId,userName,userAvatar字段
        if (!model.foreign) model.foreign = {}
        if (model.autoInsertUser) {
            fields['userid'] = {
                type: 'integer',
                comment: '用户id',
                hidden: true,
                meta: {
                    formType: 'string',
                }
            }
            fields['username'] = {
                type: 'string',
                comment: '用户名',
                hidden: true,
                meta: {
                    formType: 'string',
                }
            }
            fields['useravatar'] = {
                type: 'string',
                comment: '用户头像',
                hidden: true,
                meta: {
                    formType: 'string',
                }
            }

        }
        // 自动插入时间必须设置 cd和md
        if (model.autoInsertDate) {
            fields['cd'] = {
                type: 'string',
                comment: '创建时间',
                hidden: true,
                meta: {
                    formType: 'string',
                }
            }
            fields['md'] = {
                type: 'string',
                comment: '更新时间',
                hidden: true,
                meta: {
                    formType: 'string',
                }
            }
        }

        
        // 验证唯一性字段是否在 extendActions.show.by 数组里面
        if (uniques) {
            if (!extendActions) {
                throw Error(`模型【${modelName}】错误：模型【${model}】要构建唯一性验证，请先配置extendActions.show.by`)
            }
            const showFields = extendActions['show']['by']
            for (const uniKey in uniques) {
                const item = uniques[uniKey]
                if (!showFields.includes(item.fields)) {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】uniques里面的fields属性必须是extendActions.show.by里面存在的`)
                }
            }

        }

        if (model.primaryKey && model.primaryKey != 'id') {
            if (!Object.keys(fields).includes(model.primaryKey)) {
                throw Error(`模型【${modelName}】错误：定义【${modelName}】的主键 primaryKey属性时，需要将主键值定义在【fields】属性里面`)
            }
        }
        if (!model.modelCn) {
            result = false
            throw Error(`模型【${modelName}】错误：你必须为【${modelName}】模型指定modelCn（模型中文）属性！`);
        }
        const dataType = arg.databaseType == 'mysql' ? MySQLDataType : MongodbDataType
        for (item in fields) {
            if (item.match(/_id/g) && fields[item].type!='integer') {
                console.log(chalk.yellow(`模型【${modelName}】提示：**warning**检测到你【${modelName} 】模型的字段${item}包含了id，该字段最好设置为integer类型**warning**`))
            }
            // 字段名与模型重名校验
            if (item == modelName) {
                throw Error(`模型【${modelName}】错误：模型【${modelName}】字段属性【${item}】重名`)
            }
            // 必填字段校验
            requireFields.some((ite) => {
                if (!fields[item][ite]) throw Error(`模型【${modelName}】错误：模型【${modelName}】字段属性【${item}.${ite}】是必填项`);
            })
            // 数据类型校验
            const fieldType = fields[item].type
            if (!dataType.includes(fieldType)) throw Error(`模型【${modelName}】错误：【 ${modelName} 】模型 数据类型【${fieldType}】不合法`);

            // 多选类型字段校验
            if (fields[item].meta && fields[item].meta.isMany) {
                if (fieldType != 'text') {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的多选字段${item}的字段类型【type】必须为 text`)
                }
                if (fields[item].meta.formType != 'select') {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的多选字段${item}的表单类型【meta.formType】必须为 select`)
                }
                if (!fields[item].meta.chineseMap) {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的多选字段${item}的必须指定 【meta.chineseMap】属性`)
                }
            }
            // select 下拉框要验证是否添加了chineseMap属性
            if (fields[item].meta && fields[item].meta.formType == 'select') {

                // if (!fields[item].meta.chineseMap) throw Error(`模型【${modelName}】错误：检测到【${modelName} 】模型的${item}字段设置了meta.formType='select'，
                // 但未设置meta.chineseMap(字段映射属性)，请完善`)

            }
            if (fields[item].meta && fields[item].meta.formType == 'switch') {
                if (!fields[item].meta.activeValue) {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的switch字段${item}的必须指定 【meta.activeValue】属性`)
                } 
                if (!fields[item].meta.inactiveValue) {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的switch字段${item}的必须指定 【meta.inactiveValue】属性`)
                } 
                if (!fields[item].meta.activeText) {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的switch字段${item}的必须指定 【meta.activeText】属性`)
                } 
                if (!fields[item].meta.inactiveText) {
                    throw Error(`模型【${modelName}】错误：模型【${modelName}】的switch字段${item}的必须指定 【meta.inactiveText】属性`)
                } 
            }
            // 字段映射外键验证
            if (fields[item].meta && fields[item].meta.chineseMap && fields[item].meta.chineseMap.hasOwnProperty('useForeign')) {
                const mapField = fields[item].meta.chineseMap.useForeign
                if (!model.foreign[mapField] && !systemForeign.includes(mapField))  {
                    result = false
                    throw Error(`模型【${modelName}】错误：检测到你【${modelName} 】模型的字段${item}使用了映射且值为外键${mapField}，但是你并没有为【${modelName}】模型的foreign字段 添加外键属性 【${mapField}】`)
                }
            }
            const strictText = ['manyImage', 'richText']
            const hiddenTableFieldShould = ['manyImage', 'richText']
            if (strictText.includes(fields[item].meta.formType)) {
                // 必须是text类型的字段验证
                if (fieldType != 'text') {
                    throw Error(`模型【${modelName}】错误：检测到你【${modelName} 】模型的字段${item}使用了【${fields[item].meta.formType}】表单类型，该类型对应的type必须是text!`)
                }
                // 应该在表格中隐藏的字段，只给出警告
                if (!fields[item].tableHidden) {
                    console.log(chalk.yellow(`模型【${modelName}】提示：**warning**检测到你【${modelName} 】模型的字段${item}使用了【${fields[item].meta.formType}】表单类型，该表单类型建议配置tableHidden以在表格中隐藏**warning**`))
                }
            }

            if (fields[item].isStore) {
                if (!fields[item].fields) {
                    throw Error(`模型【${modelName}】的字段${item}错误：配置了isStore的字段必须设置fields属性！`)
                }
                if (fields[item].type != 'text') {
                    throw Error(`模型【${modelName}】的字段${item}错误：配置了isStore的字段必须设置为text类型！`)
                }
            }

        }
        // 多条件筛选字段校验
        for (const key in filters) {
            const item = filters[key];
            // 筛选表单类型校验
            if (!filterType.includes(item.type)) {
                throw Error(`模型【${modelName}】错误：  filter里面的type字段只取下面的值${JSON.stringify(filterType)}`)
            }

            // select 的 筛选类型必须在 fields 里面指定 chineseMap
            if (item.type == 'select') {
                if (!model.fields[key].meta.hasOwnProperty('chineseMap')) {
                    throw Error(`模型【${modelName}】错误：：类型为select的过滤器（filter） 必须在fields 指定chineseMap属性`)
                }
            }

        }
        // 外键验证
        if (foreign) {
         
            for (const key in foreign) {
                const onTable = foreign[key].onTable || ''
                const refer = foreign[key].refer
                const referLabel = foreign[key].referLabel
                const selectFields = foreign[key].selectFields
                if (!selectFields || !Object.keys(selectFields)) {
                    throw Error(`模型【${modelName}】错误：外键${key}查询字段属性：【selectFields】不能为空！`)
                }

                // 是否存在外键实体表
                if (![...systemTable, ...modelNames].includes(onTable.toLowerCase())) {
                    throw Error(`模型【${modelName}】错误：配置了外键${key}，其对应的实体表是${onTable}，但是你并没有创建${onTable}模型！`)
                }
                // 是否存在实体表字段
                const foreignModelIndex = modelNames.findIndex((item => {
                    return item == onTable
                }))

                const foreignModelName = modelNames[foreignModelIndex]
                if (models[foreignModelIndex]) {
                    const foreignModel = require(models[foreignModelIndex]);
                    // 外键模型是否存在 refer
                    let foreignModelFileds = JSON.parse(JSON.stringify(foreignModel.fields))
                    foreignModelFileds = Object.keys(foreignModelFileds).map(item=>{
                        // 下划线转驼峰
                        return toHump(item)
                    })


                    if (!foreignModelFileds.includes(toHump(refer)) && refer != 'id') {
                        throw Error(`模型【${modelName}】错误：配置了外键${key}，其对应的外键字段(【refer】)是${refer}，但外键模型${foreignModelName},并没有定义该字段`)
                    }
                    // 外键模型是否存在 referLabel

                    if (!foreignModelFileds.includes(toHump(referLabel))) {
                        throw Error(`模型【${modelName}】错误：配置了外键${key}，其对应的外键字段映射（【referLabel】）是${referLabel}，但外键模型${foreignModelName},并没有定义该字段`)
                    }
                }
            }
        }

    })
    return result;
}

module.exports = {
    validatorConf
}