package cn.ibizlab.core.data.lite;

import cn.ibizlab.util.errors.BadRequestAlertException;
import com.alibaba.fastjson.JSON;
import net.ibizsys.model.IPSSystem;
import net.ibizsys.model.PSModelServiceImpl;
import net.ibizsys.model.database.IPSDEFDTColumn;
import net.ibizsys.model.database.IPSSysDBTable;
import net.ibizsys.model.dataentity.IPSDataEntity;
import net.ibizsys.model.dataentity.defield.IPSDEField;
import net.ibizsys.model.dataentity.defield.IPSLinkDEField;
import net.ibizsys.model.dataentity.der.IPSDER1N;
import net.ibizsys.model.dataentity.der.IPSDERBase;
import net.ibizsys.model.dataentity.ds.IPSDEDataQueryCode;
import net.ibizsys.model.dataentity.ds.IPSDEDataQueryCodeCond;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

@Service
public class DynamicModelService {


    public String getModelPath()
    {
        return LiteStorage.MODEL_PATH;
    }

    private Map<String, IPSSystem> dynamicSystems;
    public Object systemLock = new Object();

    public  synchronized IPSSystem getDynamicSystem(String system)
    {
        synchronized(systemLock)
        {
            if(dynamicSystems==null)
                dynamicSystems=new HashMap<>();
            if(dynamicSystems.containsKey(system))
                return dynamicSystems.get(system);
        }

        return initDynamicSystem(system);
    }

    private IPSSystem initDynamicSystem(String system)
    {
        if(dynamicSystems==null)
            dynamicSystems=new HashMap<>();

        IPSSystem iPSSystem = null;
        String strPSModelFolderPath=null;
        if(Files.exists(Paths.get(getModelPath(),system,"model","PSSYSTEM.json"))) {
            strPSModelFolderPath=Paths.get(getModelPath(),system,"model").toString();
        }
        else
        {
            Map<String,DstSystemModel> allDynamicModel=getProxy().findAllDynamicModel();
            if(allDynamicModel.containsKey(system))
                strPSModelFolderPath = allDynamicModel.get(system).getDynamicModelPath();
        }
        if(StringUtils.isEmpty(strPSModelFolderPath))
            throw new BadRequestAlertException("加载系统模型错误,未找到对应模型目录", "DynamicSystem", system);

        PSModelServiceImpl psModelService = new PSModelServiceImpl();
        psModelService.setPSModelFolderPath(strPSModelFolderPath);
        try {
            iPSSystem = psModelService.getPSSystem();
            if (iPSSystem == null)
                throw new BadRequestAlertException("加载系统模型错误", "DynamicSystem", system);
            dynamicSystems.put(system, iPSSystem);
            if (!system.equals(iPSSystem.getCodeName()))
                dynamicSystems.put(iPSSystem.getCodeName(), iPSSystem);
            if (!iPSSystem.getCodeName().toLowerCase().equals(iPSSystem.getCodeName()))
                dynamicSystems.put(iPSSystem.getCodeName().toLowerCase(), iPSSystem);

        } catch (Exception e) {
            throw new BadRequestAlertException(String.format("加载系统模型错误:%s", e.getMessage()), "DynamicSystem", system);
        }
        return iPSSystem;
    }

    @Cacheable( value="syspssystem",key = "'row:sys-dst-sys-dynamic'")
    public Map<String,DstSystemModel> findAllDynamicModel()
    {
        Map<String,DstSystemModel> models=new HashMap<>();
        loopModelDir(new File(getModelPath()),models);
        return models;
    }

