// 배치관리 서비스
// 작성일: 2024-12-24

import { query, queryOne, transaction } from "../database/db";
import {
  BatchConfig,
  BatchMapping,
  BatchConfigFilter,
  BatchMappingRequest,
  BatchValidationResult,
  ApiResponse,
  ConnectionInfo,
  TableInfo,
  ColumnInfo,
  CreateBatchConfigRequest,
  UpdateBatchConfigRequest,
} from "../types/batchTypes";
import { BatchExternalDbService } from "./batchExternalDbService";

export class BatchService {
  /**
   * 배치 설정 목록 조회 (회사별)
   */
  static async getBatchConfigs(
    filter: BatchConfigFilter,
    userCompanyCode?: string
  ): Promise<ApiResponse<BatchConfig[]>> {
    try {
      const whereConditions: string[] = [];
      const values: any[] = [];
      let paramIndex = 1;

      // 회사별 필터링 (최고 관리자가 아닌 경우 필수)
      if (userCompanyCode && userCompanyCode !== "*") {
        whereConditions.push(`bc.company_code = $${paramIndex++}`);
        values.push(userCompanyCode);
      } else if (userCompanyCode === "*" && filter.company_code) {
        // 최고 관리자: 필터가 있으면 적용
        whereConditions.push(`bc.company_code = $${paramIndex++}`);
        values.push(filter.company_code);
      }

      // 필터 조건 적용
      if (filter.is_active) {
        whereConditions.push(`bc.is_active = $${paramIndex++}`);
        values.push(filter.is_active);
      }

      // 검색 조건 적용 (OR)
      if (filter.search && filter.search.trim()) {
        whereConditions.push(
          `(bc.batch_name ILIKE $${paramIndex} OR bc.description ILIKE $${paramIndex})`
        );
        values.push(`%${filter.search.trim()}%`);
        paramIndex++;
      }

      const whereClause =
        whereConditions.length > 0
          ? `WHERE ${whereConditions.join(" AND ")}`
          : "";

      const page = filter.page || 1;
      const limit = filter.limit || 10;
      const offset = (page - 1) * limit;

      // 전체 카운트 조회
      const countResult = await query<{ count: string }>(
        `SELECT COUNT(*) as count FROM batch_configs bc ${whereClause}`,
        values
      );
      const total = parseInt(countResult[0].count);
      const totalPages = Math.ceil(total / limit);

      // 목록 조회 (최근 실행 정보 포함)
      const configs = await query<any>(
        `SELECT bc.*,
           (SELECT bel.execution_status FROM batch_execution_logs bel WHERE bel.batch_config_id = bc.id ORDER BY bel.start_time DESC LIMIT 1) as last_status,
           (SELECT bel.start_time FROM batch_execution_logs bel WHERE bel.batch_config_id = bc.id ORDER BY bel.start_time DESC LIMIT 1) as last_executed_at,
           (SELECT bel.total_records FROM batch_execution_logs bel WHERE bel.batch_config_id = bc.id ORDER BY bel.start_time DESC LIMIT 1) as last_total_records
         FROM batch_configs bc
         ${whereClause}
         ORDER BY bc.created_date DESC
         LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
        [...values, limit, offset]
      );

      return {
        success: true,
        data: configs as BatchConfig[],
        pagination: {
          page,
          limit,
          total,
          totalPages,
        },
        message: `${configs.length}개의 배치 설정을 조회했습니다.`,
      };
    } catch (error) {
      console.error("배치 설정 목록 조회 오류:", error);
      return {
        success: false,
        data: [],
        message: "배치 설정 목록 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 특정 배치 설정 조회 (별칭)
   */
  static async getBatchConfig(id: number): Promise<BatchConfig | null> {
    const result = await this.getBatchConfigById(id);
    if (!result.success || !result.data) {
      return null;
    }
    return result.data;
  }

  /**
   * 배치 설정 상세 조회
   */
  static async getBatchConfigById(
    id: number
  ): Promise<ApiResponse<BatchConfig>> {
    try {
      // 배치 설정 조회
      const config = await queryOne<any>(
        `SELECT * FROM batch_configs WHERE id = $1`,
        [id]
      );

      if (!config) {
        return {
          success: false,
          message: "배치 설정을 찾을 수 없습니다.",
        };
      }

      // 매핑 정보 조회
      const mappings = await query<BatchMapping>(
        `SELECT * FROM batch_mappings WHERE batch_config_id = $1 ORDER BY mapping_order ASC`,
        [id]
      );

      const batchConfig: BatchConfig = {
        ...config,
        batch_mappings: mappings,
      } as BatchConfig;

      return {
        success: true,
        data: batchConfig,
      };
    } catch (error) {
      console.error("배치 설정 상세 조회 오류:", error);
      return {
        success: false,
        message: "배치 설정 상세 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 배치 설정 생성
   */
  static async createBatchConfig(
    data: CreateBatchConfigRequest,
    userId?: string
  ): Promise<ApiResponse<BatchConfig>> {
    try {
      // 트랜잭션으로 배치 설정과 매핑 생성
      const result = await transaction(async (client) => {
        // 배치 설정 생성
        const batchConfigResult = await client.query(
          `INSERT INTO batch_configs 
           (batch_name, description, cron_schedule, is_active, company_code, save_mode, conflict_key, auth_service_name, data_array_path, execution_type, node_flow_id, node_flow_context, created_by, created_date, updated_date)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, NOW(), NOW())
           RETURNING *`,
          [
            data.batchName,
            data.description,
            data.cronSchedule,
            data.isActive || "Y",
            data.companyCode,
            data.saveMode || "INSERT",
            data.conflictKey || null,
            data.authServiceName || null,
            data.dataArrayPath || null,
            data.executionType || "mapping",
            data.nodeFlowId || null,
            data.nodeFlowContext ? JSON.stringify(data.nodeFlowContext) : null,
            userId,
          ]
        );

        const batchConfig = batchConfigResult.rows[0];

        // 배치 매핑 생성
        const mappings = [];
        for (let index = 0; index < data.mappings.length; index++) {
          const mapping = data.mappings[index];
          const mappingResult = await client.query(
            `INSERT INTO batch_mappings 
             (batch_config_id, company_code, from_connection_type, from_connection_id, from_table_name, from_column_name, 
              from_column_type, from_api_url, from_api_key, from_api_method, from_api_param_type,
              from_api_param_name, from_api_param_value, from_api_param_source, from_api_body,
              to_connection_type, to_connection_id, to_table_name, to_column_name, to_column_type,
              to_api_url, to_api_key, to_api_method, to_api_body, mapping_order, mapping_type, created_by, created_date)
             VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, NOW())
             RETURNING *`,
            [
              batchConfig.id,
              data.companyCode, // 멀티테넌시: 배치 설정과 동일한 company_code 사용
              mapping.from_connection_type,
              mapping.from_connection_id,
              mapping.from_table_name,
              mapping.from_column_name,
              mapping.from_column_type,
              mapping.from_api_url,
              mapping.from_api_key,
              mapping.from_api_method,
              mapping.from_api_param_type,
              mapping.from_api_param_name,
              mapping.from_api_param_value,
              mapping.from_api_param_source,
              mapping.from_api_body, // FROM REST API Body
              mapping.to_connection_type,
              mapping.to_connection_id,
              mapping.to_table_name,
              mapping.to_column_name,
              mapping.to_column_type,
              mapping.to_api_url,
              mapping.to_api_key,
              mapping.to_api_method,
              mapping.to_api_body,
              mapping.mapping_order || index + 1,
              mapping.mapping_type || "direct", // 매핑 타입: direct 또는 fixed
              userId,
            ]
          );
          mappings.push(mappingResult.rows[0]);
        }

        return {
          ...batchConfig,
          batch_mappings: mappings,
        };
      });

      return {
        success: true,
        data: result as BatchConfig,
        message: "배치 설정이 성공적으로 생성되었습니다.",
      };
    } catch (error) {
      console.error("배치 설정 생성 오류:", error);
      return {
        success: false,
        message: "배치 설정 생성에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 배치 설정 수정 (회사별)
   */
  static async updateBatchConfig(
    id: number,
    data: UpdateBatchConfigRequest,
    userId?: string,
    userCompanyCode?: string
  ): Promise<ApiResponse<BatchConfig>> {
    try {
      // 기존 설정 확인
      const existingResult = await this.getBatchConfigById(id);
      if (!existingResult.success || !existingResult.data) {
        throw new Error(
          existingResult.message || "배치 설정을 찾을 수 없습니다."
        );
      }
      const existingConfig = existingResult.data;

      // 권한 체크 (회사 코드가 다르면 수정 불가)
      if (
        userCompanyCode &&
        userCompanyCode !== "*" &&
        existingConfig.company_code !== userCompanyCode
      ) {
        throw new Error("수정 권한이 없습니다.");
      }

      // 트랜잭션으로 업데이트
      const result = await transaction(async (client) => {
        // 동적 UPDATE 쿼리 생성
        const updateFields: string[] = [
          "updated_by = $1",
          "updated_date = NOW()",
        ];
        const updateValues: any[] = [userId];
        let paramIndex = 2;

        if (data.batchName) {
          updateFields.push(`batch_name = $${paramIndex++}`);
          updateValues.push(data.batchName);
        }
        if (data.description !== undefined) {
          updateFields.push(`description = $${paramIndex++}`);
          updateValues.push(data.description);
        }
        if (data.cronSchedule) {
          updateFields.push(`cron_schedule = $${paramIndex++}`);
          updateValues.push(data.cronSchedule);
        }
        if (data.isActive !== undefined) {
          updateFields.push(`is_active = $${paramIndex++}`);
          updateValues.push(data.isActive);
        }
        if (data.saveMode !== undefined) {
          updateFields.push(`save_mode = $${paramIndex++}`);
          updateValues.push(data.saveMode);
        }
        if (data.conflictKey !== undefined) {
          updateFields.push(`conflict_key = $${paramIndex++}`);
          updateValues.push(data.conflictKey || null);
        }
        if (data.authServiceName !== undefined) {
          updateFields.push(`auth_service_name = $${paramIndex++}`);
          updateValues.push(data.authServiceName || null);
        }
        if (data.dataArrayPath !== undefined) {
          updateFields.push(`data_array_path = $${paramIndex++}`);
          updateValues.push(data.dataArrayPath || null);
        }
        if (data.executionType !== undefined) {
          updateFields.push(`execution_type = $${paramIndex++}`);
          updateValues.push(data.executionType);
        }
        if (data.nodeFlowId !== undefined) {
          updateFields.push(`node_flow_id = $${paramIndex++}`);
          updateValues.push(data.nodeFlowId || null);
        }
        if (data.nodeFlowContext !== undefined) {
          updateFields.push(`node_flow_context = $${paramIndex++}`);
          updateValues.push(
            data.nodeFlowContext
              ? JSON.stringify(data.nodeFlowContext)
              : null
          );
        }

        // 배치 설정 업데이트
        const batchConfigResult = await client.query(
          `UPDATE batch_configs 
           SET ${updateFields.join(", ")}
           WHERE id = $${paramIndex}
           RETURNING *`,
          [...updateValues, id]
        );

        const batchConfig = batchConfigResult.rows[0];

        // 매핑이 제공된 경우 기존 매핑 삭제 후 새로 생성
        if (data.mappings) {
          await client.query(
            `DELETE FROM batch_mappings WHERE batch_config_id = $1`,
            [id]
          );

          const mappings = [];
          for (let index = 0; index < data.mappings.length; index++) {
            const mapping = data.mappings[index];
            const mappingResult = await client.query(
              `INSERT INTO batch_mappings 
               (batch_config_id, company_code, from_connection_type, from_connection_id, from_table_name, from_column_name, 
                from_column_type, from_api_url, from_api_key, from_api_method, from_api_param_type,
                from_api_param_name, from_api_param_value, from_api_param_source, from_api_body,
                to_connection_type, to_connection_id, to_table_name, to_column_name, to_column_type,
                to_api_url, to_api_key, to_api_method, to_api_body, mapping_order, mapping_type, created_by, created_date)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, NOW())
               RETURNING *`,
              [
                id,
                existingConfig.company_code, // 기존 설정의 company_code 유지
                mapping.from_connection_type,
                mapping.from_connection_id,
                mapping.from_table_name,
                mapping.from_column_name,
                mapping.from_column_type,
                mapping.from_api_url,
                mapping.from_api_key,
                mapping.from_api_method,
                mapping.from_api_param_type,
                mapping.from_api_param_name,
                mapping.from_api_param_value,
                mapping.from_api_param_source,
                mapping.from_api_body, // FROM REST API Body
                mapping.to_connection_type,
                mapping.to_connection_id,
                mapping.to_table_name,
                mapping.to_column_name,
                mapping.to_column_type,
                mapping.to_api_url,
                mapping.to_api_key,
                mapping.to_api_method,
                mapping.to_api_body,
                mapping.mapping_order || index + 1,
                mapping.mapping_type || "direct", // 매핑 타입: direct 또는 fixed
                userId,
              ]
            );
            mappings.push(mappingResult.rows[0]);
          }

          return {
            ...batchConfig,
            batch_mappings: mappings,
          };
        } else {
          return {
            ...batchConfig,
            batch_mappings: existingConfig.batch_mappings,
          };
        }
      });

      return {
        success: true,
        data: result as BatchConfig,
        message: "배치 설정이 성공적으로 수정되었습니다.",
      };
    } catch (error) {
      console.error("배치 설정 수정 오류:", error);
      return {
        success: false,
        message: "배치 설정 수정에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 배치 설정 삭제 (논리 삭제, 회사별)
   */
  static async deleteBatchConfig(
    id: number,
    userId?: string,
    userCompanyCode?: string
  ): Promise<ApiResponse<void>> {
    try {
      // 기존 설정 확인
      const existingResult = await this.getBatchConfigById(id);
      if (!existingResult.success || !existingResult.data) {
        throw new Error(
          existingResult.message || "배치 설정을 찾을 수 없습니다."
        );
      }
      const existingConfig = existingResult.data;

      // 권한 체크
      if (
        userCompanyCode &&
        userCompanyCode !== "*" &&
        existingConfig.company_code !== userCompanyCode
      ) {
        throw new Error("삭제 권한이 없습니다.");
      }

      // 물리 삭제 (CASCADE 설정에 따라 매핑도 삭제됨)
      await query(`DELETE FROM batch_configs WHERE id = $1`, [id]);

      return {
        success: true,
        message: "배치 설정이 성공적으로 삭제되었습니다.",
      };
    } catch (error) {
      console.error("배치 설정 삭제 오류:", error);
      return {
        success: false,
        message: "배치 설정 삭제에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * DB 연결 정보 조회
   */
  static async getConnections(): Promise<ApiResponse<ConnectionInfo[]>> {
    try {
      // BatchExternalDbService 사용
      const result = await BatchExternalDbService.getAvailableConnections();
      return result;
    } catch (error) {
      console.error("DB 연결 목록 조회 오류:", error);
      return {
        success: false,
        data: [],
        message: "DB 연결 목록 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 테이블 목록 조회
   */
  static async getTables(
    connectionType: "internal" | "external",
    connectionId?: number
  ): Promise<ApiResponse<TableInfo[]>> {
    try {
      if (connectionType === "internal") {
        // 내부 DB 테이블 조회
        const tables = await query<TableInfo>(
          `SELECT table_name, table_type, table_schema
           FROM information_schema.tables
           WHERE table_schema = 'public'
           AND table_type = 'BASE TABLE'
           ORDER BY table_name`
        );
        return {
          success: true,
          data: tables,
          message: `${tables.length}개의 테이블을 조회했습니다.`,
        };
      } else if (connectionId) {
        // 외부 DB 테이블 조회
        return await BatchExternalDbService.getTables(connectionId);
      } else {
        throw new Error("외부 연결 ID가 필요합니다.");
      }
    } catch (error) {
      console.error("테이블 목록 조회 오류:", error);
      return {
        success: false,
        data: [],
        message: "테이블 목록 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 컬럼 목록 조회
   */
  static async getColumns(
    tableName: string,
    connectionType: "internal" | "external",
    connectionId?: number
  ): Promise<ApiResponse<ColumnInfo[]>> {
    try {
      if (connectionType === "internal") {
        // 내부 DB 컬럼 조회
        const columns = await query<ColumnInfo>(
          `SELECT column_name, data_type, is_nullable, column_default
           FROM information_schema.columns
           WHERE table_schema = 'public' AND table_name = $1
           ORDER BY ordinal_position`,
          [tableName]
        );
        return {
          success: true,
          data: columns,
          message: `${columns.length}개의 컬럼을 조회했습니다.`,
        };
      } else if (connectionId) {
        // 외부 DB 컬럼 조회
        return await BatchExternalDbService.getColumns(connectionId, tableName);
      } else {
        throw new Error("외부 연결 ID가 필요합니다.");
      }
    } catch (error) {
      console.error("컬럼 목록 조회 오류:", error);
      return {
        success: false,
        data: [],
        message: "컬럼 목록 조회에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 데이터 미리보기
   */
  static async previewData(
    tableName: string,
    connectionType: "internal" | "external",
    connectionId?: number
  ): Promise<ApiResponse<any[]>> {
    try {
      if (connectionType === "internal") {
        // 내부 DB 데이터 조회
        const data = await query<any>(`SELECT * FROM ${tableName} LIMIT 10`);
        return {
          success: true,
          data,
          message: "데이터 미리보기 성공",
        };
      } else if (connectionId) {
        // 외부 DB 데이터 조회
        return await BatchExternalDbService.getDataFromTable(
          connectionId,
          tableName
        );
      } else {
        throw new Error("외부 연결 ID가 필요합니다.");
      }
    } catch (error) {
      console.error("데이터 미리보기 오류:", error);
      return {
        success: false,
        data: [],
        message: "데이터 미리보기에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * REST API 데이터 미리보기
   */
  static async previewRestApiData(
    apiUrl: string,
    apiKey: string,
    endpoint: string,
    method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
    paramInfo?: {
      paramType: "url" | "query";
      paramName: string;
      paramValue: string;
      paramSource: "static" | "dynamic";
    },
    body?: string
  ): Promise<ApiResponse<any>> {
    try {
      return await BatchExternalDbService.previewRestApiData(
        apiUrl,
        apiKey,
        endpoint,
        method,
        paramInfo,
        body
      );
    } catch (error) {
      console.error("REST API 미리보기 오류:", error);
      return {
        success: false,
        message: "REST API 미리보기에 실패했습니다.",
        error: error instanceof Error ? error.message : "알 수 없는 오류",
      };
    }
  }

  /**
   * 배치 유효성 검사
   */
  static async validateBatch(
    config: Partial<CreateBatchConfigRequest>
  ): Promise<BatchValidationResult> {
    const errors: string[] = [];

    if (!config.batchName) errors.push("배치 작업명이 필요합니다.");
    if (!config.cronSchedule) errors.push("Cron 스케줄이 필요합니다.");
    if (!config.mappings || config.mappings.length === 0) {
      errors.push("최소 하나 이상의 매핑이 필요합니다.");
    }

    // 추가 유효성 검사 로직...

    return {
      isValid: errors.length === 0,
      errors,
    };
  }

  /**
   * 테이블에서 데이터 조회 (연결 타입에 따라 내부/외부 DB 구분)
   */
  static async getDataFromTable(
    tableName: string,
    connectionType: "internal" | "external" = "internal",
    connectionId?: number
  ): Promise<any[]> {
    try {
      console.log(
        `[BatchService] 테이블에서 데이터 조회: ${tableName} (${connectionType}${connectionId ? `:${connectionId}` : ""})`
      );

      if (connectionType === "internal") {
        // 내부 DB에서 데이터 조회 (주의: SQL 인젝션 위험 - 실제 프로덕션에서는 테이블명 검증 필요)
        const result = await query<any>(`SELECT * FROM ${tableName} LIMIT 100`);
        console.log(
          `[BatchService] 내부 DB 데이터 조회 결과: ${result.length}개 레코드`
        );
        return result;
      } else if (connectionType === "external" && connectionId) {
        // 외부 DB에서 데이터 조회
        const result = await BatchExternalDbService.getDataFromTable(
          connectionId,
          tableName
        );
        if (result.success && result.data) {
          console.log(
            `[BatchService] 외부 DB 데이터 조회 결과: ${result.data.length}개 레코드`
          );
          return result.data;
        } else {
          console.error(`외부 DB 데이터 조회 실패: ${result.message}`);
          return [];
        }
      } else {
        throw new Error(
          `잘못된 연결 타입 또는 연결 ID: ${connectionType}, ${connectionId}`
        );
      }
    } catch (error) {
      console.error(`테이블 데이터 조회 오류 (${tableName}):`, error);
      throw error;
    }
  }

  /**
   * 테이블에서 특정 컬럼들만 조회 (연결 타입에 따라 내부/외부 DB 구분)
   */
  static async getDataFromTableWithColumns(
    tableName: string,
    columns: string[],
    connectionType: "internal" | "external" = "internal",
    connectionId?: number
  ): Promise<any[]> {
    try {
      console.log(
        `[BatchService] 테이블에서 컬럼 지정 데이터 조회: ${tableName} (${connectionType})`
      );

      if (connectionType === "internal") {
        // 내부 DB
        const columnString = columns.join(", ");
        const result = await query<any>(
          `SELECT ${columnString} FROM ${tableName} LIMIT 100`
        );
        return result;
      } else if (connectionType === "external" && connectionId) {
        // 외부 DB
        const result = await BatchExternalDbService.getDataFromTableWithColumns(
          connectionId,
          tableName,
          columns
        );
        if (result.success && result.data) {
          return result.data;
        } else {
          throw new Error(result.message || "외부 DB 조회 실패");
        }
      } else {
        throw new Error("잘못된 연결 설정입니다.");
      }
    } catch (error) {
      console.error(`데이터 조회 오류 (${tableName}):`, error);
      throw error;
    }
  }

  /**
   * 테이블에 데이터 삽입 (연결 타입에 따라 내부/외부 DB 구분)
   * @param tableName 테이블명
   * @param data 삽입할 데이터 배열
   * @param connectionType 연결 타입 (internal/external)
   * @param connectionId 외부 연결 ID
   * @param saveMode 저장 모드 (INSERT/UPSERT)
   * @param conflictKey UPSERT 시 충돌 기준 컬럼명
   */
  static async insertDataToTable(
    tableName: string,
    data: any[],
    connectionType: "internal" | "external" = "internal",
    connectionId?: number,
    saveMode: "INSERT" | "UPSERT" = "INSERT",
    conflictKey?: string
  ): Promise<{
    successCount: number;
    failedCount: number;
  }> {
    try {
      console.log(
        `[BatchService] 테이블에 데이터 ${saveMode}: ${tableName} (${connectionType}${connectionId ? `:${connectionId}` : ""}), ${data.length}개 레코드${conflictKey ? `, 충돌키: ${conflictKey}` : ""}`
      );

      if (!data || data.length === 0) {
        return { successCount: 0, failedCount: 0 };
      }

      if (connectionType === "internal") {
        // 내부 DB에 데이터 삽입
        let successCount = 0;
        let failedCount = 0;

        // 각 레코드를 개별적으로 삽입
        for (const record of data) {
          try {
            const columns = Object.keys(record);
            const values = Object.values(record);
            const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");

            let queryStr: string;

            if (saveMode === "UPSERT" && conflictKey) {
              // UPSERT 모드: ON CONFLICT DO UPDATE
              // 충돌 키를 제외한 컬럼들만 UPDATE
              const updateColumns = columns.filter(
                (col) => col !== conflictKey
              );

              // 업데이트할 컬럼이 없으면 DO NOTHING 사용
              if (updateColumns.length === 0) {
                queryStr = `INSERT INTO ${tableName} (${columns.join(", ")}) 
                  VALUES (${placeholders}) 
                  ON CONFLICT (${conflictKey}) 
                  DO NOTHING`;
              } else {
                const updateSet = updateColumns
                  .map((col) => `${col} = EXCLUDED.${col}`)
                  .join(", ");

                // updated_date 컬럼이 있으면 현재 시간으로 업데이트
                const hasUpdatedDate = columns.includes("updated_date");
                const finalUpdateSet = hasUpdatedDate
                  ? `${updateSet}, updated_date = NOW()`
                  : updateSet;

                queryStr = `INSERT INTO ${tableName} (${columns.join(", ")}) 
                  VALUES (${placeholders}) 
                  ON CONFLICT (${conflictKey}) 
                  DO UPDATE SET ${finalUpdateSet}`;
              }
            } else {
              // INSERT 모드: 기존 방식
              queryStr = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${placeholders})`;
            }

            await query(queryStr, values);
            successCount++;
          } catch (insertError) {
            console.error(
              `내부 DB 데이터 ${saveMode} 실패 (${tableName}):`,
              insertError
            );
            failedCount++;
          }
        }

        return { successCount, failedCount };
      } else if (connectionType === "external" && connectionId) {
        // 외부 DB에 데이터 삽입 (UPSERT는 내부 DB만 지원)
        if (saveMode === "UPSERT") {
          console.warn(
            `[BatchService] 외부 DB는 UPSERT를 지원하지 않습니다. INSERT로 실행합니다.`
          );
        }

        const result = await BatchExternalDbService.insertDataToTable(
          connectionId,
          tableName,
          data
        );

        if (result.success && result.data) {
          return result.data;
        } else {
          console.error(`외부 DB 데이터 삽입 실패: ${result.message}`);
          // 실패 시 전체 실패로 간주하지 않고 0/전체 로 반환
          return { successCount: 0, failedCount: data.length };
        }
      } else {
        throw new Error(
          `잘못된 연결 타입 또는 연결 ID: ${connectionType}, ${connectionId}`
        );
      }
    } catch (error) {
      console.error(`데이터 ${saveMode} 오류 (${tableName}):`, error);
      return { successCount: 0, failedCount: data ? data.length : 0 };
    }
  }
}
