package net.northking.cctp.maven.analyse.dialects;

import net.northking.cctp.maven.analyse.Constants;
import net.northking.cctp.maven.analyse.Messages;
import net.northking.cctp.maven.analyse.entity.ColumnRef;
import net.northking.cctp.maven.analyse.entity.ForeignKeyRef;
import net.northking.cctp.maven.analyse.entity.IndexInfoRef;
import net.northking.cctp.maven.analyse.entity.PrimaryKeyRef;
import net.northking.cctp.maven.analyse.entity.TableRef;
import net.northking.cctp.maven.analyse.util.DBUtil;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created on 2019/8/8
 *
 * @author xiusong.xie
 * @since 1.0
 */
public abstract class AbsDialect implements Dialect
{
  /**
   * 是否执行过“加载表结构”
   */
  protected static boolean exec = false;

  /**
   * 表结构集合
   */
  protected List<TableRef> tables = new ArrayList<>();

  /**
   * 数据库结构对象
   */
  protected DatabaseMetaData databaseMetaData;


  /**
   * 获取表结构定义
   *
   * @throws ClassNotFoundException ClassNotFoundException
   * @throws SQLException           SQLException
   * @throws InstantiationException InstantiationException
   * @throws IllegalAccessException IllegalAccessException
   */
  protected void loadTable(String schema, String[] tableNames)
      throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException
  {

    Connection connection = DBUtil.getInstance().getConnection();
    databaseMetaData = connection.getMetaData();
    ResultSet resultSet = databaseMetaData.getTables(connection.getCatalog(), schema, null,
        new String[]{Constants.TYPE_TABLE});
    while (resultSet.next())
    {
      String tableCat = connection.getCatalog();
      String tableSchema = schema;
      String tableName = resultSet.getString(Constants.TABLE_NAME);
      String tableType = resultSet.getString(Constants.TABLE_TYPE);

      if (tableNames != null && tableNames.length > 0)
      {
        for (String target : tableNames)
        {
          if (tableName.equalsIgnoreCase(target))
          {
            registerTable(resultSet, tableCat, tableSchema, tableName, tableType);
            break;
          }
        }
      } else
      {
        registerTable(resultSet, tableCat, tableSchema, tableName, tableType);
      }


    }
    resultSet.close();
  }

  private void registerTable(ResultSet resultSet, String tableCat, String tableSchema, String tableName, String tableType) throws SQLException
  {
    String remarks = findTableComment(resultSet);

    TableRef jdbcTable = new TableRef(tableCat, tableSchema, tableName, tableType, remarks);
    tables.add(jdbcTable);
  }

  /**
   * 获取表的注释
   *
   * @param resultSet 结果集
   * @return 表的注释
   * @throws SQLException
   */
  protected String findTableComment(ResultSet resultSet) throws SQLException
  {
//    String tableName = resultSet.getString(Constants.TABLE_NAME);
    String tableComment = resultSet.getString(Constants.REMARKS);
    if (tableComment == null)
    {
      tableComment = "";
    }
    return tableComment;
  }

  @Override
  public List<TableRef> loadTables(String schema, String[] tableNames) throws Exception
  {
//    loadTablesComment();
    loadTable(schema, tableNames);
    analyseTables();
    exec = true;
    return tables;
  }

  @Override
  public List<TableRef> getTables(String schema, String[] tableNames) throws Exception
  {
    if (exec)
    {
      return tables;
    } else
    {
      return loadTables(schema, tableNames);
    }
  }

  /**
   * 分析表结构
   *
   * @throws SQLException SQLException
   */
  protected void analyseTables() throws SQLException
  {
    for (TableRef table : tables)
    {
      analyseTable(table);
    }
  }

  /**
   * 分析表结构
   *
   * @param table 表定义
   * @throws SQLException SQLException
   */
  private void analyseTable(final TableRef table) throws SQLException
  {
    Messages.info("Print.AnalyseTable", table.getTableName(), table.getComment());
    table.addColumns(findColumns(table));
    table.setPrimaryKey(findPrimaryKey(table));
    table.addForeignKeys(findImportedKeys(table));
    table.addIndexInfos(findUniqueIndexs(table));
  }