    private void loopModelDir(File dir,Map<String,DstSystemModel> models)
    {
        if(dir.isDirectory())
        {
            Path path=Paths.get(dir.getAbsolutePath(),"PSSYSTEM.json");
            File check=path.toFile();
            if(check.exists())
            {
                DstSystemModel dstSystemModel=new DstSystemModel().fromPath(path);
                if(dstSystemModel!=null&&(!StringUtils.isEmpty(dstSystemModel.getPssystemid())))
                {
                    String system=dstSystemModel.getPssystemid();
                    if(models.containsKey(system)&&models.get(system).getLastModify()>=dstSystemModel.getLastModify())
                        return;

                    models.put(system, dstSystemModel);
                    if (!system.toLowerCase().equals(system))
                        models.put(system.toLowerCase(), dstSystemModel);

                    String deployId=path.getParent().getFileName().toString();
                    if("model".equalsIgnoreCase(deployId))
                        deployId=dir.getParentFile().getName();
                    if(!system.equalsIgnoreCase(deployId))
                    {
                        models.put(deployId, dstSystemModel);
                        if (!deployId.toLowerCase().equals(deployId))
                            models.put(deployId.toLowerCase(), dstSystemModel);
                    }
                }
                return;
            }
            else
            {
                for(File sub:dir.listFiles())
                {
                    loopModelDir(sub,models);
                }
            }
        }
    }

    @Cacheable( value="meta-entities", key = "'system:'+#p0")
    public Map<String,MetaEntityModel> getEntities(String system) throws Exception
    {
        Map<String,MetaEntityModel> entities=new HashMap<>();
        IPSSystem iPSSystem=getProxy().getDynamicSystem(system);
        int i=1;
        for(IPSDataEntity dataEntity:iPSSystem.getAllPSDataEntities()){
            MetaEntityModel metaEntityModel=new MetaEntityModel();
            metaEntityModel.setEntityId(dataEntity.getId()).setEntityName(dataEntity.getName())
                    .setLogicName(dataEntity.getLogicName()).setCodeName(dataEntity.getCodeName()).setTableName(dataEntity.getTableName())
                    .setDsId(iPSSystem.getCodeName()+"-"+((StringUtils.isEmpty(dataEntity.getDSLink())||dataEntity.getDSLink().equalsIgnoreCase("DEFAULT"))?"master":dataEntity.getDSLink()))
                    .setDsName(metaEntityModel.getDsId())
                    .setSystemId(iPSSystem.getCodeName()).setSystemName(iPSSystem.getLogicName()).setShowOrder(i);
            i++;
            entities.put(metaEntityModel.getCodeName(),metaEntityModel);
            if(!metaEntityModel.getEntityName().equals(metaEntityModel.getCodeName()))
                entities.put(metaEntityModel.getEntityName(),metaEntityModel);

        }
        return entities;
    }


