import type { ConstraintInfo } from '#/api/core/database-manager';

export interface TableConstraintDefinition {
  name: string;
  type: string;
  definition?: string;
  columns?: string[];
  referencedTable?: string;
  referencedColumns?: string[];
  onDelete?: string;
  onUpdate?: string;
}

export const FOREIGN_KEY_ACTIONS = [
  'NO ACTION',
  'RESTRICT',
  'CASCADE',
  'SET NULL',
  'SET DEFAULT',
] as const;

export type ForeignKeyAction = (typeof FOREIGN_KEY_ACTIONS)[number];

export function normalizeForeignKeyAction(value?: string): string {
  if (!value) {
    return '';
  }
  return value.toUpperCase().replaceAll(/\s+/g, ' ').trim();
}

export function parseForeignKeyActions(definition?: string): {
  onDelete: string;
  onUpdate: string;
} {
  const result = { onDelete: '', onUpdate: '' };
  if (!definition) {
    return result;
  }

  const onDeleteMatch = definition.match(
    /\sON DELETE (NO ACTION|RESTRICT|CASCADE|SET NULL|SET DEFAULT)\b/i,
  );
  const onUpdateMatch = definition.match(
    /\sON UPDATE (NO ACTION|RESTRICT|CASCADE|SET NULL|SET DEFAULT)\b/i,
  );

  if (onDeleteMatch?.[1]) {
    result.onDelete = normalizeForeignKeyAction(onDeleteMatch[1]);
  }
  if (onUpdateMatch?.[1]) {
    result.onUpdate = normalizeForeignKeyAction(onUpdateMatch[1]);
  }

  return result;
}

function buildForeignKeyActionsSql(
  constraint: TableConstraintDefinition,
  dbType?: string,
): string {
  const db = (dbType || 'postgresql').toLowerCase();
  const supportsOnUpdate = db !== 'sqlserver' && db !== 'oracle' && db !== 'mssql';

  const parts: string[] = [];
  if (constraint.onDelete) {
    parts.push(`ON DELETE ${constraint.onDelete}`);
  }
  if (supportsOnUpdate && constraint.onUpdate) {
    parts.push(`ON UPDATE ${constraint.onUpdate}`);
  }
  return parts.length ? ` ${parts.join(' ')}` : '';
}

export function normalizeConstraintType(type: string): string {
  const normalized = type.toLowerCase().replaceAll('_', ' ').trim();
  if (normalized === 'primary key' || normalized === 'primary') {
    return 'primary';
  }
  if (normalized === 'foreign key' || normalized === 'foreign') {
    return 'foreign';
  }
  if (normalized === 'unique') {
    return 'unique';
  }
  if (normalized === 'check') {
    return 'check';
  }
  return normalized;
}

export function parseForeignKeyFromDefinition(definition?: string): {
  columns: string[];
  referencedTable: string;
  referencedColumns: string[];
} {
  const empty = { columns: [], referencedTable: '', referencedColumns: [] };
  if (!definition?.trim()) {
    return empty;
  }

  const match = definition.match(
    /FOREIGN KEY\s*\(([^)]+)\)\s*REFERENCES\s*(?:"?([\w$]+)"?\.)?"?([\w$]+)"?\s*\(([^)]+)\)/i,
  );
  if (!match) {
    return empty;
  }

  const parseColumnList = (value: string) =>
    value
      .split(',')
      .map((column) => column.trim().replaceAll(/^"|"$/g, ''))
      .filter(Boolean);

  return {
    columns: parseColumnList(match[1]),
    referencedTable: match[3],
    referencedColumns: parseColumnList(match[4]),
  };
}

export function inferForeignKeyLocalColumnFromName(
  constraintName?: string,
): string | undefined {
  if (!constraintName?.trim()) {
    return undefined;
  }
  const match = constraintName.trim().match(/^fk_(.+)$/i);
  return match?.[1];
}

export function parseConstraintColumnsFromApi(
  type: string,
  definition?: string,
  constraintName?: string,
  apiColumns?: string[],
): { columns: string[]; definition: string } {
  const columns = apiColumns?.filter(Boolean) || [];
  let normalizedDefinition = definition?.trim() || '';

  if (columns.length) {
    return { columns, definition: normalizedDefinition };
  }

  if (normalizedDefinition) {
    const checkNotNullMatch = normalizedDefinition.match(
      /CHECK\s*\(\(?\s*"?(\w+)"?\s+IS NOT NULL\s*\)?\)?/i,
    );
    if (checkNotNullMatch?.[1]) {
      const column = checkNotNullMatch[1];
      return {
        columns: [column],
        definition: `${column} IS NOT NULL`,
      };
    }

    const notNullPrefixMatch = normalizedDefinition.match(/^NOT NULL\s+"?(\w+)"?$/i);
    if (notNullPrefixMatch?.[1]) {
      const column = notNullPrefixMatch[1];
      return {
        columns: [column],
        definition: `${column} IS NOT NULL`,
      };
    }

    const isNotNullMatch = normalizedDefinition.match(/^"?(\w+)"?\s+IS NOT NULL$/i);
    if (isNotNullMatch?.[1]) {
      return {
        columns: [isNotNullMatch[1]],
        definition: normalizedDefinition,
      };
    }
  }

  if (normalizeConstraintType(type) === 'check' && constraintName) {
    const nameMatch = constraintName.match(/_(\w+)_not_null$/i);
    if (nameMatch?.[1]) {
      const column = nameMatch[1];
      return {
        columns: [column],
        definition: normalizedDefinition || `${column} IS NOT NULL`,
      };
    }
  }

  return { columns: [], definition: normalizedDefinition };
}