  /**
   * 加载表字段定义
   *
   * @param table 表定义
   * @return 表字段定义
   * @throws SQLException SQLException
   */
  private List<ColumnRef> findColumns(final TableRef table) throws SQLException
  {
    List<ColumnRef> columnRefList = new ArrayList<>();
    ResultSet resultSet = databaseMetaData.getColumns(table.getCatalog(), table.getSchema(), table.getTableName(), null);
    while (resultSet.next())
    { // COLUMN_NAME
      String columnName = resultSet.getString(Constants.COLUMN_NAME);
      int type = resultSet.getInt(Constants.DATA_TYPE);
      String typeName = resultSet.getString(Constants.TYPE_NAME);
      int size = resultSet.getInt(Constants.COLUMN_SIZE);
      int digits = resultSet.getInt(Constants.DECIMAL_DIGITS);
      int nullAble = resultSet.getInt(Constants.NULLABLE);
      String defaultValue = resultSet.getString(Constants.COLUMN_DEF);
      String remarks = resultSet.getString(Constants.REMARKS);

      ColumnRef column = new ColumnRef(columnName, type, typeName, size, digits, nullAble, remarks, defaultValue);
      column.setTableName(table.getTableName());
      columnRefList.add(column);
      Messages.info("Print.AnalyseColumn", column);
    }
    return columnRefList;
  }

  /**
   * 加载表的主键
   *
   * @param table 表定义
   * @return 表的主键
   * @throws SQLException SQLException
   */
  private PrimaryKeyRef findPrimaryKey(final TableRef table) throws SQLException
  {
    ResultSet resultSet = databaseMetaData.getPrimaryKeys(table.getCatalog(), table.getSchema(), table.getTableName());
    PrimaryKeyRef primaryKeyRef = new PrimaryKeyRef();

    while (resultSet.next())
    { // COLUMN_NAME
      String columnName = resultSet.getString(Constants.COLUMN_NAME);
      String pkName = resultSet.getString(Constants.PK_NAME);
      primaryKeyRef.setPkName(pkName);
      primaryKeyRef.setColumnName(columnName);
    }
    Messages.info("Print.AnalysePK", primaryKeyRef);
    return primaryKeyRef;
  }

  /**
   * 分析表外键引用
   *
   * @param table 表定义
   * @return 表外键
   * @throws SQLException SQLException
   */
  private List<ForeignKeyRef> findImportedKeys(final TableRef table) throws SQLException
  {
    List<ForeignKeyRef> foreignKeyRefs = new ArrayList<>();
    ResultSet resultSet = databaseMetaData.getImportedKeys(table.getCatalog(), table.getSchema(), table.getTableName());
    while (resultSet.next())
    {
      String pkTableName = resultSet.getString(Constants.PKTABLE_NAME);
      String pkColumnName = resultSet.getString(Constants.PKCOLUMN_NAME);
      String fkTableName = resultSet.getString(Constants.FKTABLE_NAME);
      String fkColumnName = resultSet.getString(Constants.FKCOLUMN_NAME);
      String fkName = resultSet.getString(Constants.FK_NAME);
      ForeignKeyRef foreignKey = new ForeignKeyRef(pkTableName, pkColumnName, fkTableName, fkColumnName, fkName);
      foreignKeyRefs.add(foreignKey);
      Messages.info("Print.AnalyseFK", foreignKey);
    }
    return foreignKeyRefs;
  }

  private List<IndexInfoRef> findUniqueIndexs(final TableRef table) throws SQLException
  {
    Map<String, IndexInfoRef> map = new HashMap<>();
    ResultSet resultSet = databaseMetaData.getIndexInfo(table.getCatalog(), table.getSchema(), table.getTableName(), false, false);
    StringBuilder msg = new StringBuilder();
    while (resultSet.next())
    {
      if (resultSet.getBoolean("NON_UNIQUE"))
      {
        continue;
      }

      ColumnRef columnRef = table.findColumn(resultSet.getString("COLUMN_NAME"));
      String indexName = resultSet.getString("INDEX_NAME");
      IndexInfoRef indexInfoRef = map.get(indexName);
      if (indexInfoRef == null)
      {
        indexInfoRef = new IndexInfoRef();
        indexInfoRef.setTableName(table.getTableName());
        indexInfoRef.setIndexName(indexName);
        indexInfoRef.setNonUnique(resultSet.getBoolean("NON_UNIQUE"));
        indexInfoRef.setType(resultSet.getShort("TYPE"));
        indexInfoRef.setOrdinalPosition(resultSet.getShort("ORDINAL_POSITION"));
        indexInfoRef.setAscOrDesc(resultSet.getString("ASC_OR_DESC"));
        indexInfoRef.setCardinality(resultSet.getLong("CARDINALITY"));
        indexInfoRef.setPages(resultSet.getLong("PAGES"));
        indexInfoRef.appendColumn(columnRef);
        map.put(indexName, indexInfoRef);
      } else
      {
        indexInfoRef.appendColumn(columnRef);
      }
    }

    List<IndexInfoRef> indexInfoRefs = new ArrayList<>();
    for (Map.Entry<String, IndexInfoRef> entry : map.entrySet())
    {
      indexInfoRefs.add(entry.getValue());
    }
    return indexInfoRefs;
  }

}
