#!/usr/bin/env node

/**
 * PostgreSQL Schema 间数据迁移脚本
 * 
 * 功能:
 * - 迁移表数据（支持跨数据库和同数据库不同schema）
 * - 迁移视图定义（自动处理schema引用和依赖关系）
 * 
 * 使用方法:
 * yarn add pg
 * node migrate-between-pg-schemas.js
 * 
 * 环境变量配置:
 * 
 * 源数据库配置:
 * PG_HOST - PostgreSQL 主机地址
 * PG_PORT - PostgreSQL 端口 (默认: 5432)
 * PG_USER - PostgreSQL 用户名
 * PG_PASSWORD - PostgreSQL 密码
 * PG_DATABASE - PostgreSQL 数据库名
 * DATABASE_URL - 源数据库连接URL（可选，优先级高于单独配置）
 * PG_SCHEMA - 源 schema 名称 (默认: public)
 * 
 * 目标数据库配置（可选，如果不配置则在同一数据库的不同schema间迁移）:
 * PG_HOST_TARGET - 目标 PostgreSQL 主机地址
 * PG_PORT_TARGET - 目标 PostgreSQL 端口 (默认: 5432)
 * PG_USER_TARGET - 目标 PostgreSQL 用户名
 * PG_PASSWORD_TARGET - 目标 PostgreSQL 密码
 * PG_DATABASE_TARGET - 目标 PostgreSQL 数据库名
 * DATABASE_URL_TARGET - 目标数据库连接URL（可选，优先级高于单独配置）
 * PG_SCHEMA_TARGET - 目标 schema 名称 (必填)
 */

const { Pool } = require('pg');

// 批量插入大小
const BATCH_SIZE = 1000;
const MAX_ROWS_TO_MIGRATE = 5000;

// 解析源数据库配置
let sourcePgConfig = null;
let sourceSchema = process.env.PG_SCHEMA || 'public';

if (process.env.DATABASE_URL) {
  const url = new URL(process.env.DATABASE_URL);
  sourcePgConfig = {
    host: url.hostname,
    port: Number(url.port),
    user: url.username,
    password: url.password,
    database: url.pathname.slice(1),
  };
  if (!process.env.PG_SCHEMA) {
    sourceSchema = url.searchParams.get('schema') || 'public';
  }
} else {
  sourcePgConfig = {
    host: process.env.PG_HOST || 'localhost',
    port: Number(process.env.PG_PORT || 5432),
    user: process.env.PG_USER || 'kavenma',
    password: process.env.PG_PASSWORD || 'kavenma',
    database: process.env.PG_DATABASE || 'kavenma',
  };
}

// 解析目标数据库配置
let targetPgConfig = null;
let targetSchema = process.env.PG_SCHEMA_TARGET;
let isCrossDatabase = false; // 是否跨数据库迁移

// 检查是否配置了目标数据库
const hasTargetDatabaseConfig = 
  process.env.DATABASE_URL_TARGET ||
  process.env.PG_HOST_TARGET ||
  process.env.PG_DATABASE_TARGET;

if (hasTargetDatabaseConfig) {
  // 配置了目标数据库，进行跨数据库迁移
  isCrossDatabase = true;
  
  if (process.env.DATABASE_URL_TARGET) {
    const url = new URL(process.env.DATABASE_URL_TARGET);
    targetPgConfig = {
      host: url.hostname,
      port: Number(url.port),
      user: url.username,
      password: url.password,
      database: url.pathname.slice(1),
    };
    if (!process.env.PG_SCHEMA_TARGET) {
      targetSchema = url.searchParams.get('schema') || 'public';
    }
  } else {
    targetPgConfig = {
      host: process.env.PG_HOST_TARGET || sourcePgConfig.host,
      port: Number(process.env.PG_PORT_TARGET || process.env.PG_PORT || 5432),
      user: process.env.PG_USER_TARGET || sourcePgConfig.user,
      password: process.env.PG_PASSWORD_TARGET || sourcePgConfig.password,
      database: process.env.PG_DATABASE_TARGET || sourcePgConfig.database,
    };
  }
} else {
  // 未配置目标数据库，使用源数据库配置（同数据库不同schema迁移）
  targetPgConfig = sourcePgConfig;
}

/**
 * 获取源 schema 中所有表名
 */
async function getSourceTables(pgPool) {
  const result = await pgPool.query(
    `SELECT table_name 
     FROM information_schema.tables 
     WHERE table_schema = $1 
     AND table_type = 'BASE TABLE'
     ORDER BY table_name`,
    [sourceSchema]
  );
  return result.rows.map(row => row.table_name);
}

/**
 * 获取源 schema 中所有视图名
 */
async function getSourceViews(pgPool) {
  const result = await pgPool.query(
    `SELECT table_name 
     FROM information_schema.views 
     WHERE table_schema = $1
     ORDER BY table_name`,
    [sourceSchema]
  );
  return result.rows.map(row => row.table_name);
}

/**
 * 获取指定 schema 中的外键约束信息
 * @param {Pool} pgPool - 数据库连接池
 * @param {string} schema - schema 名称
 */
async function getForeignKeyConstraints(pgPool, schema) {
  const result = await pgPool.query(
    `SELECT
      tc.table_name,
      ccu.table_name AS referenced_table_name
     FROM information_schema.table_constraints AS tc
     JOIN information_schema.key_column_usage AS kcu
       ON tc.constraint_name = kcu.constraint_name
       AND tc.table_schema = kcu.table_schema
     JOIN information_schema.constraint_column_usage AS ccu
       ON ccu.constraint_name = tc.constraint_name
       AND ccu.table_schema = tc.table_schema
     WHERE tc.constraint_type = 'FOREIGN KEY'
       AND tc.table_schema = $1
       AND ccu.table_schema = $1
     GROUP BY tc.table_name, ccu.table_name`,
    [schema]
  );
  return result.rows.map(row => ({
    TABLE_NAME: row.table_name,
    REFERENCED_TABLE_NAME: row.referenced_table_name
  }));
}

