package liquibase.diff.output.changelog.core;

import liquibase.change.Change;
import liquibase.change.ColumnConfig;
import liquibase.change.ConstraintsConfig;
import liquibase.change.core.CreateTableChange;
import liquibase.database.Database;
import liquibase.database.core.InformixDatabase;
import liquibase.database.core.MSSQLDatabase;
import liquibase.database.core.MySQLDatabase;
import liquibase.datatype.DataTypeFactory;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.datatype.core.DateTimeType;
import liquibase.diff.output.DiffOutputControl;
import liquibase.diff.output.changelog.ChangeGeneratorChain;
import liquibase.diff.output.changelog.MissingObjectChangeGenerator;
import liquibase.statement.DatabaseFunction;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Column;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Table;

import java.util.Date;

public class MissingTableChangeGenerator implements MissingObjectChangeGenerator {
    @Override
    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
        if (Table.class.isAssignableFrom(objectType)) {
            return PRIORITY_DEFAULT;
        }
        return PRIORITY_NONE;
    }

    @Override
    public Class<? extends DatabaseObject>[] runAfterTypes() {
        return null;
    }

    @Override
    public Class<? extends DatabaseObject>[] runBeforeTypes() {
        return null;
    }

    @Override
    public Change[] fixMissing(DatabaseObject missingObject, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) {
        Table missingTable = (Table) missingObject;

        PrimaryKey primaryKey = missingTable.getPrimaryKey();

//        if (control.diffResult.getReferenceSnapshot().getDatabase().isLiquibaseTable(missingTable.getSchema().toCatalogAndSchema(), missingTable.getName())) {
//            continue;
//        }

        CreateTableChange change = createCreateTableChange();
        change.setTableName(missingTable.getName());
        if (control.getIncludeCatalog()) {
            change.setCatalogName(missingTable.getSchema().getCatalogName());
        }
        if (control.getIncludeSchema()) {
            change.setSchemaName(missingTable.getSchema().getName());
        }
        if (missingTable.getRemarks() != null) {
            change.setRemarks(missingTable.getRemarks());
        }

        for (Column column : missingTable.getColumns()) {
            ColumnConfig columnConfig = new ColumnConfig();
            columnConfig.setName(column.getName());
            LiquibaseDataType ldt = DataTypeFactory.getInstance().from(column.getType(), comparisonDatabase);
            DatabaseDataType ddt = ldt.toDatabaseDataType(referenceDatabase);
            String typeString = ddt.toString();
            if (referenceDatabase instanceof MSSQLDatabase) {
                typeString = referenceDatabase.unescapeDataTypeString(typeString);
            }
            columnConfig.setType(typeString);

            if (column.isAutoIncrement()) {
                columnConfig.setAutoIncrement(true);
            }

            ConstraintsConfig constraintsConfig = null;
            // In MySQL, the primary key must be specified at creation for an autoincrement column
            if (column.isAutoIncrement() && primaryKey != null && primaryKey.getColumnNamesAsList().contains(column.getName())) {
                constraintsConfig = new ConstraintsConfig();
                constraintsConfig.setPrimaryKey(true);
                constraintsConfig.setPrimaryKeyTablespace(primaryKey.getTablespace());
                // MySQL sets some primary key names as PRIMARY which is invalid
                if (comparisonDatabase instanceof MySQLDatabase && "PRIMARY".equals(primaryKey.getName())) {
                    constraintsConfig.setPrimaryKeyName(null);
                } else  {
                    constraintsConfig.setPrimaryKeyName(primaryKey.getName());
                }
                control.setAlreadyHandledMissing(primaryKey);
                control.setAlreadyHandledMissing(primaryKey.getBackingIndex());
            } else if (column.isNullable() != null && !column.isNullable()) {
                constraintsConfig = new ConstraintsConfig();
                constraintsConfig.setNullable(false);
            }

            if (constraintsConfig != null) {
                columnConfig.setConstraints(constraintsConfig);
            }

            setDefaultValue(columnConfig, column, referenceDatabase);

            if (column.getRemarks() != null) {
                columnConfig.setRemarks(column.getRemarks());
            }

            change.addColumn(columnConfig);
            control.setAlreadyHandledMissing(column);
        }


        return new Change[] {
                change
        };
    }

    public static void setDefaultValue(ColumnConfig columnConfig, Column column, Database database) {
        LiquibaseDataType dataType = DataTypeFactory.getInstance().from(column.getType(), database);

        Object defaultValue = column.getDefaultValue();
        if (defaultValue == null) {
            // do nothing
        } else if (column.isAutoIncrement()) {
            // do nothing
        } else if (defaultValue instanceof Date) {
            columnConfig.setDefaultValueDate((Date) defaultValue);
        } else if (defaultValue instanceof Boolean) {
            columnConfig.setDefaultValueBoolean(((Boolean) defaultValue));
        } else if (defaultValue instanceof Number) {
            columnConfig.setDefaultValueNumeric(((Number) defaultValue));
        } else if (defaultValue instanceof DatabaseFunction) {

            DatabaseFunction function = (DatabaseFunction) defaultValue;
            if ("current".equals(function.getValue())) {
              if (database instanceof InformixDatabase) {
                if (dataType instanceof DateTimeType) {
                  if (dataType.getAdditionalInformation() == null || dataType.getAdditionalInformation().length() == 0) {
                    if (dataType.getParameters() != null && dataType.getParameters().length > 0) {

                      String parameter = String.valueOf(dataType.getParameters()[0]);

                      if ("4365".equals(parameter)) {
                        function = new DatabaseFunction("current year to fraction(3)");
                      }

                      if ("3594".equals(parameter)) {
                        function = new DatabaseFunction("current year to second");
                      }

                      if ("3080".equals(parameter)) {
                        function = new DatabaseFunction("current year to minute");
                      }

                      if ("2052".equals(parameter)) {
                        function = new DatabaseFunction("current year to day");
                      }
                    }
                  }
                }
              }
            }

            columnConfig.setDefaultValueComputed(function);
        } else {
            columnConfig.setDefaultValue(defaultValue.toString());
        }
    }

    protected CreateTableChange createCreateTableChange() {
        return new CreateTableChange();
    }
}
