import { mapToDbType } from '#/utils/database-types';
import {
  quoteIdentifier,
  quoteTable,
} from '#/views/_core/database-manager/utils/sql-identifier';

export interface ColumnFieldDefinition {
  name: string;
  type: string;
  length?: number;
  precision?: number;
  scale?: number;
  nullable: boolean;
  default?: string;
  primaryKey: boolean;
  unique: boolean;
  comment?: string;
}

function normalizeDbType(dbType: string): string {
  const db = (dbType || 'postgresql').toLowerCase();
  if (db === 'sql server' || db === 'mssql') {
    return 'sqlserver';
  }
  return db;
}

function escapeSqlString(value: string): string {
  return value.replaceAll("'", "''");
}

function buildTypeDef(field: ColumnFieldDefinition, dbType: string): string {
  const db = normalizeDbType(dbType);
  let typeDef = mapToDbType(field.type, dbType);

  const typesWithLength = [
    'varchar',
    'char',
    'nvarchar',
    'nchar',
    'varbinary',
    'binary',
  ];
  const typesWithPrecision = ['decimal', 'numeric'];
  const mysqlTypesWithPrecision = ['float'];
  const lowerType = field.type.toLowerCase();

  if (
    typesWithLength.includes(lowerType) &&
    field.length &&
    field.length > 0
  ) {
    typeDef += `(${field.length})`;
  } else if (typesWithPrecision.includes(lowerType) && field.precision) {
    typeDef += field.scale
      ? `(${field.precision}, ${field.scale})`
      : `(${field.precision})`;
  } else if (
    db === 'mysql' &&
    mysqlTypesWithPrecision.includes(lowerType) &&
    field.precision
  ) {
    typeDef += field.scale
      ? `(${field.precision}, ${field.scale})`
      : `(${field.precision})`;
  }

  return typeDef;
}

export function buildColumnDefinition(
  field: ColumnFieldDefinition,
  dbType: string,
): string {
  const db = normalizeDbType(dbType);
  const col = quoteIdentifier(field.name, db);
  let def = `${col} ${buildTypeDef(field, dbType)}`;

  if (!field.nullable) {
    def += ' NOT NULL';
  }

  if (field.default) {
    def += ` DEFAULT ${field.default}`;
  }

  if (field.comment && db === 'mysql') {
    def += ` COMMENT '${escapeSqlString(field.comment)}'`;
  }

  return def;
}

function buildSqlServerColumnExtendedProperty(
  tableName: string,
  columnName: string,
  comment: string,
  schema: string,
  isUpdate: boolean,
): string {
  const proc = isUpdate ? 'sp_updateextendedproperty' : 'sp_addextendedproperty';
  return `EXEC ${proc} @name=N'MS_Description', @value=N'${escapeSqlString(comment)}', @level0type=N'SCHEMA', @level0name=N'${escapeSqlString(schema)}', @level1type=N'TABLE', @level1name=N'${escapeSqlString(tableName)}', @level2type=N'COLUMN', @level2name=N'${escapeSqlString(columnName)}';`;
}

function buildSqlServerDropDefaultSql(
  tableRef: string,
  tableName: string,
  columnName: string,
  schema: string,
): string {
  const schemaName = escapeSqlString(schema || 'dbo');
  const table = escapeSqlString(tableName);
  const column = escapeSqlString(columnName);
  return `DECLARE @sql NVARCHAR(MAX);
SELECT @sql = N'ALTER TABLE ${tableRef} DROP CONSTRAINT ' + QUOTENAME(dc.name)
FROM sys.default_constraints dc
INNER JOIN sys.columns col ON dc.parent_object_id = col.object_id AND dc.parent_column_id = col.column_id
INNER JOIN sys.tables tab ON col.object_id = tab.object_id
INNER JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
WHERE sch.name = N'${schemaName}' AND tab.name = N'${table}' AND col.name = N'${column}';
IF @sql IS NOT NULL EXEC sp_executesql @sql;`;
}

