package cn.ibizlab.core.data.model;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.springframework.util.StringUtils;

import java.util.*;

@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
public class POSchema {

    private String name;

    private String remarks;

    private String dsType;

    private String defaultDataSource;

    private String logicVal;
    private String logicDelVal;

    private List<Column> columns;

    public POSchema addColumn(Column column)
    {
        if(columns==null)
            columns=new ArrayList<>();
        columns.add(column);
        if(columnMaps!=null)
            columnMaps=null;
        return this;
    }

    private List<ForeignKeyConstraint> foreignKeyConstraints;

    public POSchema addForeignKeyConstraint(ForeignKeyConstraint foreignKeyConstraint)
    {
        if(foreignKeyConstraints==null)
            foreignKeyConstraints=new ArrayList<>();
        foreignKeyConstraints.add(foreignKeyConstraint);

        return this;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private Map<String,Column> columnMaps;

    @JsonIgnore
    @JSONField(serialize = false)
    private Map<String,String> resultMap;

    @JsonIgnore
    @JSONField(serialize = false)
    private Map<String,String> keyMap;

    @JsonIgnore
    @JSONField(serialize = false)
    public Map<String, Column> getColumnMaps() {
        if(columns!=null&&columnMaps==null)
        {
            columnMaps=new LinkedHashMap<>();
            resultMap=new LinkedHashMap<>();
            keyMap=new LinkedHashMap<>();
            columns.forEach(column -> {
                columnMaps.put(column.getName().toLowerCase(),column);
                if((!StringUtils.isEmpty(column.getAlias()))&&column.getAlias().equalsIgnoreCase(column.getName()))
                {
                    columnMaps.put(column.getAlias().toLowerCase(), column);
                    resultMap.put(column.getName().toLowerCase(),column.getAlias().toLowerCase());
                    if(column.isPrimaryKey())
                        keyMap.put(column.getName().toLowerCase(),column.getAlias().toLowerCase());
                }
                else
                {
                    resultMap.put(column.getName().toLowerCase(), column.getName().toLowerCase());
                    if(column.isPrimaryKey())
                        keyMap.put(column.getName().toLowerCase(),column.getName().toLowerCase());
                }

            });
        }
        return columnMaps;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public Map<String, String> getResultMap() {
        if(columns!=null&&resultMap==null)
        {
            getColumnMaps();
        }
        return resultMap;
    }


    @JsonIgnore
    @JSONField(serialize = false)
    public Map<String, String> getKeyMap() {
        if(columns!=null&&keyMap==null)
        {
            getColumnMaps();
        }
        return keyMap;
    }

    public Column getColumn(String name)
    {
        if(getColumnMaps()!=null)
        {
            return columnMaps.get(name.toLowerCase());
        }
        return null;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private Column lastModifyColumn;
    @JsonIgnore
    @JSONField(serialize = false)
    public Column getLastModifyField() {
        if(columns!=null&&lastModifyColumn==null)
            for(Column col:columns)
                if(col.isLastModify())
                {
                    lastModifyColumn=col;
                    return lastModifyColumn;
                }
        return lastModifyColumn;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private Column createTimeColumn;
    @JsonIgnore
    @JSONField(serialize = false)
    public Column getCreateTimeColumn() {
        if(columns!=null&&createTimeColumn==null)
            for(Column col:columns)
                if(col.isCreateTime())
                {
                    createTimeColumn=col;
                    return createTimeColumn;
                }
        return createTimeColumn;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private Column tenantColumn;
    @JsonIgnore
    @JSONField(serialize = false)
    public Column getTenantColumn() {
        if(columns!=null&&tenantColumn==null)
            for(Column col:columns)
                if(col.isTenant())
                {
                    tenantColumn=col;
                    return tenantColumn;
                }
        return tenantColumn;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private boolean logicValid=true;
    @JsonIgnore
    @JSONField(serialize = false)
    public boolean isLogicValid()
    {
        if(logicValid&&logicValidColumn==null) {
            if (columns != null) {
                for (Column col:columns) {
                    if (col.isLogicValid()) {
                        logicValidColumn = col;
                        return logicValid;
                    }
                }
            }
            logicValid = false;
        }
        return logicValid;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private Column logicValidColumn;
    @JsonIgnore
    @JSONField(serialize = false)
    public Column getLogicValidColumn() {
        if(logicValid&&logicValidColumn==null) {
            if (columns != null) {
                for (Column col:columns) {
                    if (col.isLogicValid()) {
                        logicValidColumn = col;
                        return logicValidColumn;
                    }
                }
            }
            logicValid = false;
        }
        return logicValidColumn;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @Accessors(chain = true)
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Column{
        private String name;
        private String remarks;
        private String type;
        private Integer length;
        private Integer precision;
        private String defaultValue;
        private Boolean autoIncrement;
        private String alias;
        private String predefined;

        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isPrimaryKey()
        {
            return this.getConstraints()!=null&&this.getConstraints().getPrimaryKey()!=null&&this.getConstraints().getPrimaryKey()==true;
        }

        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isLogicValid()
        {
            return "LOGICVALID".equals(this.getPredefined());
        }

        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isLastModify()
        {
            return "UPDATEDATE".equals(this.getPredefined());
        }

        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isCreateTime()
        {
            return "CREATEDATE".equals(this.getPredefined());
        }

        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isTenant()
        {
            return "TENANT".equals(this.getPredefined());
        }

        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isText()
        {
            return this.getType().toUpperCase().indexOf("TEXT")>=0||this.getType().toUpperCase().indexOf("CHAR")>=0||this.getType().toUpperCase().indexOf("LOB")>=0;
        }
        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isNumber()
        {
            return this.getType().toUpperCase().indexOf("NUM")>=0||this.getType().toUpperCase().indexOf("FLOAT")>=0||this.getType().toUpperCase().indexOf("DOUBLE")>=0||this.getType().toUpperCase().indexOf("DECIMAL")>=0;
        }
        @JsonIgnore
        @JSONField(serialize = false)
        public boolean isInt()
        {
            return this.getType().toUpperCase().indexOf("INT")==0;
        }

        private Constraints constraints;

        public Constraints getConstraints(boolean createWhenNotExist)
        {
            if(createWhenNotExist)
            {
                if(constraints==null)
                    constraints=new Constraints();
            }
            return constraints;
        }

    }

    @Getter
    @Setter
    @NoArgsConstructor
    @Accessors(chain = true)
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Constraints{
        private Boolean nullable;
        private Boolean primaryKey;
        private String primaryKeyName;
        private String foreignKeyName;
        private String referencedTableName;
        private String referencedColumnNames;

    }

    @Getter
    @Setter
    @NoArgsConstructor
    @Accessors(chain = true)
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class ForeignKeyConstraint{
        private String baseColumnNames;
        private String baseTableName;
        private String constraintName;
        private String referencedTableName;
        private String referencedColumnNames;
        private Boolean validate;
        private String onUpdate;
        private String onDelete;

    }

    @Getter
    @Setter
    @NoArgsConstructor
    @Accessors(chain = true)
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Script{
        private String name;
        private String vendorProvider;
        private String declare;
        private String body;
        private Map params;

    }


    public List<Script> scripts;

    private static Map<String, String> provider = new HashMap<String, String>(){{
        put("oracle", "oracle");
        put("mysql", "mysql");
        put("postgresql", "postgresql");
        put("mppdb", "postgresql");
        put("dm", "oracle");
        put("dameng", "oracle");
        put("gbase", "oracle");
        put("h2", "mysql");
    }};



    public Script getScript(String name,String type)
    {
        if(scripts!=null)
        {
            String vendorProvider="mysql";
            if((!StringUtils.isEmpty(type))&&provider.containsKey(type.toLowerCase()))
                vendorProvider=provider.get(type.toLowerCase());
            for(Script script:scripts)
            {
                if(name.equalsIgnoreCase(script.getName())&&((script.getVendorProvider().indexOf(vendorProvider)>=0)||StringUtils.isEmpty(script.getVendorProvider())))
                    return script;
            }
        }
        else
		{
			scripts=new ArrayList<>();
		}
        return getDefaultQueryScript();
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public Script defaultQueryScript;

    @JsonIgnore
    @JSONField(serialize = false)
    public Script getDefaultQueryScript()
    {
        if(defaultQueryScript==null)
        {
            defaultQueryScript=new Script();
            defaultQueryScript.setName("default");
            defaultQueryScript.setVendorProvider("mysql,oracle,postgresql");
            String sql="select ";

            sql += (getBaseColumns()+" from "+name+" t ");

            if(isLogicValid())
            {

                sql += " where t."+this.getLogicValidCond();

            }
            defaultQueryScript.setBody(sql);
        }
        return defaultQueryScript;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public String logicValidCond;
    @JsonIgnore
    @JSONField(serialize = false)
    public String logicValidDelCond;
    @JsonIgnore
    @JSONField(serialize = false)
    public synchronized String getLogicValidCond()
    {
        if(isLogicValid()&&logicValidCond==null)
        {
            if(logicVal==null)
                logicVal="1";
            if(logicDelVal==null)
                logicDelVal="0";
            logicValidCond  =  this.getLogicValidColumn().getName()+"=";
            logicValidDelCond = this.getLogicValidColumn().getName()+"=";
            if(this.getLogicValidColumn().isText()) {
                logicValidCond += ("'" + logicVal + "'");
                logicValidDelCond += ("'" + logicDelVal + "'");
            }
            else {
                logicValidCond += (logicVal + " ");
                logicValidDelCond += (logicDelVal + " ");
            }
        }
        else
            logicValidCond="";
        return logicValidCond;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private String baseColumns;
    @JsonIgnore
    @JSONField(serialize = false)
    public String getBaseColumns()
    {
        if(StringUtils.isEmpty(baseColumns))
        {
            String cols="";
            if(columns!=null)
            {
                for(Column col:columns)
                {
                    if(!StringUtils.isEmpty(cols))
                        cols+=",";
                    cols+=(col.getName());
                    if((!StringUtils.isEmpty(col.getAlias()))&&col.getAlias().equalsIgnoreCase(col.getName()))
                        cols+=(" as "+col.getAlias());
                }
            }
            else
                cols="*";
            baseColumns=cols;
        }
        return baseColumns;
    }




    @JSONField(serialize = false)
    @JsonIgnore
    private boolean built = false;

    @JSONField(serialize = false)
    @JsonIgnore
    public synchronized POSchema build()
    {
        if(!built)
        {
            getColumnMaps();
            getBaseColumns();
            getLastModifyField();
            getCreateTimeColumn();
            getLogicValidColumn();
            getLogicValidCond();
            getTenantColumn();
            built=true;
        }
        return this;
    }

}