    public  EntityModel getDynamicEntity(String system,String entity) throws Exception
    {
        MetaEntityModel metaEntityModel=getProxy().getEntities(system).get(entity);
        if(metaEntityModel==null)
            throw new BadRequestAlertException("未找到对应的实体模型","DynamicEntity",entity);
        IPSSystem iPSSystem=getProxy().getDynamicSystem(system);
        IPSDataEntity dataEntity = iPSSystem.getPSDataEntity(metaEntityModel.getEntityId(),true);
        if(dataEntity==null)
            throw new BadRequestAlertException("未找到对应的实体模型","DynamicEntity",entity);
        EntityModel entityModel=new EntityModel();

        if(dataEntity.isLogicValid())
        {
            List<Setting> settingList=new ArrayList<>();
            String val=dataEntity.getValidLogicValue();
            if(StringUtils.isEmpty(val))val="1";
            String deVal=dataEntity.getInvalidLogicValue();
            if(StringUtils.isEmpty(deVal))deVal="0";
            settingList.add(new Setting().setProperty("logicval").setValue(val));
            settingList.add(new Setting().setProperty("logicdelval").setValue(deVal));
            metaEntityModel.setExtParams(JSON.toJSONString(settingList));
        }
        entityModel.setEntity(metaEntityModel);

        List<String> dsTypes=new ArrayList<>();

        if(dataEntity.getAllPSDEDBConfigs()!=null)
        {
            dataEntity.getAllPSDEDBConfigs().forEach(item->{
                dsTypes.add(item.getDBType().toLowerCase());
                if(!entityModel.getTableName().equalsIgnoreCase(item.getTableName()))
                    entityModel.getEntity().set("table-"+item.getDBType().toLowerCase(),item.getTableName());
            });
        }

        if(dsTypes.size()>0)
            entityModel.getEntity().set("ds_types",String.join(",",dsTypes));

        Map<String,FieldModel> fieldMaps=new LinkedHashMap<>();
        for(IPSDEField defield:dataEntity.getAllPSDEFields())
        {

            FieldModel fieldModel=new FieldModel();
            MetaFieldModel metaFieldModel=new MetaFieldModel();
            metaFieldModel.setFieldId(defield.getId()).setFieldName(defield.getName()).setCodeName(defield.getCodeName()).setFieldLogicName(defield.getLogicName())
                    .setEntityId(dataEntity.getId()).setEntityCodeName(dataEntity.getCodeName()).setEntityName(dataEntity.getName()).setSystemId(metaEntityModel.getSystemId())
                    .setFieldUniName(dataEntity.getName()+"."+defield.getName()).setFieldShowName(String.format("%1$s-%2$s[%3$s]",defield.getName(),defield.getLogicName(),dataEntity.getName()))
                    .setKeyField(defield.isKeyDEField()?1:null).setMajorField(defield.isMajorDEField()?1:null).setUnionKey(defield.getUnionKeyValue()).setFieldType(defield.getDataType())
            .setPredefined(defield.getPredefinedType()).setDict(defield.getPSCodeList()!=null?defield.getPSCodeList().getCodeName():null).setNullable(defield.isAllowEmpty()?1:0)
            .setPhysicalField(defield.isPhisicalDEField()?1:0).setDataType(DataType.findTypeName(defield.getStdDataType())).setDataLength(defield.getLength()).setDataPreci(defield.getPrecision())
            .setIsEnableAudit(defield.isEnableAudit()?1:null).setShowOrder(defield.getImportOrder());

            if(defield.getAllPSDEFDTColumns()!=null)
            {
                defield.getAllPSDEFDTColumns().forEach(col->{
                    if(!metaFieldModel.getFieldName().equalsIgnoreCase(col.getColumnName()))
                        metaFieldModel.set("column-"+col.getDBType().toLowerCase(),col.getColumnName());
                    if(StringUtils.isEmpty(metaFieldModel)&&defield.isFormulaDEField()&&(!StringUtils.isEmpty(col.getQueryCodeExp())))
                        metaFieldModel.setExpression(col.getQueryCodeExp().replace("`","").replace("[","").replace("]",""));
                });
            }

            if(defield.isLinkDEField() && defield instanceof IPSLinkDEField)
            {
                IPSLinkDEField linkDEField = (IPSLinkDEField)defield;
                metaFieldModel.setRefFieldId(linkDEField.getRelatedPSDEField().getId())
                .setRefFieldName(linkDEField.getRelatedPSDEField().getName()).setRelationId(linkDEField.getPSDER().getId())
                .setRelationName(linkDEField.getPSDER().getName()).setRelationCodeName(linkDEField.getPSDER().getCodeName()).setRefEntityName(linkDEField.getPSDER().getMajorPSDataEntity().getName());
            }

            fieldModel.setCodeName(metaFieldModel.getCodeName()).setColumnName(metaFieldModel.getFieldName()).setUnionName(metaFieldModel.getFieldUniName()).setShowName(metaFieldModel.getFieldShowName())
            .setComment(metaFieldModel.getFieldLogicName()).setField(metaFieldModel);

            fieldMaps.put(fieldModel.getColumnName(),fieldModel);
            entityModel.addField(fieldModel);
        }

        if(dataEntity.getMinorPSDERs()!=null)
        {
            for(IPSDERBase der : dataEntity.getMinorPSDERs())
            {
                RelationshipModel rel=new RelationshipModel();
                rel.setCodeName(der.getCodeName()).setEntityId(der.getMajorPSDataEntity().getId())
                        .setEntityCodeName(der.getMajorPSDataEntity().getCodeName()).setEntityName(der.getMajorPSDataEntity().getName())
                        .setEntityLogicName(der.getMajorPSDataEntity().getLogicName()).setSystemId(iPSSystem.getCodeName()).setTableName(der.getMajorPSDataEntity().getTableName());

                MetaRelationshipModel metaRel=new MetaRelationshipModel();
                metaRel.setId(der.getId()).setName(der.getName()).setRelationType(der.getDERType()).setCodeName(der.getCodeName()).setRefEntityId(der.getMajorPSDataEntity().getId())
                        .setRefEntityName(der.getMajorPSDataEntity().getName()).setEntityId(dataEntity.getId()).setEntityName(dataEntity.getName()).setNestedName(der.getMinorCodeName())
                        .setSystemId(iPSSystem.getCodeName());

                if(der instanceof IPSDER1N)
                {
                    IPSDER1N der1n=(IPSDER1N)der;
                    MetaLookupModel lookupModel=new MetaLookupModel().setRelationid(der.getId())
                            .setFieldname(der1n.getPickupDEFName()).setReffieldname(der1n.getPSPickupDEField().getRelatedPSDEField().getName());
                    metaRel.addLookup(lookupModel);
                }

                rel.setRelation(metaRel);
                entityModel.addReference(rel);
            }

        }

        if(dataEntity.getMajorPSDERs()!=null)
        {
            for(IPSDERBase der : dataEntity.getMajorPSDERs())
            {
                if(StringUtils.isEmpty(der.getMinorCodeName()))
                    continue;
                RelationshipModel rel=new RelationshipModel();
                rel.setCodeName(der.getMinorCodeName()).setEntityId(der.getMinorPSDataEntity().getId())
                        .setEntityCodeName(der.getMinorPSDataEntity().getCodeName()).setEntityName(der.getMinorPSDataEntity().getName())
                        .setEntityLogicName(der.getMinorPSDataEntity().getLogicName()).setSystemId(iPSSystem.getCodeName()).setTableName(der.getMinorPSDataEntity().getTableName());

                MetaRelationshipModel metaRel=new MetaRelationshipModel();
                metaRel.setId(der.getId()).setName(der.getName()).setRelationType(der.getDERType()).setCodeName(der.getCodeName()).setRefEntityId(der.getMajorPSDataEntity().getId())
                        .setRefEntityName(der.getMajorPSDataEntity().getName()).setEntityId(dataEntity.getId()).setEntityName(dataEntity.getName()).setNestedName(der.getMinorCodeName())
                        .setSystemId(iPSSystem.getCodeName());

                if(der instanceof IPSDER1N)
                {
                    IPSDER1N der1n=(IPSDER1N)der;
                    MetaLookupModel lookupModel=new MetaLookupModel().setRelationid(der.getId())
                            .setFieldname(der1n.getPickupDEFName()).setReffieldname(der1n.getPSPickupDEField().getRelatedPSDEField().getName());
                    metaRel.addLookup(lookupModel);
                }

                rel.setRelation(metaRel);
                entityModel.addNested(rel);
            }
        }

        if(dataEntity.getAllPSDEDataQueries()!=null)
        {
            dataEntity.getAllPSDEDataQueries().forEach(dataQuery->{

                try {
                    if(dataQuery.getAllPSDEDataQueryCodes()!=null)
                    {
                        dataQuery.getAllPSDEDataQueryCodes().forEach(dq->{

                            String code=getQueryCode(dq);

                            MetaDataSetModel dsModel=new MetaDataSetModel().setDatasetId(entityModel.getEntityName().toLowerCase()+"-dq-"+dataQuery.getCodeName()+"-"+dq.getDBType().toLowerCase())
                                    .setDatasetName(dataQuery.getLogicName()).setCodeName(dataQuery.getCodeName())
                                    .setEntityId(entityModel.getEntityId()).setEntityName(entityModel.getEntityName()).setDsCode(code);
                            entityModel.addDataSet(dsModel);
                        });
                    }
                } catch (Exception exception) {

                }

            });
        }

        if(dataEntity.getAllPSDEDataSets()!=null)
        {
            dataEntity.getAllPSDEDataSets().forEach(dataSet->{


                try {

                    if(dataSet.getPSDEDataQueries()!=null)
                    {



                        Map<String,String> map =new HashMap<>();
                        dataSet.getPSDEDataQueries().forEach(dataQuery->{

                            try {
                                if(dataQuery.getAllPSDEDataQueryCodes()!=null)
                                {
                                    dataQuery.getAllPSDEDataQueryCodes().forEach(dq->{
                                        String code="";
                                        if(!map.containsKey(dq.getDBType())) {
                                            map.put(dq.getDBType(), "");
                                            code="";
                                        }
                                        else {
                                            code = map.get(dq.getDBType());
                                            code = code+"\r\n union all \r\n";
                                        }
                                        code = code+ getQueryCode(dq);
                                        ;//"<include refid=\""+entityModel.getEntityName().toLowerCase()+"_dq_"+dataQuery.getCodeName()+"_"+dq.getDBType().toLowerCase()+"\"/>";
                                        map.put(dq.getDBType(),code);
                                    });
                                }
                            } catch (Exception exception) {

                            }

                        });

                        for(Map.Entry<String,String> entry:map.entrySet())
                        {
                            String sql="select t1.* from (\r\n".concat(entry.getValue()).concat("\r\n) t1");
                            if(dataSet.getMajorSortPSDEField()!=null)
                            {
                                IPSDEFDTColumn column = dataSet.getMajorSortPSDEField().getPSDEFDTColumn(entry.getKey(),false);

                                sql=sql.concat(" order by ").concat(column==null?dataSet.getMajorSortPSDEField().getName():column.getColumnName());
                                if(!StringUtils.isEmpty(dataSet.getMajorSortDir()))
                                    sql=sql.concat(" ").concat(dataSet.getMajorSortDir());
                                if(dataSet.getMinorSortPSDEField()!=null)
                                {
                                    IPSDEFDTColumn subCol = dataSet.getMinorSortPSDEField().getPSDEFDTColumn(entry.getKey(),false);

                                    sql=sql.concat(",").concat(subCol==null?dataSet.getMinorSortPSDEField().getName():subCol.getColumnName());
                                    if(!StringUtils.isEmpty(dataSet.getMinorSortDir()))
                                        sql=sql.concat(" ").concat(dataSet.getMinorSortDir());

                                }
                            }
                            MetaDataSetModel dsModel=new MetaDataSetModel().setDatasetId(entityModel.getEntityName().toLowerCase()+"-ds-"+dataSet.getCodeName()+"-"+entry.getKey().toLowerCase())
                                    .setDatasetName(dataSet.getLogicName()).setCodeName(dataSet.getCodeName())
                                    .setEntityId(entityModel.getEntityId()).setEntityName(entityModel.getEntityName()).setDsCode(sql);
                            entityModel.addDataSet(dsModel);
                        }

                    }
                } catch (Exception exception) {

                }

            });


        }


        return entityModel;
    }

