package cn.ibizlab.core.lite.extensions.service;


import cn.ibizlab.core.lite.domain.MetaField;
import cn.ibizlab.core.lite.domain.MetaRelationship;
import cn.ibizlab.core.lite.extensions.domain.MetaEntityModel;
import cn.ibizlab.core.lite.extensions.domain.SysModel;
import cn.ibizlab.core.lite.extensions.util.DstLiquibaseConfiguration;
import cn.ibizlab.core.util.config.LiquibaseConfiguration;
import cn.ibizlab.util.domain.LiquibaseProp;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSON;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.database.DatabaseFactory;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.diff.output.DiffOutputControl;
import liquibase.exception.DatabaseException;
import liquibase.integration.commandline.CommandLineUtils;
import liquibase.integration.spring.SpringLiquibase;
import liquibase.resource.ResourceAccessor;
import liquibase.util.StringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.json.JSONObject;
import org.json.XML;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

@Slf4j
@Service("DstLiquibaseService")
public class DstLiquibaseService{


    private boolean ignoreClasspathPrefix = true;
    protected String contexts;
    protected Map<String, String> parameters;
    protected String defaultSchema;
    protected String liquibaseSchema;
    protected String databaseChangeLogTable;
    protected String databaseChangeLogLockTable;
    protected String liquibaseTablespace;
    protected boolean dropFirst;

    @Value("${ibiz.filePath:/app/file/}")
    protected String liquibasePath;

    @Value("${ibiz.generateDs.catalogName:}")
    protected String catalogName;

    @Value("${ibiz.generateDs.generateField:tables,columns,indexes,foreignkeys,primarykeys,uniqueconstraints}")
    protected String generateField;

    @Value("${ibiz.generateDs.includeCatalog:false}")
    protected Boolean includeCatalog;

    @Value("${ibiz.generateDs.includeSchema:false}")
    private boolean includeSchema;

    @Value("${ibiz.generateDs.includeTablespace:true}")
    private boolean includeTablespace;

    @Value("${ibiz.generateDs.context:}")
    private String context;

    @Value("${ibiz.generateDs.dataDir:}")
    private String dataDir;

    @Resource
    public DstLiquibaseConfiguration dstLiquibaseConfiguration;

    public SysModel generateLiquibase(LiquibaseProp liquibaseProp, String name) {
        SysModel sysModel = liquibaseGenerateChangeLog(liquibaseProp,name);
        return sysModel;
    }



    @SneakyThrows
    public SysModel liquibaseGenerateChangeLog(LiquibaseProp liquibaseProp,String name){
        // 根据指定数据源生成对象
        String liquibaseChangeLogPath = getChangeLog();
        File file = new File(liquibaseChangeLogPath);
        if(!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }
        if(!file.exists()){
            file.createNewFile();
        }
        SpringLiquibase springLiquibase = dstLiquibaseConfiguration.dstliquibase(liquibaseProp);
        Liquibase liquibase = createLiquibase(springLiquibase,liquibaseChangeLogPath);
        Database database = liquibase.getDatabase();


        DiffOutputControl diffOutputControl = new DiffOutputControl(includeCatalog, includeSchema, includeTablespace, null);
        CommandLineUtils.doGenerateChangeLog(liquibaseChangeLogPath, database, catalogName,liquibaseProp.getDefaultSchema(),StringUtils.trimToNull(generateField),
                name, context, dataDir, diffOutputControl);
        SysModel sysModel = generateSysModel(liquibaseChangeLogPath);
        return sysModel;
    }