export function mapConstraintFromApi(
  con: ConstraintInfo,
): TableConstraintDefinition {
  const type = normalizeConstraintType(con.constraint_type || 'check');
  const actions =
    type === 'foreign'
      ? parseForeignKeyActions(con.definition)
      : { onDelete: '', onUpdate: '' };

  if (type === 'foreign') {
    const fkFromDef = parseForeignKeyFromDefinition(con.definition);
    const apiColumns = con.columns?.split(', ').filter(Boolean) || [];
    const apiReferencedColumns =
      con.referenced_columns?.split(', ').filter(Boolean) || [];

    let columns = fkFromDef.columns.length ? fkFromDef.columns : apiColumns;
    let referencedTable = fkFromDef.referencedTable || con.referenced_table || '';
    let referencedColumns = fkFromDef.referencedColumns.length
      ? fkFromDef.referencedColumns
      : apiReferencedColumns;

    if (!columns.length) {
      const inferred = inferForeignKeyLocalColumnFromName(con.constraint_name);
      if (inferred) {
        columns = [inferred];
      }
    }

    return {
      name: con.constraint_name,
      type,
      definition: con.definition || '',
      columns,
      referencedTable,
      referencedColumns,
      onDelete: actions.onDelete,
      onUpdate: actions.onUpdate,
    };
  }

  const apiColumns = con.columns?.split(', ').filter(Boolean) || [];
  const parsed = parseConstraintColumnsFromApi(
    type,
    con.definition,
    con.constraint_name,
    apiColumns,
  );

  return {
    name: con.constraint_name,
    type,
    definition: parsed.definition,
    columns: parsed.columns,
    referencedTable: con.referenced_table || '',
    referencedColumns:
      con.referenced_columns?.split(', ').filter(Boolean) || [],
    onDelete: actions.onDelete,
    onUpdate: actions.onUpdate,
  };
}

function quoteIdentifier(name: string, dbType: string): string {
  if (dbType === 'mysql') {
    return `\`${name}\``;
  }
  if (dbType === 'sqlserver' || dbType === 'sql server') {
    return `[${name}]`;
  }
  if (dbType === 'oracle' || dbType === 'postgresql') {
    return `"${name.replace(/"/g, '""')}"`;
  }
  return `"${name}"`;
}

function quoteTableRef(
  tableName: string,
  schema: string | undefined,
  dbType: string,
): string {
  if (tableName.includes('.')) {
    const [schemaName, pureTableName] = tableName.split('.', 2);
    return `${quoteIdentifier(schemaName, dbType)}.${quoteIdentifier(pureTableName, dbType)}`;
  }
  if (schema) {
    return `${quoteIdentifier(schema, dbType)}.${quoteIdentifier(tableName, dbType)}`;
  }
  return quoteIdentifier(tableName, dbType);
}

function quoteColumnList(columns: string[] | undefined, dbType: string): string {
  return (columns || [])
    .map((column) => quoteIdentifier(column, dbType))
    .join(', ');
}

export function buildCreateTableConstraintSql(
  constraint: TableConstraintDefinition,
  dbType: string,
  schema?: string,
): string {
  const type = normalizeConstraintType(constraint.type);
  const columns = quoteColumnList(constraint.columns, dbType);
  let sql = `  CONSTRAINT ${quoteIdentifier(constraint.name, dbType)}`;

  switch (type) {
    case 'primary': {
      sql += ` PRIMARY KEY (${columns})`;
      break;
    }
    case 'foreign': {
      const refTable = quoteTableRef(
        constraint.referencedTable || '',
        schema,
        dbType,
      );
      const refColumns = quoteColumnList(constraint.referencedColumns, dbType);
      sql += ` FOREIGN KEY (${columns}) REFERENCES ${refTable} (${refColumns})${buildForeignKeyActionsSql(constraint, dbType)}`;
      break;
    }
    case 'unique': {
      sql += ` UNIQUE (${columns})`;
      break;
    }
    case 'check': {
      sql += ` CHECK (${constraint.definition || ''})`;
      break;
    }
    default: {
      return '';
    }
  }

  return sql;
}