    @Autowired
    @Lazy
    private DynamicModelService proxy;

    public DynamicModelService getProxy()
    {
        return proxy;
    }



    public static enum  DataType {
        UNKNOWN(0,"UNKNOWN"),
        BIGINT(1,"BIGINT"),
        BINARY(2,"BINARY"),
        BIT(3,"BIT"),
        CHAR(4,"CHAR"),
        DATETIME(5,"DATETIME"),
        DECIMAL(6,"DECIMAL"),
        FLOAT(7,"FLOAT"),
        IMAGE(8,"IMAGE"),
        INT(9,"INT"),
        MONEY(10,"MONEY"),
        NCHAR(11,"NCHAR"),
        NTEXT(12,"NTEXT"),
        NVARCHAR(13,"NVARCHAR"),
        NUMERIC(14,"NUMERIC"),
        REAL(15,"REAL"),
        SMALLDATETIME(16,"SMALLDATETIME"),
        SMALLINT(17,"SMALLINT"),
        SMALLMONEY(18,"SMALLMONEY"),
        SQL_VARIANT(19,"SQL_VARIANT"),
        SYSNAME(20,"SYSNAME"),
        TEXT(21,"TEXT"),
        TIMESTAMP(22,"TIMESTAMP"),
        TINYINT(23,"TINYINT"),
        VARBINARY(24,"VARBINARY"),
        VARCHAR(25,"VARCHAR"),
        UNIQUEIDENTIFIER(26,"UNIQUEIDENTIFIER"),
        DATE(27,"DATE"),
        TIME(28,"TIME");