    /**
     * 构建一个SysModel对象
     * @param path 生成liquibaseChangeLog路径
     */
    public static SysModel generateSysModel(String path) {

        List<MetaEntityModel> metaEntityModels            =       new CopyOnWriteArrayList<>();
        SysModel sysModel                                 =       new SysModel();
        Map<String, MetaRelationship> parentMap           =       null;
        Map<String, MetaRelationship> subMap              =       null;
        List<Map<String,MetaRelationship>> parentEntities =       null;
        List<Map<String,MetaRelationship>> subEntities    =       null;
        MetaRelationship parent                           =       null;
        MetaRelationship sub                              =       null;
        MetaEntityModel metaEntityModel                   =       null;
        List<Map> commonMessage                           =       null;
        MetaField metaField                               =       null;
        List<Map> column                                  =       null;
        Map head                                          =       null;
        Map temp                                          =       null;

        com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(ConvertXMLtoJSON(path));
        head = (Map) jsonObject.get("databaseChangeLog");
        commonMessage = (List<Map>) head.get("changeSet");
        parentEntities = new CopyOnWriteArrayList<>();
        subEntities = new CopyOnWriteArrayList<>();
        // 维护父子键实体
        for (int i = 0;i < commonMessage.size(); i++) {
            temp = (Map) commonMessage.get(i).get("addForeignKeyConstraint");
            if(!ObjectUtils.isEmpty(temp)){
                // 存入父键map
                parent = new MetaRelationship();
                parentMap = new ConcurrentHashMap();
                parent.setName("DER1N_"+temp.get("referencedTableName").toString().toUpperCase());
                parent.setCodeName(temp.get("referencedColumnNames").toString());
                parent.setEntityName(temp.get("referencedTableName").toString().toUpperCase());
                parent.setRelationType("DER1N");
                parent.setSystemId(commonMessage.get(0).get("author").toString());
                parent.setRefEntityName(temp.get("baseTableName").toString().toUpperCase());
                parentMap.put(temp.get("baseColumnNames").toString(),parent);
                parentEntities.add(parentMap);

                // 存入子键map
                sub = new MetaRelationship();
                subMap = new ConcurrentHashMap();
                sub.setName("DER1N_"+temp.get("baseTableName").toString().toUpperCase());
                sub.setCodeName(temp.get("baseColumnNames").toString());
                sub.setEntityName(temp.get("baseTableName").toString().toUpperCase());
                sub.setRelationType("DER1N");
                sub.setSystemId(commonMessage.get(0).get("author").toString());
                sub.setRefEntityName(temp.get("referencedTableName").toString().toUpperCase());
                subMap.put(temp.get("referencedColumnNames").toString(),sub);
                subEntities.add(subMap);
            }
        }
        // 第一、二层 获取第一个作者以及id
        if (!ObjectUtils.isEmpty(commonMessage)){
            sysModel.setSystemid((String) commonMessage.get(0).get("author"));
            sysModel.setSystemname((String) commonMessage.get(0).get("author"));
        }

        Hashtable<String,Set<String>> keysets=new Hashtable<String,Set<String>>();
        for (int i = 0;i < commonMessage.size(); i++) {
            temp = (Map) commonMessage.get(i).get("addPrimaryKey");
            if(ObjectUtils.isEmpty(temp)){
                continue;
            }
            if(ObjectUtils.isEmpty(temp.get("tableName"))||ObjectUtils.isEmpty(temp.get("columnNames"))){
                continue;
            }
            String tableName=temp.get("tableName").toString().toUpperCase();
            String columnNames=temp.get("columnNames").toString().toUpperCase().replace(" ", "");
            Set<String> sets=new LinkedHashSet<>();
            for(String str:columnNames.split(","))
            {
                sets.add(str);
            }
            keysets.put(tableName, sets);

        }
        // 第三层 获取实体对象List 在这层可以放入subEntitys、parentEntitys等关系字段
        for (int i = 0;i < commonMessage.size(); i++) {
            temp = (Map) commonMessage.get(i).get("createTable");
            if(ObjectUtils.isEmpty(temp)){
                continue;
            }
            metaEntityModel = new MetaEntityModel();
            // 为了获取MetaEntityModel下那些基本类型字段（此处可补充MetaEntityModel等描述字段）
            String tableName=temp.get("tableName") == null ? "" : temp.get("tableName").toString().toUpperCase();
            metaEntityModel.setEntityName(tableName);
            metaEntityModel.setCodeName(tableName);
            metaEntityModel.setTableName(tableName);
            metaEntityModel.setLogicName(temp.get("remarks") == null ? metaEntityModel.getEntityName() : temp.get("remarks").toString().toUpperCase());
            metaEntityModel.setSystemId((String) commonMessage.get(0).get("author"));

         
            if(!(temp.get("column") instanceof List))
                continue;
            column = (List<Map>) temp.get("column");
            // 属性List
            List<MetaField> metaFields = new CopyOnWriteArrayList<>();
            List<MetaRelationship> parentRelationships = new CopyOnWriteArrayList<>();
            List<MetaRelationship> subRelationships = new CopyOnWriteArrayList<>();
            List<MetaField> unionKeys=new ArrayList<>();
            Set<String> keyset=keysets.get(tableName);
            if(keyset==null){
                keyset = new LinkedHashSet<>();
            }
            
            
            for (int j = 0; j < column.size();j++){
                // （此处可补充MetaField描述字段，以及父子外键关系等）
                metaField = new MetaField();
                metaField.setFieldName(column.get(j).get("name") == null? "" : column.get(j).get("name").toString().toUpperCase());
                metaField.setCodeName(metaField.getFieldName());
                String logicName=column.get(j).get("remarks") == null? metaField.getFieldName() : column.get(j).get("remarks").toString().trim().replace("\n","");
                if(logicName.length()>30)
                    logicName=logicName.substring(0,30);
                metaField.setFieldLogicName(logicName);
                metaField.setDataType(column.get(j).get("type") == null? "" : fieldToName(column.get(j).get("type").toString().toUpperCase()));
                int[] len=fieldToLength(column.get(j).get("type").toString());
                metaField.setDataLength(column.get(j).get("type") == null? 0 : len[0]);
                if(len[1]>0)
                    metaField.setDataPreci(len[1]);

                if(metaField.getDataType().indexOf("CHAR")>=0) {
                    metaField.setFieldType("TEXT");
                    if(metaField.getDataLength()>=1000)
                        metaField.setFieldType("LONGTEXT_1000");
                }
                else if((metaField.getDataType().indexOf("NUMB")>=0&&(metaField.getDataPreci()==null||metaField.getDataPreci()==0)&&metaField.getDataLength()>9)||(metaField.getDataType().indexOf("BIGINT")>=0)||(metaField.getDataType().indexOf("LONG")>=0))
                    metaField.setFieldType("BIGINT");
                else if((metaField.getDataType().indexOf("NUMB")>=0&&(metaField.getDataPreci()==null||metaField.getDataPreci()==0))||(metaField.getDataType().indexOf("INT")>=0))
                    metaField.setFieldType("INT");
                else if(metaField.getDataType().indexOf("NUMB")>=0||metaField.getDataType().indexOf("DECIMAL")>=0||metaField.getDataType().indexOf("FLOAT")>=0)
                    metaField.setFieldType("BIGDECIMAL");
                else if(metaField.getDataType().indexOf("LOB")>=0||metaField.getDataType().indexOf("MEDIUMTEXT")>=0)
                    metaField.setFieldType("LONGTEXT");
                else if(metaField.getDataType().indexOf("TIME")>=0)
                    metaField.setFieldType("DATETIME");
                else if(metaField.getDataType().indexOf("DATE")>=0) {
                    metaField.setFieldType("DATE");
                }

                if(metaField.getFieldType().startsWith("DATE") &&metaField.getDataType().startsWith("TIME") )
                {
                    if(metaField.getFieldName().equalsIgnoreCase("ZHXGSJ")
                            ||metaField.getFieldName().equalsIgnoreCase("ZSJKBGSJ")
                            ||metaField.getFieldName().equalsIgnoreCase("UPDATEDATE") )
                    {
                        metaField.setFieldType("DATETIME");
                        metaField.setPredefined("UPDATEDATE");
                    }
                    else if(metaField.getFieldName().equalsIgnoreCase("CJSJ")||metaField.getFieldName().equalsIgnoreCase("CREATEDATE"))
                    {
                        metaField.setFieldType("DATETIME");
                        metaField.setPredefined("CREATEDATE");
                    }
                    else if(metaField.getDataPreci()==null||metaField.getDataPreci()==0)
                        metaField.setFieldType("DATE");
                    else
                        metaField.setFieldType("DATETIME");
                }
                if(metaField.getFieldName().equalsIgnoreCase("ENABLE")||metaField.getFieldName().equalsIgnoreCase("SFSC"))
                {
                    metaField.setPredefined("LOGICVALID");
                }

                int isKey=0;
                Map<String,List<Map>> map = (Map<String, List<Map>>) column.get(j).get("constraints");
                if(!ObjectUtils.isEmpty(map)){
                    metaField.setNullable(map.get("nullable")==null?1:0);
                    isKey=map.get("primaryKey")==null?0:1;
                }else {
                    metaField.setNullable(1);
                }

                if(isKey==0&&keyset.contains(metaField.getFieldName()))
                    isKey=1;

                metaField.setKeyField(isKey);
                if(isKey==1)
                    unionKeys.add(metaField);    

                String name = column.get(j).get("name") == null? "" : column.get(j).get("name").toString();
                // 若在父键表查有该数据且属于同一实体则记录一条
                for (int l = 0; l < parentEntities.size();l++){
                    if(name.equals(parentEntities.get(l).keySet().toString().replaceAll("[\\[\\]]", ""))){
                        if(temp.get("tableName").toString().toUpperCase().equals(parentEntities.get(l).get(name).getRefEntityName())) {
                            parentEntities.get(l).get(name).setEntityId(metaField.getEntityId());
                            parentRelationships.add(parentEntities.get(l).get(name));
                        }
                    }
                }        
                metaEntityModel.setParentEntitys(parentRelationships);

                // 若在子键表查有该数据且属于同一实体则记录一条
                for (int l = 0; l < subEntities.size();l++){
                        if(name.equals(subEntities.get(l).keySet().toString().replaceAll("[\\[\\]]", ""))){
                            if(temp.get("tableName").toString().toUpperCase().equals(subEntities.get(l).get(name).getRefEntityName())){
                                subRelationships.add(subEntities.get(l).get(name));
                            }
                        }
                    }
                metaEntityModel.setSubEntitys(subRelationships);
                metaField.setPhysicalField(1);
                metaField.setShowOrder(j+1);
                metaField.setEntityName(metaEntityModel.getEntityName());

                metaFields.add(metaField);
            }

            int unionKeysLimit=1;
            if(sysModel.getSystemid().equalsIgnoreCase("tyyw2plus")||sysModel.getSystemid().equalsIgnoreCase("tyyw"))
                unionKeysLimit=0;

            if(unionKeys.size()>unionKeysLimit)
            {
                MetaField uilid = new MetaField();
                unionKeys.get(0).copyTo(uilid,true);
                uilid.setFieldName("UILID");
                uilid.setFieldLogicName("UILID");
                uilid.setCodeName("UILID");
                uilid.setKeyField(1);
                uilid.setPhysicalField(0);
                uilid.setFieldType("TEXT");
                uilid.setDataType("VARCHAR");
                String expression="";
                for(int no=0;no<unionKeys.size();no++)
                {
                    MetaField unikey=unionKeys.get(no);
                    unikey.setKeyField(0);
                    unikey.setUnionKey("KEY"+(no+1));
                    if(!StringUtils.isEmpty(expression))
                        expression+="||'||'||";
                    if(unikey.getFieldType().indexOf("DATE")>=0)
                        expression+=("to_char("+unikey.getFieldName()+" ,'yyyy-MM-dd hh24:mi:ss')");
                    else if(unikey.getFieldType().indexOf("INT")>=0||unikey.getFieldType().indexOf("DECIMAL")>=0)
                        expression+=("to_char("+unikey.getFieldName()+")");
                    else
                        expression+=unikey.getFieldName();

                }
                uilid.setExpression(expression);
                uilid.setShowOrder(0);
                metaFields.add(0,uilid);
            }


            metaEntityModel.setFields(metaFields);
            metaEntityModels.add(metaEntityModel);
            sysModel.setEntities(metaEntityModels);
        }
        return sysModel;
    }
    private static int[] fieldToLength(String s) {
        int[] a={0,0};
        if(s.contains("(")) {
            String newStr = s.substring(s.indexOf("(") + 1, s.indexOf(")")).replace(" BYTE","").replace(" ","").replace("*","22");
            String[] arr=newStr.split(",");
            if(arr.length>=1)
                a[0]=Integer.parseInt(arr[0]);
            if(arr.length>=2)
                a[1]=Integer.parseInt(arr[1]);
        }
        return a;
    }
    private static String fieldToName(String s) {
        if(s.contains("(")){
            String newStr = s.substring(0, s.indexOf("("));
            return newStr;
        }
        return s;
    }
    public static String ConvertXMLtoJSON(String path)  {

        InputStream is = null;
        try {
            is = new FileInputStream(path);
            String xml;
            xml = IOUtils.toString(is);
            //将xml转为json
            JSONObject xmlJSONObj = XML.toJSONObject(xml);
            String jsonPrettyPrintString = xmlJSONObj.toString();

            return jsonPrettyPrintString;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    @SneakyThrows
    protected Liquibase createLiquibase(SpringLiquibase springLiquibase,String changelogPath){
        SpringLiquibase.SpringResourceOpener resourceAccessor = springLiquibase.new SpringResourceOpener(changelogPath);
        Liquibase liquibase = new Liquibase(changelogPath, resourceAccessor, createDatabase(springLiquibase.getDataSource().getConnection(), resourceAccessor));
        liquibase.setIgnoreClasspathPrefix(isIgnoreClasspathPrefix());
        if (parameters != null) {
            for (Map.Entry<String, String> entry : parameters.entrySet()) {
                liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
            }
        }

        if (isDropFirst()) {
            liquibase.dropAll();
        }

        return liquibase;
    }

    public boolean isDropFirst() {
        return dropFirst;
    }

    public boolean isIgnoreClasspathPrefix() {
        return ignoreClasspathPrefix;
    }


    protected Database createDatabase(Connection c, ResourceAccessor resourceAccessor) throws DatabaseException {

        DatabaseConnection liquibaseConnection;
        if (c == null) {
            log.info("Null connection returned by liquibase datasource. Using offline unknown database");
            liquibaseConnection = new OfflineConnection("offline:unknown", resourceAccessor);

        } else {
            liquibaseConnection = new JdbcConnection(c);
        }

        Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(liquibaseConnection);
        if (StringUtils.trimToNull(this.defaultSchema) != null) {
            if (database.supportsSchemas()) {
                database.setDefaultSchemaName(this.defaultSchema);
            } else if (database.supportsCatalogs()) {
                database.setDefaultCatalogName(this.defaultSchema);
            }
        }
        if (StringUtils.trimToNull(this.liquibaseSchema) != null) {
            if (database.supportsSchemas()) {
                database.setLiquibaseSchemaName(this.liquibaseSchema);
            } else if (database.supportsCatalogs()) {
                database.setLiquibaseCatalogName(this.liquibaseSchema);
            }
        }
        if (StringUtils.trimToNull(this.liquibaseTablespace) != null && database.supportsTablespaces()) {
            database.setLiquibaseTablespaceName(this.liquibaseTablespace);
        }
        if (StringUtils.trimToNull(this.databaseChangeLogTable) != null) {
            database.setDatabaseChangeLogTableName(this.databaseChangeLogTable);
        }
        if (StringUtils.trimToNull(this.databaseChangeLogLockTable) != null) {
            database.setDatabaseChangeLogLockTableName(this.databaseChangeLogLockTable);
        }
        return database;
    }

    @SneakyThrows
    public String getChangeLog() {
        // 自定义属性参数
        String uuid = String.valueOf(UUID.randomUUID());
        byte[] nameBytes = uuid.getBytes("UTF-8");
        String fileid = DigestUtils.md5DigestAsHex(nameBytes);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = sdf.format(new Date())+".xml";
        String fileFullPath = this.liquibasePath+"ibizutil"+ File.separator + fileid+File.separator + fileName;
        return fileFullPath;
    }
}