/**
 * 获取源 schema 表的列信息
 */
async function getSourceTableColumns(pgPool, tableName) {
  const result = await pgPool.query(
    `SELECT column_name, data_type, is_nullable, column_default
     FROM information_schema.columns
     WHERE table_schema = $1 AND table_name = $2
     ORDER BY ordinal_position`,
    [sourceSchema, tableName]
  );
  return result.rows.map(row => ({
    COLUMN_NAME: row.column_name,
    DATA_TYPE: row.data_type,
    IS_NULLABLE: row.is_nullable,
    COLUMN_DEFAULT: row.column_default
  }));
}

/**
 * 获取目标 schema 表的列信息
 */
async function getTargetTableColumns(targetPool, tableName) {
  const result = await targetPool.query(
    `SELECT column_name, data_type, is_nullable, column_default
     FROM information_schema.columns
     WHERE table_schema = $1 AND table_name = $2
     ORDER BY ordinal_position`,
    [targetSchema, tableName]
  );
  return result.rows;
}

/**
 * 获取源 schema 表的总行数
 */
async function getTableRowCount(pgPool, tableName) {
  const result = await pgPool.query(
    `SELECT COUNT(*) as count FROM ${escapeIdentifier(sourceSchema)}.${escapeIdentifier(tableName)}`
  );
  return parseInt(result.rows[0].count, 10);
}

/**
 * 获取表的主键列名
 * @returns {Array<string>} 主键列名数组，如果没有主键返回空数组
 */
async function getPrimaryKeyColumns(pgPool, tableName) {
  const result = await pgPool.query(
    `SELECT column_name
     FROM information_schema.key_column_usage
     WHERE table_schema = $1
       AND table_name = $2
       AND constraint_name IN (
         SELECT constraint_name
         FROM information_schema.table_constraints
         WHERE table_schema = $1
           AND table_name = $2
           AND constraint_type = 'PRIMARY KEY'
       )
     ORDER BY ordinal_position`,
    [sourceSchema, tableName]
  );
  return result.rows.map(row => row.column_name);
}

/**
 * 转义标识符（用于 SQL 查询）
 */
function escapeIdentifier(identifier) {
  return `"${identifier.replace(/"/g, '""')}"`;
}

/**
 * 转换值（PostgreSQL 内部迁移，通常不需要特殊转换）
 * @param {*} value - 要转换的值
 * @param {string} sourceDataType - 源列的数据类型
 * @param {string} targetDataType - 目标列的数据类型（可选）
 */
function convertValue(value, sourceDataType, targetDataType) {
  if (value === null || value === undefined) {
    return null;
  }

  // 处理日期时间类型
  if (value instanceof Date) {
    return value;
  }

  // 确定目标数据类型（优先使用目标类型，如果没有则使用源类型）
  const dataType = targetDataType || sourceDataType;

  // 处理 JSON 类型
  if (dataType === 'json' || dataType === 'jsonb') {
    // 关键修复：对于 JSON/JSONB 字段，我们需要确保值被正确序列化
    // PostgreSQL 的 pg 库在读取 JSONB 时会自动解析为对象，但插入时可能无法正确序列化
    // 问题根源：pg 库在序列化 JavaScript 对象时，可能产生不符合 PostgreSQL 严格 JSON 要求的格式
    // 解决方案：直接返回序列化后的 JSON 字符串，让 PostgreSQL 自己处理类型转换
    
    // 如果值已经是对象或数组，序列化为 JSON 字符串
    if (typeof value === 'object' && value !== null && !(value instanceof Date) && !(value instanceof Buffer)) {
      try {
        // 清理对象：移除 undefined 值，确保所有值都可以被 JSON.stringify 正确处理
        const cleanedValue = cleanObjectForJSON(value);
        
        // 序列化为 JSON 字符串
        const serialized = JSON.stringify(cleanedValue);
        
        // 验证序列化后的字符串可以重新解析（确保是有效的 JSON）
        JSON.parse(serialized);
        
        // 直接返回序列化后的字符串，而不是对象
        // 这样 PostgreSQL 可以直接使用这个字符串，而不需要 pg 库再次序列化
        return serialized;
      } catch (e) {
        // 如果无法序列化或解析，返回 null
        return null;
      }
    }
    
    // 如果值是字符串，验证是否为有效的 JSON
    if (typeof value === 'string') {
      // 空字符串或空白字符串，返回 null
      const trimmed = value.trim();
      if (trimmed === '') {
        return null;
      }
      
      // 检查是否是有效的 JSON 字符串格式（快速检查）
      // JSON 必须以 {, [, ", 数字, true, false, null 开头
      const firstChar = trimmed[0];
      if (!['{', '[', '"', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 't', 'f', 'n'].includes(firstChar)) {
        return null;
      }
      
      try {
        // 验证字符串是有效的 JSON（尝试解析）
        const parsed = JSON.parse(trimmed);
        // 验证可以重新序列化（确保格式正确）
        JSON.stringify(parsed);
        // 返回原始字符串（已经是有效的 JSON 字符串）
        return trimmed;
      } catch (e) {
        // 如果解析失败，返回 null
        return null;
      }
    }
    
    // 其他类型（数字、布尔等），序列化为 JSON 字符串
    if (typeof value === 'number' || typeof value === 'boolean') {
      try {
        // 直接序列化为 JSON 字符串
        return JSON.stringify(value);
      } catch (e) {
        return null;
      }
    }
    
    // 未知类型，返回 null
    return null;
  }

  return value;
}