export function buildAddConstraintSql(
  constraint: TableConstraintDefinition,
  tableRef: string,
  dbType: string,
  schema?: string,
): string {
  const type = normalizeConstraintType(constraint.type);
  const columns = quoteColumnList(constraint.columns, dbType);
  let sql = `ALTER TABLE ${tableRef} ADD CONSTRAINT ${quoteIdentifier(constraint.name, dbType)}`;

  switch (type) {
    case 'primary': {
      sql += ` PRIMARY KEY (${columns})`;
      break;
    }
    case 'foreign': {
      const refTable = quoteTableRef(
        constraint.referencedTable || '',
        schema,
        dbType,
      );
      const refColumns = quoteColumnList(constraint.referencedColumns, dbType);
      sql += ` FOREIGN KEY (${columns}) REFERENCES ${refTable} (${refColumns})${buildForeignKeyActionsSql(constraint, dbType)}`;
      break;
    }
    case 'unique': {
      sql += ` UNIQUE (${columns})`;
      break;
    }
    case 'check': {
      sql += ` CHECK (${constraint.definition || ''})`;
      break;
    }
    default: {
      break;
    }
  }

  return `${sql};`;
}

export function buildDropConstraintSql(
  constraint: TableConstraintDefinition,
  tableRef: string,
  dbType: string,
): string {
  const name = quoteIdentifier(constraint.name, dbType);
  if (dbType === 'mysql' && normalizeConstraintType(constraint.type) === 'foreign') {
    return `ALTER TABLE ${tableRef} DROP FOREIGN KEY ${name};`;
  }
  return `ALTER TABLE ${tableRef} DROP CONSTRAINT ${name};`;
}

function normalizeConstraintForCompare(
  constraint: TableConstraintDefinition,
): TableConstraintDefinition {
  return {
    name: constraint.name.trim(),
    type: normalizeConstraintType(constraint.type),
    definition: constraint.definition?.trim() || '',
    columns: [...(constraint.columns || [])],
    referencedTable: constraint.referencedTable?.trim() || '',
    referencedColumns: [...(constraint.referencedColumns || [])],
    onDelete: normalizeForeignKeyAction(constraint.onDelete),
    onUpdate: normalizeForeignKeyAction(constraint.onUpdate),
  };
}

export function constraintEquals(
  left: TableConstraintDefinition,
  right: TableConstraintDefinition,
): boolean {
  return (
    JSON.stringify(normalizeConstraintForCompare(left)) ===
    JSON.stringify(normalizeConstraintForCompare(right))
  );
}

export function buildConstraintAlterSql(
  originalConstraints: TableConstraintDefinition[],
  currentConstraints: TableConstraintDefinition[],
  tableRef: string,
  dbType: string,
  schema?: string,
): string[] {
  const statements: string[] = [];
  const originalMap = new Map(
    originalConstraints.map((item) => [item.name, item]),
  );
  const currentMap = new Map(currentConstraints.map((item) => [item.name, item]));

  for (const original of originalConstraints) {
    const current = currentMap.get(original.name);
    if (!current) {
      statements.push(buildDropConstraintSql(original, tableRef, dbType));
      continue;
    }
    if (!constraintEquals(original, current)) {
      statements.push(buildDropConstraintSql(original, tableRef, dbType));
      statements.push(buildAddConstraintSql(current, tableRef, dbType, schema));
    }
  }

  for (const current of currentConstraints) {
    if (!originalMap.has(current.name)) {
      statements.push(buildAddConstraintSql(current, tableRef, dbType, schema));
    }
  }

  return statements;
}

export interface ConstraintValidateResult {
  valid: boolean;
  errorKey?:
    | 'constraintNameRequired'
    | 'constraintColumnsRequired'
    | 'constraintCheckDefinitionRequired'
    | 'constraintReferencedTableRequired'
    | 'constraintReferencedColumnsRequired'
    | 'constraintReferencedColumnsMismatch';
}

export function validateConstraints(
  constraints: TableConstraintDefinition[],
): ConstraintValidateResult {
  for (const constraint of constraints) {
    if (!constraint.name?.trim()) {
      return { valid: false, errorKey: 'constraintNameRequired' };
    }

    if (!constraint.columns?.length) {
      return { valid: false, errorKey: 'constraintColumnsRequired' };
    }

    const type = normalizeConstraintType(constraint.type);
    if (type === 'check' && !constraint.definition?.trim()) {
      return { valid: false, errorKey: 'constraintCheckDefinitionRequired' };
    }

    if (type === 'foreign') {
      if (!constraint.referencedTable?.trim()) {
        return { valid: false, errorKey: 'constraintReferencedTableRequired' };
      }
      if (!constraint.referencedColumns?.length) {
        return {
          valid: false,
          errorKey: 'constraintReferencedColumnsRequired',
        };
      }
      if (constraint.columns.length !== constraint.referencedColumns.length) {
        return {
          valid: false,
          errorKey: 'constraintReferencedColumnsMismatch',
        };
      }
    }
  }

  return { valid: true };
}