function buildSqlServerTableExtendedProperty(
  tableName: string,
  comment: string,
  schema: string,
  isUpdate: boolean,
): string {
  const proc = isUpdate ? 'sp_updateextendedproperty' : 'sp_addextendedproperty';
  return `EXEC ${proc} @name=N'MS_Description', @value=N'${escapeSqlString(comment)}', @level0type=N'SCHEMA', @level0name=N'${escapeSqlString(schema)}', @level1type=N'TABLE', @level1name=N'${escapeSqlString(tableName)}';`;
}

export function buildAddColumnSql(
  tableRef: string,
  field: ColumnFieldDefinition,
  dbType: string,
  options?: { schema?: string; tableName?: string; hadComment?: boolean },
): string[] {
  const db = normalizeDbType(dbType);
  const columnDef = buildColumnDefinition(field, dbType);
  const statements: string[] = [];

  switch (db) {
    case 'mysql': {
      statements.push(`ALTER TABLE ${tableRef} ADD COLUMN ${columnDef};`);
      break;
    }
    case 'sqlserver': {
      statements.push(`ALTER TABLE ${tableRef} ADD ${columnDef};`);
      if (field.comment && options?.tableName) {
        statements.push(
          buildSqlServerColumnExtendedProperty(
            options.tableName,
            field.name,
            field.comment,
            options.schema || 'dbo',
            false,
          ),
        );
      }
      break;
    }
    case 'oracle': {
      statements.push(`ALTER TABLE ${tableRef} ADD (${columnDef});`);
      if (field.comment) {
        statements.push(
          `COMMENT ON COLUMN ${tableRef}.${quoteIdentifier(field.name, db)} IS '${escapeSqlString(field.comment)}';`,
        );
      }
      break;
    }
    case 'postgresql':
    default: {
      statements.push(`ALTER TABLE ${tableRef} ADD COLUMN ${columnDef};`);
      if (field.comment) {
        statements.push(
          `COMMENT ON COLUMN ${tableRef}.${quoteIdentifier(field.name, db)} IS '${escapeSqlString(field.comment)}';`,
        );
      }
      break;
    }
  }

  return statements;
}

export function buildDropColumnSql(
  tableRef: string,
  columnName: string,
  dbType: string,
): string {
  const db = normalizeDbType(dbType);
  return `ALTER TABLE ${tableRef} DROP COLUMN ${quoteIdentifier(columnName, db)};`;
}

export function buildTableCommentAlterSql(
  tableRef: string,
  tableName: string,
  comment: string,
  dbType: string,
  schema?: string,
  hadComment?: boolean,
): string | null {
  const db = normalizeDbType(dbType);
  const escaped = escapeSqlString(comment);

  switch (db) {
    case 'postgresql':
    case 'oracle': {
      return `COMMENT ON TABLE ${tableRef} IS '${escaped}';`;
    }
    case 'mysql': {
      return `ALTER TABLE ${tableRef} COMMENT = '${escaped}';`;
    }
    case 'sqlserver': {
      return buildSqlServerTableExtendedProperty(
        tableName,
        comment,
        schema || 'dbo',
        Boolean(hadComment),
      );
    }
    default: {
      return null;
    }
  }
}

export function buildColumnCommentAlterSql(
  tableRef: string,
  tableName: string,
  field: ColumnFieldDefinition,
  dbType: string,
  schema?: string,
  hadComment?: boolean,
): string[] {
  const db = normalizeDbType(dbType);
  const col = quoteIdentifier(field.name, db);
  const comment = field.comment || '';
  const statements: string[] = [];

  switch (db) {
    case 'postgresql':
    case 'oracle': {
      statements.push(
        `COMMENT ON COLUMN ${tableRef}.${col} IS '${escapeSqlString(comment)}';`,
      );
      break;
    }
    case 'mysql': {
      const columnDef = buildColumnDefinition(field, dbType);
      statements.push(`ALTER TABLE ${tableRef} MODIFY COLUMN ${columnDef};`);
      break;
    }
    case 'sqlserver': {
      statements.push(
        buildSqlServerColumnExtendedProperty(
          tableName,
          field.name,
          comment,
          schema || 'dbo',
          Boolean(hadComment),
        ),
      );
      break;
    }
    default: {
      break;
    }
  }

  return statements;
}