/**
 * 清理对象，移除 undefined 值，确保可以正确序列化为 JSON
 * @param {*} obj - 要清理的对象
 */
function cleanObjectForJSON(obj) {
  if (obj === null || obj === undefined) {
    return null;
  }
  
  if (Array.isArray(obj)) {
    return obj.map(item => cleanObjectForJSON(item));
  }
  
  if (typeof obj === 'object' && obj.constructor === Object) {
    const cleaned = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];
        // 跳过 undefined 值
        if (value !== undefined) {
          cleaned[key] = cleanObjectForJSON(value);
        }
      }
    }
    return cleaned;
  }
  
  // 对于其他类型（字符串、数字、布尔等），直接返回
  return obj;
}

/**
 * 拓扑排序：根据外键依赖关系确定表的迁移顺序
 * @param {Array<string>} tables - 所有表名
 * @param {Array} constraints - 外键约束信息
 * @returns {Array<string>} 排序后的表名数组
 */
function topologicalSort(tables, constraints) {
  // 构建依赖图：table -> [依赖的表列表]
  const dependencies = new Map();
  const inDegree = new Map();
  
  // 初始化
  tables.forEach(table => {
    dependencies.set(table, []);
    inDegree.set(table, 0);
  });
  
  // 构建依赖关系
  constraints.forEach(constraint => {
    const table = constraint.TABLE_NAME;
    const referencedTable = constraint.REFERENCED_TABLE_NAME;
    
    // 只处理在表列表中的表
    if (tables.includes(table) && tables.includes(referencedTable)) {
      dependencies.get(referencedTable).push(table);
      inDegree.set(table, (inDegree.get(table) || 0) + 1);
    }
  });
  
  // 拓扑排序
  const queue = [];
  const result = [];
  
  // 找到所有入度为 0 的节点（没有依赖的表）
  inDegree.forEach((degree, table) => {
    if (degree === 0) {
      queue.push(table);
    }
  });
  
  while (queue.length > 0) {
    const current = queue.shift();
    result.push(current);
    
    // 减少依赖当前表的其他表的入度
    dependencies.get(current).forEach(dependent => {
      const newDegree = inDegree.get(dependent) - 1;
      inDegree.set(dependent, newDegree);
      if (newDegree === 0) {
        queue.push(dependent);
      }
    });
  }
  
  // 如果存在循环依赖，将剩余的表添加到结果中
  inDegree.forEach((degree, table) => {
    if (degree > 0 && !result.includes(table)) {
      console.log(`  警告: 表 ${table} 可能存在循环依赖，将在最后迁移`);
      result.push(table);
    }
  });
  
  return result;
}

/**
 * 获取视图的定义
 */
async function getViewDefinition(pgPool, viewName) {
  const result = await pgPool.query(
    `SELECT pg_get_viewdef($1::regclass, true) as definition`,
    [`${sourceSchema}.${viewName}`]
  );
  return result.rows[0]?.definition || null;
}

/**
 * 替换视图定义中的schema引用
 * 将源schema的引用替换为目标schema
 */
function replaceSchemaInViewDefinition(viewDefinition, sourceSchema, targetSchema) {
  if (!viewDefinition) {
    return viewDefinition;
  }

  // 转义schema名称用于正则表达式
  const escapedSourceSchema = sourceSchema.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const targetSchemaEscaped = escapeIdentifier(targetSchema);
  
  let replaced = viewDefinition;
  
  // 替换 "schema"."table" 格式（带引号的schema和表名）
  const quotedBothPattern = new RegExp(
    `"${escapedSourceSchema}"\\s*\\.\\s*"([^"]+)"`,
    'gi'
  );
  replaced = replaced.replace(quotedBothPattern, `${targetSchemaEscaped}."$1"`);
  
  // 替换 "schema".table 格式（带引号的schema，不带引号的表名）
  const quotedSchemaOnlyPattern = new RegExp(
    `"${escapedSourceSchema}"\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)`,
    'gi'
  );
  replaced = replaced.replace(quotedSchemaOnlyPattern, (match, tableName) => {
    return `${targetSchemaEscaped}.${escapeIdentifier(tableName)}`;
  });
  
  // 替换 schema."table" 格式（不带引号的schema，带引号的表名）
  const quotedTableOnlyPattern = new RegExp(
    `\\b${escapedSourceSchema}\\s*\\.\\s*"([^"]+)"`,
    'gi'
  );
  replaced = replaced.replace(quotedTableOnlyPattern, `${targetSchemaEscaped}."$1"`);
  
  // 替换 schema.table 格式（都不带引号）
  const unquotedPattern = new RegExp(
    `\\b${escapedSourceSchema}\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)`,
    'gi'
  );
  replaced = replaced.replace(unquotedPattern, (match, tableName) => {
    return `${targetSchemaEscaped}.${escapeIdentifier(tableName)}`;
  });

  return replaced;
}

/**
 * 获取视图的依赖关系（视图依赖的其他视图或表）
 */