        public final int code;
        public final String name;

        private DataType(int code, String name) {
            this.code = code;
            this.name = name;
        }

        public int getCode() {
            return code;
        }

        public String getName() {
            return name;
        }

        public static String findTypeName(Integer type) {
            for (DataType userTypeEnum : DataType.values()) {
                if (userTypeEnum.getCode()==type) {
                    return userTypeEnum.getName();
                }
            }
            return VARCHAR.getName();
        }

    }

    /*
    <#function contextParamConvert contextParam>
	<#comment>补充上下文的IN查询(in通过$获取参数,其余为#)： IN (${srfdatacontext('cityid','{"defname":"CITYID","dename":"CITY"}')}) -->IN ( ${srf.srfdatacontext.cityid} )  </#comment>
	<#assign resultParam=contextParam?replace("(IN|in) \\(\\$\\{srf(datacontext|sessioncontext|webcontext)\\('(\\w+)','(.*?)'\\)}\\)","$1 (\\$\{srf.$2.$3})","r")>
	<#comment>平台配置格式替换${srfdatacontext('cityid','{"defname":"CITYID","dename":"CITY"}')}  --> #{srf.srfdatacontext.cityid}   </#comment>
	<#assign resultParam=resultParam?replace("\\$\\{srf(datacontext|sessioncontext|webcontext)\\('(\\w+)','(.*?)'\\)}","#\{srf.$1.$2}","r")>
	<#comment>用户配置格式替换${srfdatacontext('cityid')}  --> #{srf.srfdatacontext.cityid}   </#comment>
	<#assign resultParam=resultParam?replace("\\$\\{srf(datacontext|sessioncontext|webcontext)\\('(\\w+)'\\)}","#\{srf.$1.$2}","r")>
	<#comment>将上下文参数转小写 #{srf.srfdatacontext.SRFORGID} --> #{srf.srfdatacontext.srforgid}</#comment>
	<#assign params=resultParam?matches('srf.(datacontext|sessioncontext|webcontext).(\\w+)')>
	<#list params as param>
		<#assign resultParam=resultParam?replace(param,param?lower_case,'i')>
	</#list>
    <#if sys.getSaaSMode()==1 && item.getSaaSMode()==1>
        <#assign resultParam=resultParam?replace("AND t11.SRFDCID = '__SRFSAASDCID__'","")>
    </#if>
	<#return resultParam>
</#function>
*/
    private String contextParamConvert(String contextParam)
    {
        String resultParam=contextParam.replaceAll("(IN|in) \\(\\$\\{srf(datacontext|sessioncontext|webcontext)\\('(\\w+)','(.*?)'\\)}\\)","$1 (\\${srf.$2.$3})");
        resultParam=resultParam.replaceAll("\\$\\{srf(datacontext|sessioncontext|webcontext)\\('(\\w+)','(.*?)'\\)}","#{srf.$1.$2}");
        resultParam=resultParam.replaceAll("\\$\\{srf(datacontext|sessioncontext|webcontext)\\('(\\w+)'\\)}","#{srf.$1.$2}");
        resultParam=resultParam.replace("AND t11.SRFDCID = '__SRFSAASDCID__'","");
        return resultParam;
    }

    private String getQueryCode(IPSDEDataQueryCode dq)
    {
        String code=contextParamConvert(dq.getQueryCode());
        if(dq.getPSDEDataQueryCodeConds()!=null)
        {
            int i=0;
            for(IPSDEDataQueryCodeCond cond:dq.getPSDEDataQueryCodeConds())
            {
                if(i==0)
                    code=code.concat(" where ");
                else if(i>0)
                    code=code.concat(" and ");
                code=code.concat(contextParamConvert(cond.getCustomCond()));
                i++;
            }
        }
        return code;
    }
}