export function buildFieldAlterSql(
  originalFields: ColumnFieldDefinition[],
  currentFields: ColumnFieldDefinition[],
  tableRef: string,
  tableName: string,
  dbType: string,
  schema?: string,
): string[] {
  const db = normalizeDbType(dbType);
  const statements: string[] = [];
  const colRef = (name: string) => quoteIdentifier(name, db);

  for (const origField of originalFields) {
    if (!currentFields.find((f) => f.name === origField.name)) {
      statements.push(buildDropColumnSql(tableRef, origField.name, dbType));
    }
  }

  for (const field of currentFields) {
    if (!originalFields.find((f) => f.name === field.name)) {
      statements.push(
        ...buildAddColumnSql(tableRef, field, dbType, {
          schema,
          tableName,
        }),
      );
    }
  }

  for (const field of currentFields) {
    const origField = originalFields.find((f) => f.name === field.name);
    if (!origField || JSON.stringify(field) === JSON.stringify(origField)) {
      continue;
    }

    const col = colRef(field.name);
    const typeDef = buildTypeDef(field, dbType);
    const origTypeDef = buildTypeDef(origField, dbType);

    if (db === 'postgresql') {
      const typeChanged =
        field.type !== origField.type ||
        field.length !== origField.length ||
        field.precision !== origField.precision ||
        field.scale !== origField.scale;

      if (typeChanged) {
        statements.push(
          `ALTER TABLE ${tableRef} ALTER COLUMN ${col} TYPE ${typeDef} USING ${col}::${typeDef};`,
        );
      }
      if (field.nullable !== origField.nullable) {
        statements.push(
          `ALTER TABLE ${tableRef} ALTER COLUMN ${col} ${field.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'};`,
        );
      }
      if (field.default !== origField.default) {
        if (field.default) {
          statements.push(
            `ALTER TABLE ${tableRef} ALTER COLUMN ${col} SET DEFAULT ${field.default};`,
          );
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} ALTER COLUMN ${col} DROP DEFAULT;`,
          );
        }
      }
      if (field.unique !== origField.unique) {
        if (field.unique) {
          statements.push(
            `ALTER TABLE ${tableRef} ADD CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)} UNIQUE (${col});`,
          );
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP CONSTRAINT IF EXISTS ${colRef(`${tableName}_${field.name}_key`)};`,
          );
        }
      }
      if (field.primaryKey !== origField.primaryKey) {
        if (field.primaryKey) {
          statements.push(
            `ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`,
          );
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP CONSTRAINT IF EXISTS ${colRef(`${tableName}_pkey`)};`,
          );
        }
      }
      if (field.comment !== origField.comment) {
        statements.push(
          ...buildColumnCommentAlterSql(
            tableRef,
            tableName,
            field,
            dbType,
            schema,
            Boolean(origField.comment),
          ),
        );
      }
    } else if (db === 'mysql') {
      statements.push(
        `ALTER TABLE ${tableRef} MODIFY COLUMN ${buildColumnDefinition(field, dbType)};`,
      );

      if (field.unique !== origField.unique) {
        if (field.unique) {
          statements.push(
            `ALTER TABLE ${tableRef} ADD UNIQUE INDEX ${colRef(`${tableName}_${field.name}_key`)} (${col});`,
          );
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP INDEX IF EXISTS ${colRef(`${tableName}_${field.name}_key`)};`,
          );
        }
      }
      if (field.primaryKey !== origField.primaryKey) {
        if (field.primaryKey) {
          statements.push(`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`);
        } else {
          statements.push(`ALTER TABLE ${tableRef} DROP PRIMARY KEY;`);
        }
      }
    } else if (db === 'sqlserver') {
      if (typeDef !== origTypeDef) {
        let alterDef = `${col} ${typeDef}`;
        alterDef += field.nullable ? ' NULL' : ' NOT NULL';
        statements.push(`ALTER TABLE ${tableRef} ALTER COLUMN ${alterDef};`);
      } else if (field.nullable !== origField.nullable) {
        let alterDef = `${col} ${typeDef}`;
        alterDef += field.nullable ? ' NULL' : ' NOT NULL';
        statements.push(`ALTER TABLE ${tableRef} ALTER COLUMN ${alterDef};`);
      }

      if (field.default !== origField.default) {
        if (field.default) {
          statements.push(
            `ALTER TABLE ${tableRef} ADD DEFAULT ${field.default} FOR ${col};`,
          );
        } else if (origField.default) {
          statements.push(
            buildSqlServerDropDefaultSql(
              tableRef,
              tableName,
              field.name,
              schema || 'dbo',
            ),
          );
        }
      }

      if (field.comment !== origField.comment) {
        statements.push(
          ...buildColumnCommentAlterSql(
            tableRef,
            tableName,
            field,
            dbType,
            schema,
            Boolean(origField.comment),
          ),
        );
      }

      if (field.unique !== origField.unique) {
        if (field.unique) {
          statements.push(
            `ALTER TABLE ${tableRef} ADD CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)} UNIQUE (${col});`,
          );
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)};`,
          );
        }
      }
      if (field.primaryKey !== origField.primaryKey) {
        if (field.primaryKey) {
          statements.push(`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`);
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_pkey`)};`,
          );
        }
      }
    } else if (db === 'oracle') {
      const structureChanged =
        typeDef !== origTypeDef ||
        field.nullable !== origField.nullable ||
        field.default !== origField.default;

      if (structureChanged) {
        let modifyDef = `${col} ${typeDef}`;
        modifyDef += field.nullable ? ' NULL' : ' NOT NULL';
        if (field.default !== origField.default) {
          modifyDef += field.default
            ? ` DEFAULT ${field.default}`
            : ' DEFAULT NULL';
        }
        statements.push(`ALTER TABLE ${tableRef} MODIFY (${modifyDef});`);
      }

      if (field.comment !== origField.comment) {
        statements.push(
          ...buildColumnCommentAlterSql(
            tableRef,
            tableName,
            field,
            dbType,
            schema,
            Boolean(origField.comment),
          ),
        );
      }

      if (field.unique !== origField.unique) {
        if (field.unique) {
          statements.push(
            `ALTER TABLE ${tableRef} ADD CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)} UNIQUE (${col});`,
          );
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)};`,
          );
        }
      }
      if (field.primaryKey !== origField.primaryKey) {
        if (field.primaryKey) {
          statements.push(`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`);
        } else {
          statements.push(
            `ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_pk`)};`,
          );
        }
      }
    }
  }

  return statements;
}

/** 建表后追加表/列注释 SQL */
export function buildCreateTableCommentSql(
  tableName: string,
  schema: string | undefined,
  dbType: string,
  tableComment: string,
  fields: ColumnFieldDefinition[],
): string[] {
  const db = normalizeDbType(dbType);
  const tableRef = quoteTable(schema, tableName, db);
  const statements: string[] = [];

  if (tableComment) {
    switch (db) {
      case 'postgresql':
      case 'oracle': {
        statements.push(
          `COMMENT ON TABLE ${tableRef} IS '${escapeSqlString(tableComment)}';`,
        );
        break;
      }
      case 'mysql': {
        statements.push(
          `ALTER TABLE ${tableRef} COMMENT = '${escapeSqlString(tableComment)}';`,
        );
        break;
      }
      case 'sqlserver': {
        statements.push(
          buildSqlServerTableExtendedProperty(
            tableName,
            tableComment,
            schema || 'dbo',
            false,
          ),
        );
        break;
      }
      default: {
        break;
      }
    }
  }

  for (const field of fields) {
    if (!field.comment) {
      continue;
    }
    switch (db) {
      case 'postgresql':
      case 'oracle': {
        statements.push(
          `COMMENT ON COLUMN ${tableRef}.${quoteIdentifier(field.name, db)} IS '${escapeSqlString(field.comment)}';`,
        );
        break;
      }
      case 'sqlserver': {
        statements.push(
          buildSqlServerColumnExtendedProperty(
            tableName,
            field.name,
            field.comment,
            schema || 'dbo',
            false,
          ),
        );
        break;
      }
      default: {
        break;
      }
    }
  }

  return statements;
}