async function getViewDependencies(pgPool, viewName) {
  const result = await pgPool.query(
    `SELECT DISTINCT
       dependent_ns.nspname as dependent_schema,
       dependent_view.relname as dependent_view,
       source_ns.nspname as source_schema,
       source_table.relname as source_table
     FROM pg_depend
     JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
     JOIN pg_class as dependent_view ON pg_rewrite.ev_class = dependent_view.oid
     JOIN pg_class as source_table ON pg_depend.refobjid = source_table.oid
     JOIN pg_namespace dependent_ns ON dependent_view.relnamespace = dependent_ns.oid
     JOIN pg_namespace source_ns ON source_table.relnamespace = source_ns.oid
     WHERE dependent_ns.nspname = $1
       AND dependent_view.relname = $2
       AND dependent_view.relkind = 'v'
       AND source_table.relkind IN ('r', 'v')
     ORDER BY source_schema, source_table`,
    [sourceSchema, viewName]
  );
  return result.rows.map(row => ({
    dependent_schema: row.dependent_schema,
    dependent_view: row.dependent_view,
    source_schema: row.source_schema,
    source_table: row.source_table
  }));
}

/**
 * 迁移单个视图
 */
async function migrateView(sourcePool, targetPool, viewName) {
  const migrationType = isCrossDatabase 
    ? `跨数据库迁移 (${sourcePgConfig.database}.${sourceSchema} -> ${targetPgConfig.database}.${targetSchema})`
    : `同数据库迁移 (${sourceSchema} -> ${targetSchema})`;
  console.log(`\n开始迁移视图: ${viewName} [${migrationType}]`);

  try {
    // 检查目标 schema 中是否已存在该视图
    const targetViewCheck = await targetPool.query(
      `SELECT EXISTS (
        SELECT FROM information_schema.views 
        WHERE table_schema = $1 
        AND table_name = $2
      )`,
      [targetSchema, viewName]
    );

    if (targetViewCheck.rows[0].exists) {
      console.log(`  提示: 目标 schema ${targetSchema} 中已存在视图 ${viewName}，跳过迁移`);
      return;
    }

    // 获取视图定义
    const viewDefinition = await getViewDefinition(sourcePool, viewName);
    
    if (!viewDefinition) {
      console.log(`  警告: 无法获取视图 ${viewName} 的定义，跳过`);
      return;
    }

    console.log(`  获取视图定义成功`);

    // 获取视图依赖关系
    const dependencies = await getViewDependencies(sourcePool, viewName);
    if (dependencies.length > 0) {
      console.log(`  视图依赖: ${dependencies.map(d => `${d.source_schema}.${d.source_table}`).join(', ')}`);
    }

    // 替换视图定义中的schema引用
    let modifiedDefinition = replaceSchemaInViewDefinition(viewDefinition, sourceSchema, targetSchema);
    
    // 如果目标schema与源schema不同，还需要处理可能的其他schema引用
    // 这里假设视图主要引用源schema中的对象，如果需要引用其他schema，可能需要额外处理

    // 构建CREATE VIEW语句
    const targetViewName = escapeIdentifier(targetSchema) + '.' + escapeIdentifier(viewName);
    const createViewQuery = `CREATE OR REPLACE VIEW ${targetViewName} AS ${modifiedDefinition}`;

    // 在目标schema中创建视图
    const client = await targetPool.connect();
    try {
      await client.query('BEGIN');
      await client.query(createViewQuery);
      await client.query('COMMIT');
      console.log(`  ✓ 视图 ${viewName} 迁移完成`);
    } catch (error) {
      await client.query('ROLLBACK');
      
      // 检查是否是依赖相关的错误
      const isDependencyError = error.message && (
        error.message.includes('does not exist') ||
        error.message.includes('relation') ||
        error.message.includes('不存在')
      );

      if (isDependencyError) {
        console.log(`  警告: 视图 ${viewName} 迁移失败，可能是依赖的对象不存在: ${error.message}`);
        console.log(`  提示: 请确保依赖的表或视图已迁移到目标schema`);
      } else {
        throw error;
      }
    } finally {
      client.release();
    }
  } catch (error) {
    console.error(`  ✗ 视图 ${viewName} 迁移失败:`, error.message);
    console.log(`  跳过视图 ${viewName}，继续迁移其他视图`);
  }
}

/**
 * 重置表的自增序列
 * 在数据迁移后，将序列值设置为当前最大 id + 1
 */
async function resetSequences(targetPool, tableName, primaryKeyColumns) {
  if (primaryKeyColumns.length === 0) {
    return;
  }

  const tableRef = `${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)}`;

  for (const pkColumn of primaryKeyColumns) {
    try {
      // 获取序列名称
      const seqResult = await targetPool.query(
        `SELECT pg_get_serial_sequence($1, $2) as seq_name`,
        [`${targetSchema}.${tableName}`, pkColumn]
      );

      const seqName = seqResult.rows[0]?.seq_name;
      if (!seqName) {
        // 该列没有关联的序列（可能不是自增列）
        continue;
      }

      // 获取当前最大值并重置序列
      const resetQuery = `
        SELECT setval(
          $1,
          COALESCE((SELECT MAX(${escapeIdentifier(pkColumn)}) FROM ${tableRef}), 0) + 1,
          false
        )
      `;
      const resetResult = await targetPool.query(resetQuery, [seqName]);
      const newSeqValue = resetResult.rows[0]?.setval;

      console.log(`  ✓ 已重置序列 ${seqName} 到 ${newSeqValue}`);
    } catch (error) {
      // 忽略序列重置错误（可能是非自增主键）
      console.log(`  提示: 无法重置列 ${pkColumn} 的序列: ${error.message}`);
    }
  }
}

/**
 * 迁移单个表的数据
 */
async function migrateTable(sourcePool, targetPool, tableName) {
  const migrationType = isCrossDatabase 
    ? `跨数据库迁移 (${sourcePgConfig.database}.${sourceSchema} -> ${targetPgConfig.database}.${targetSchema})`
    : `同数据库迁移 (${sourceSchema} -> ${targetSchema})`;
  console.log(`\n开始迁移表: ${tableName} [${migrationType}]`);

  try {
    // 获取源 schema 列信息
    const sourceColumns = await getSourceTableColumns(sourcePool, tableName);
    const sourceColumnNames = sourceColumns.map(col => col.COLUMN_NAME);
    
    // 获取主键信息
    const primaryKeyColumns = await getPrimaryKeyColumns(sourcePool, tableName);
    const hasPrimaryKey = primaryKeyColumns.length > 0;
    
    // 获取总行数
    const totalRows = await getTableRowCount(sourcePool, tableName);
    console.log(`  总行数: ${totalRows}`);
    
    if (hasPrimaryKey) {
      console.log(`  主键列: ${primaryKeyColumns.join(', ')}`);
      console.log(`  将仅迁移最新的 ${MAX_ROWS_TO_MIGRATE} 条数据（按主键降序）`);
    }

    if (totalRows === 0) {
      console.log(`  表 ${tableName} 为空，跳过`);
      return;
    }
    
    // 限制迁移行数
    const actualRowsToMigrate = Math.min(totalRows, MAX_ROWS_TO_MIGRATE);

    // 检查目标 schema 表是否存在
    const targetTableCheck = await targetPool.query(
      `SELECT EXISTS (
        SELECT FROM information_schema.tables 
        WHERE table_schema = $1 
        AND table_name = $2
      )`,
      [targetSchema, tableName]
    );

    if (!targetTableCheck.rows[0].exists) {
      console.log(`  警告: 目标 schema ${targetSchema} 中不存在表 ${tableName}，跳过`);
      return;
    }

    // 检查目标表是否已有数据
    const targetRowCountResult = await targetPool.query(
      `SELECT COUNT(*) as count FROM ${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)}`
    );
    const targetRowCount = parseInt(targetRowCountResult.rows[0].count, 10);
    
    if (targetRowCount > 0) {
      console.log(`  提示: 目标表 ${tableName} 已有 ${targetRowCount} 条数据，跳过迁移`);
      return;
    }

    // 获取目标 schema 列信息
    const targetColumns = await getTargetTableColumns(targetPool, tableName);
    const targetColumnNames = targetColumns.map(col => col.column_name);

    // 找出两个表都存在的共同字段（忽略大小写差异）
    const commonColumns = sourceColumnNames.filter(sourceCol => {
      return targetColumnNames.some(targetCol => targetCol.toLowerCase() === sourceCol.toLowerCase());
    });

    if (commonColumns.length === 0) {
      console.log(`  警告: 表 ${tableName} 没有共同字段，跳过`);
      return;
    }

    // 记录字段差异
    const missingInTarget = sourceColumnNames.filter(col => 
      !targetColumnNames.some(targetCol => targetCol.toLowerCase() === col.toLowerCase())
    );
    const missingInSource = targetColumnNames.filter(col => 
      !sourceColumnNames.some(sourceCol => sourceCol.toLowerCase() === col.toLowerCase())
    );

    if (missingInTarget.length > 0) {
      console.log(`  提示: 源 schema 中存在但目标 schema 中不存在的字段: ${missingInTarget.join(', ')}`);
    }
    if (missingInSource.length > 0) {
      console.log(`  提示: 目标 schema 中存在但源 schema 中不存在的字段: ${missingInSource.join(', ')}`);
    }
    console.log(`  将迁移 ${commonColumns.length} 个共同字段: ${commonColumns.join(', ')}`);

    // 清空目标表（可选，如果需要增量迁移可以注释掉）
    // await targetPool.query(`TRUNCATE TABLE ${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)} CASCADE`);

    let offset = 0;
    let migratedRows = 0;
    let errorCount = 0;

    // 构建查询语句
    const sourceTableRef = `${escapeIdentifier(sourceSchema)}.${escapeIdentifier(tableName)}`;
    const targetTableRef = `${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)}`;
    
    // 分批读取和插入数据
    while (offset < actualRowsToMigrate) {
      let query;
      
      if (hasPrimaryKey && primaryKeyColumns.length > 0) {
        // 对于有主键的表，按主键降序排序，直接限制为MAX_ROWS_TO_MIGRATE条
        const orderByClause = primaryKeyColumns
          .map(col => `${escapeIdentifier(col)} DESC`)
          .join(', ');
        const limit = Math.min(BATCH_SIZE, actualRowsToMigrate - offset);
        query = `SELECT * FROM ${sourceTableRef} ORDER BY ${orderByClause} LIMIT ${limit} OFFSET ${offset}`;
      } else {
        // 对于没有主键的表，使用普通查询
        query = `SELECT * FROM ${sourceTableRef} LIMIT ${BATCH_SIZE} OFFSET ${offset}`;
      }
      
      const result = await sourcePool.query(query);
      const rows = result.rows;

      if (rows.length === 0) {
        break;
      }

      // 批量插入到目标 schema
      const client = await targetPool.connect();
      try {
        await client.query('BEGIN');
        
        // 准备批量插入的值（只使用共同字段）
        const allValues = rows.map(row => {
          return commonColumns.map(colName => {
            const sourceColumn = sourceColumns.find(c => c.COLUMN_NAME === colName);
            const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
            return convertValue(row[colName], sourceColumn?.DATA_TYPE, targetColumn?.data_type);
          });
        });

        // 使用批量插入（构建多行 VALUES）
        if (allValues.length > 0) {
          // 构建 VALUES 占位符，对于 JSON/JSONB 字段使用类型转换
          const valuesPlaceholders = allValues.map((_, rowIndex) => {
            const startParam = rowIndex * commonColumns.length + 1;
            return `(${commonColumns.map((_, colIndex) => {
              const colName = commonColumns[colIndex];
              const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
              const isJsonType = targetColumn && (targetColumn.data_type === 'json' || targetColumn.data_type === 'jsonb');
              const paramIndex = startParam + colIndex;
              
              // 对于 JSON/JSONB 字段，使用类型转换确保格式正确
              // 注意：现在 convertValue 返回的是序列化后的字符串，所以需要类型转换
              if (isJsonType) {
                return `$${paramIndex}::${targetColumn.data_type}`;
              }
              return `$${paramIndex}`;
            }).join(', ')})`;
          }).join(', ');
          
          // 使用目标 schema 中实际的列名（处理大小写）
          const targetColumnNamesForInsert = commonColumns.map(col => {
            const targetCol = targetColumnNames.find(targetCol => targetCol.toLowerCase() === col.toLowerCase());
            return escapeIdentifier(targetCol || col);
          });
          
          const batchInsertQuery = `INSERT INTO ${targetTableRef} (${targetColumnNamesForInsert.join(', ')}) VALUES ${valuesPlaceholders}`;
          const flatValues = allValues.flat();
          
          await client.query(batchInsertQuery, flatValues);
        }

        await client.query('COMMIT');
        migratedRows += rows.length;
        const progressTotal = hasPrimaryKey ? actualRowsToMigrate : totalRows;
        console.log(`  已迁移: ${migratedRows}/${progressTotal} 行`);
      } catch (error) {
        await client.query('ROLLBACK');
        
        // 检查是否是 JSON 格式错误
        const isJsonError = error.message && (
          error.message.includes('invalid input syntax for type json') ||
          error.message.includes('invalid input syntax for type jsonb') ||
          (error.message.includes('invalid input syntax') && error.message.includes('json'))
        );

        // 检查是否是字段相关的错误
        const isColumnError = error.message && (
          error.message.includes('column') ||
          error.message.includes('字段') ||
          error.message.includes('does not exist') ||
          error.message.includes('unknown column')
        );

        // 检查是否是外键约束错误
        const isForeignKeyError = error.message && (
          error.message.includes('foreign key') ||
          error.message.includes('外键') ||
          error.message.includes('violates foreign key constraint') ||
          error.message.includes('referential integrity')
        );

        if (isJsonError) {
          // 对于 JSON 错误，尝试逐行插入以跳过有问题的行
          console.log(`  警告: 批量插入遇到 JSON 格式错误，尝试逐行插入以跳过有问题的行...`);
          let rowInserted = 0;
          let rowSkipped = 0;
          
          // 使用目标 schema 中实际的列名（处理大小写）
          const targetColumnNamesForInsert = commonColumns.map(col => {
            const targetCol = targetColumnNames.find(targetCol => targetCol.toLowerCase() === col.toLowerCase());
            return escapeIdentifier(targetCol || col);
          });
          
          for (let i = 0; i < rows.length; i++) {
            const row = rows[i];
            try {
              await client.query('BEGIN');
              
              // 准备单行插入的值
              const rowValues = commonColumns.map(colName => {
                const sourceColumn = sourceColumns.find(c => c.COLUMN_NAME === colName);
                const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
                return convertValue(row[colName], sourceColumn?.DATA_TYPE, targetColumn?.data_type);
              });
              
              // 构建单行插入语句，对于 JSON/JSONB 字段使用类型转换
              const placeholders = commonColumns.map((colName, index) => {
                const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
                const isJsonType = targetColumn && (targetColumn.data_type === 'json' || targetColumn.data_type === 'jsonb');
                
                // 对于 JSON/JSONB 字段，使用类型转换确保格式正确
                // 注意：现在 convertValue 返回的是序列化后的字符串，所以需要类型转换
                if (isJsonType) {
                  return `$${index + 1}::${targetColumn.data_type}`;
                }
                return `$${index + 1}`;
              }).join(', ');
              const singleInsertQuery = `INSERT INTO ${targetTableRef} (${targetColumnNamesForInsert.join(', ')}) VALUES (${placeholders})`;
              
              await client.query(singleInsertQuery, rowValues);
              await client.query('COMMIT');
              rowInserted++;
            } catch (rowError) {
              await client.query('ROLLBACK');
              
              // 检查是否是 JSON 错误
              const isRowJsonError = rowError.message && (
                rowError.message.includes('invalid input syntax for type json') ||
                rowError.message.includes('invalid input syntax for type jsonb') ||
                (rowError.message.includes('invalid input syntax') && rowError.message.includes('json'))
              );
              
              if (isRowJsonError) {
                rowSkipped++;
                // 尝试找出有问题的 JSON 字段
                const problematicFields = [];
                for (const colName of commonColumns) {
                  const column = sourceColumns.find(c => c.COLUMN_NAME === colName);
                  const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
                  if (column && (column.DATA_TYPE === 'json' || column.DATA_TYPE === 'jsonb') ||
                      targetColumn && (targetColumn.data_type === 'json' || targetColumn.data_type === 'jsonb')) {
                    const value = row[colName];
                    if (value !== null && value !== undefined) {
                      try {
                        if (typeof value === 'string') {
                          JSON.parse(value);
                        } else if (typeof value === 'object') {
                          JSON.stringify(value);
                        }
                      } catch (e) {
                        problematicFields.push(`${colName}(${typeof value})`);
                      }
                    }
                  }
                }
                if (problematicFields.length > 0) {
                  console.log(`    跳过行 ${i + 1}（JSON 字段问题: ${problematicFields.join(', ')}）`);
                } else {
                  console.log(`    跳过行 ${i + 1}（JSON 格式错误: ${rowError.message.substring(0, 150)}）`);
                }
              } else {
                // 其他错误，也跳过这一行，但输出详细错误信息
                rowSkipped++;
                console.log(`    跳过行 ${i + 1}（错误: ${rowError.message.substring(0, 200)}）`);
              }
            }
          }
          
          migratedRows += rowInserted;
          errorCount += rowSkipped;
          if (rowSkipped > 0) {
            console.log(`  逐行插入完成: 成功 ${rowInserted} 行，跳过 ${rowSkipped} 行`);
          }
        } else if (isColumnError) {
          errorCount++;
          console.log(`  警告: 字段差异错误（已忽略）: ${error.message}`);
          // 继续处理下一批数据
          migratedRows += rows.length;
        } else if (isForeignKeyError) {
          errorCount++;
          console.log(`  警告: 外键约束错误（已忽略，可能因为依赖表数据未完全迁移）: ${error.message}`);
          // 继续处理下一批数据
          migratedRows += rows.length;
        } else {
          // 非字段/外键/JSON错误，抛出异常
          throw error;
        }
      } finally {
        client.release();
      }

      // 更新偏移量
      offset += BATCH_SIZE;
      
      // 如果获取的数据少于批次大小，说明已经获取完所有数据
      if (rows.length < BATCH_SIZE) {
        break;
      }
    }

    if (migratedRows < totalRows) {
      console.log(`  提示: 表 ${tableName} 共有 ${totalRows} 行，仅迁移了最新的 ${migratedRows} 行`);
    }

    if (errorCount > 0) {
      console.log(`  ⚠ 表 ${tableName} 迁移完成，共 ${migratedRows} 行，遇到 ${errorCount} 个字段差异错误（已忽略）`);
    } else {
      console.log(`  ✓ 表 ${tableName} 迁移完成，共 ${migratedRows} 行`);
    }

    // 重置自增序列，避免后续插入时 id 冲突
    if (migratedRows > 0 && hasPrimaryKey) {
      await resetSequences(targetPool, tableName, primaryKeyColumns);
    }
  } catch (error) {
    console.error(`  ✗ 表 ${tableName} 迁移失败:`, error.message);
    // 不再抛出错误，继续迁移下一个表
    console.log(`  跳过表 ${tableName}，继续迁移其他表`);
  }
}

/**
 * 主函数
 */
async function main() {
  console.log('PostgreSQL Schema 间数据迁移工具');
  console.log('=====================================\n');
  
  if (isCrossDatabase) {
    console.log('迁移模式: 跨数据库迁移');
    console.log(`源数据库: ${sourcePgConfig.host}:${sourcePgConfig.port}/${sourcePgConfig.database}`);
    console.log(`源 Schema: ${sourceSchema}`);
    console.log(`目标数据库: ${targetPgConfig.host}:${targetPgConfig.port}/${targetPgConfig.database}`);
    console.log(`目标 Schema: ${targetSchema}\n`);
  } else {
    console.log('迁移模式: 同数据库不同 Schema 迁移');
    console.log(`数据库: ${sourcePgConfig.host}:${sourcePgConfig.port}/${sourcePgConfig.database}`);
    console.log(`源 Schema: ${sourceSchema}`);
    console.log(`目标 Schema: ${targetSchema}\n`);
  }

  // 验证配置
  if (!sourcePgConfig.database) {
    console.error('错误: 请设置源数据库配置 (PG_DATABASE 或 DATABASE_URL)');
    process.exit(1);
  }
  if (!sourceSchema) {
    console.error('错误: 请设置 PG_SCHEMA 环境变量');
    process.exit(1);
  }
  if (!targetSchema) {
    console.error('错误: 请设置 PG_SCHEMA_TARGET 环境变量');
    process.exit(1);
  }
  if (isCrossDatabase && !targetPgConfig.database) {
    console.error('错误: 请设置目标数据库配置 (PG_DATABASE_TARGET 或 DATABASE_URL_TARGET)');
    process.exit(1);
  }
  if (sourceSchema === targetSchema && !isCrossDatabase) {
    console.error('错误: 源 schema 和目标 schema 不能相同（同数据库迁移时）');
    process.exit(1);
  }
  if (isCrossDatabase && 
      sourcePgConfig.host === targetPgConfig.host &&
      sourcePgConfig.port === targetPgConfig.port &&
      sourcePgConfig.database === targetPgConfig.database &&
      sourceSchema === targetSchema) {
    console.error('错误: 源和目标配置完全相同');
    process.exit(1);
  }

  let sourcePool = null;
  let targetPool = null;

  try {
    // 连接源 PostgreSQL
    console.log('正在连接源 PostgreSQL...');
    sourcePool = new Pool(sourcePgConfig);
    await sourcePool.query('SELECT 1');
    console.log(`✓ 源 PostgreSQL 连接成功 (${sourcePgConfig.database})\n`);

    // 连接目标 PostgreSQL（如果跨数据库）
    if (isCrossDatabase) {
      console.log('正在连接目标 PostgreSQL...');
      targetPool = new Pool(targetPgConfig);
      await targetPool.query('SELECT 1');
      console.log(`✓ 目标 PostgreSQL 连接成功 (${targetPgConfig.database})\n`);
    } else {
      // 同数据库迁移，使用同一个连接池
      targetPool = sourcePool;
    }

    // 检查源 schema 是否存在
    const sourceSchemaCheck = await sourcePool.query(
      `SELECT EXISTS (
        SELECT FROM information_schema.schemata 
        WHERE schema_name = $1
      )`,
      [sourceSchema]
    );
    if (!sourceSchemaCheck.rows[0].exists) {
      console.error(`错误: 源 schema ${sourceSchema} 不存在`);
      process.exit(1);
    }

    // 检查目标 schema 是否存在
    const targetSchemaCheck = await targetPool.query(
      `SELECT EXISTS (
        SELECT FROM information_schema.schemata 
        WHERE schema_name = $1
      )`,
      [targetSchema]
    );
    if (!targetSchemaCheck.rows[0].exists) {
      console.error(`错误: 目标 schema ${targetSchema} 不存在`);
      process.exit(1);
    }

    // 获取所有表
    console.log(`正在获取源 schema ${sourceSchema} 中的表列表...`);
    const tables = await getSourceTables(sourcePool);
    console.log(`找到 ${tables.length} 个表: ${tables.join(', ')}\n`);

    if (tables.length === 0) {
      console.log('没有找到任何表，退出');
      return;
    }

    // 获取外键约束信息（从目标数据库获取，因为目标数据库可能有更严格的外键约束）
    console.log('正在分析外键依赖关系（基于目标数据库）...');
    const constraints = await getForeignKeyConstraints(targetPool, targetSchema);
    console.log(`找到 ${constraints.length} 个外键约束`);
    
    if (constraints.length > 0) {
      console.log('外键依赖关系:');
      constraints.forEach(constraint => {
        console.log(`  ${constraint.TABLE_NAME} -> ${constraint.REFERENCED_TABLE_NAME}`);
      });
    }
    console.log('');

    // 根据外键依赖关系排序表
    const sortedTables = topologicalSort(tables, constraints);
    
    if (sortedTables.length !== tables.length) {
      console.log('警告: 排序后的表数量与原始表数量不一致');
    }
    
    console.log('迁移顺序（考虑外键依赖）:');
    sortedTables.forEach((table, index) => {
      const deps = constraints
        .filter(c => c.TABLE_NAME === table)
        .map(c => c.REFERENCED_TABLE_NAME);
      if (deps.length > 0) {
        console.log(`  ${index + 1}. ${table} (依赖: ${deps.join(', ')})`);
      } else {
        console.log(`  ${index + 1}. ${table}`);
      }
    });
    console.log('');

    // 按照排序后的顺序迁移每个表
    for (const tableName of sortedTables) {
      try {
        await migrateTable(sourcePool, targetPool, tableName);
      } catch (error) {
        console.error(`  表 ${tableName} 迁移出错，继续处理下一个表:`, error.message);
        // 继续迁移下一个表，不中断整个流程
      }
    }

    console.log('\n=====================================');
    console.log('✓ 所有表迁移完成！\n');

    // 迁移视图（在表迁移完成后进行，因为视图可能依赖于表）
    console.log('=====================================');
    console.log('开始迁移视图...');
    console.log('=====================================\n');

    // 获取所有视图
    console.log(`正在获取源 schema ${sourceSchema} 中的视图列表...`);
    const views = await getSourceViews(sourcePool);
    console.log(`找到 ${views.length} 个视图: ${views.join(', ')}\n`);

    if (views.length > 0) {
      // 获取视图依赖关系（视图之间的依赖）
      console.log('正在分析视图依赖关系...');
      const viewDependencies = [];
      for (const viewName of views) {
        const deps = await getViewDependencies(sourcePool, viewName);
        // 只记录视图之间的依赖（不包括对表的依赖）
        for (const dep of deps) {
          if (dep.source_table && views.includes(dep.source_table) && dep.source_schema === sourceSchema) {
            viewDependencies.push({
              TABLE_NAME: viewName,
              REFERENCED_TABLE_NAME: dep.source_table
            });
          }
        }
      }
      
      if (viewDependencies.length > 0) {
        console.log(`找到 ${viewDependencies.length} 个视图依赖关系:`);
        viewDependencies.forEach(dep => {
          console.log(`  ${dep.TABLE_NAME} -> ${dep.REFERENCED_TABLE_NAME}`);
        });
        console.log('');
      }

      // 根据依赖关系排序视图
      const sortedViews = topologicalSort(views, viewDependencies);
      
      console.log('视图迁移顺序（考虑依赖关系）:');
      sortedViews.forEach((view, index) => {
        const deps = viewDependencies
          .filter(d => d.TABLE_NAME === view)
          .map(d => d.REFERENCED_TABLE_NAME);
        if (deps.length > 0) {
          console.log(`  ${index + 1}. ${view} (依赖: ${deps.join(', ')})`);
        } else {
          console.log(`  ${index + 1}. ${view}`);
        }
      });
      console.log('');

      // 按照排序后的顺序迁移每个视图
      for (const viewName of sortedViews) {
        try {
          await migrateView(sourcePool, targetPool, viewName);
        } catch (error) {
          console.error(`  视图 ${viewName} 迁移出错，继续处理下一个视图:`, error.message);
          // 继续迁移下一个视图，不中断整个流程
        }
      }

      console.log('\n=====================================');
      console.log('✓ 所有视图迁移完成！');
    } else {
      console.log('没有找到任何视图');
    }
  } catch (error) {
    console.error('\n迁移过程中发生错误:', error);
    process.exit(1);
  } finally {
    // 关闭连接
    if (sourcePool) {
      await sourcePool.end();
      console.log('\n源 PostgreSQL 连接已关闭');
    }
    if (targetPool && isCrossDatabase && targetPool !== sourcePool) {
      await targetPool.end();
      console.log('目标 PostgreSQL 连接已关闭');
    }
  }
}

// 运行主函数
if (require.main === module) {
  main().catch(error => {
    console.error('未处理的错误:', error);
    process.exit(1);
  });
}

module.exports = { main };


