import { query, queryOne, transaction, getPool } from "../database/db";
import { EventTriggerService } from "./eventTriggerService";
import { DataflowControlService } from "./dataflowControlService";
import tableCategoryValueService from "./tableCategoryValueService";

export interface FormDataResult {
  id: number;
  screenId: number;
  tableName: string;
  data: Record<string, any>;
  createdAt: Date | null;
  updatedAt: Date | null;
  createdBy: string;
  updatedBy: string;
}

export interface PartialUpdateResult {
  success: boolean;
  data: any;
  message: string;
}

export interface PaginatedFormData {
  content: FormDataResult[];
  totalElements: number;
  totalPages: number;
  currentPage: number;
  size: number;
}

export interface ValidationError {
  field: string;
  message: string;
  code: string;
}

export interface ValidationResult {
  valid: boolean;
  errors: ValidationError[];
}

export interface TableColumn {
  columnName: string;
  dataType: string;
  nullable: boolean;
  primaryKey: boolean;
  maxLength?: number | null;
  defaultValue?: any;
}

export class DynamicFormService {
  private dataflowControlService = new DataflowControlService();
  /**
   * 값을 PostgreSQL 타입에 맞게 변환
   */
  private convertValueForPostgreSQL(value: any, dataType: string): any {
    if (value === null || value === undefined || value === "") {
      return null;
    }

    const lowerDataType = dataType.toLowerCase();

    // 숫자 타입 처리
    if (
      lowerDataType.includes("integer") ||
      lowerDataType.includes("bigint") ||
      lowerDataType.includes("serial")
    ) {
      return parseInt(value) || null;
    }

    if (
      lowerDataType.includes("numeric") ||
      lowerDataType.includes("decimal") ||
      lowerDataType.includes("real") ||
      lowerDataType.includes("double")
    ) {
      return parseFloat(value) || null;
    }

    // 불린 타입 처리
    if (lowerDataType.includes("boolean")) {
      if (typeof value === "boolean") return value;
      if (typeof value === "string") {
        return value.toLowerCase() === "true" || value === "1";
      }
      return Boolean(value);
    }

    // 날짜/시간 타입 처리
    if (
      lowerDataType.includes("date") ||
      lowerDataType.includes("timestamp") ||
      lowerDataType.includes("time")
    ) {
      if (typeof value === "string") {
        // 빈 문자열이면 null 반환
        if (value.trim() === "") {
          return null;
        }

        try {
          // YYYY-MM-DD 형식인 경우
          if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
            // DATE 타입이면 문자열 그대로 유지
            if (lowerDataType === "date") {
              console.log(
                `📅 날짜 문자열 유지: ${value} -> "${value}" (DATE 타입)`
              );
              return value; // 문자열 그대로 반환
            }
            // TIMESTAMP 타입이면 Date 객체로 변환
            else {
              console.log(
                `📅 날짜시간 변환: ${value} -> Date 객체 (TIMESTAMP 타입)`
              );
              return new Date(value + "T00:00:00");
            }
          }
          // 다른 날짜 형식도 Date 객체로 변환
          else {
            console.log(`📅 날짜 타입 변환: ${value} -> Date 객체`);
            return new Date(value);
          }
        } catch (error) {
          console.error(`❌ 날짜 변환 실패: ${value}`, error);
          return null;
        }
      }

      // 이미 Date 객체인 경우 그대로 반환
      if (value instanceof Date) {
        return value;
      }

      // 숫자인 경우 timestamp로 처리
      if (typeof value === "number") {
        return new Date(value);
      }

      return null;
    }

    // 기본적으로 문자열로 반환
    return value;
  }

  /**
   * 테이블의 컬럼 정보 조회 (타입 포함)
   */
  private async getTableColumnInfo(
    tableName: string
  ): Promise<Array<{ column_name: string; data_type: string }>> {
    try {
      const result = await query<{ column_name: string; data_type: string }>(
        `SELECT column_name, data_type
         FROM information_schema.columns
         WHERE table_name = $1
         AND table_schema = 'public'`,
        [tableName]
      );

      return result;
    } catch (error) {
      console.error(`테이블 ${tableName}의 컬럼 정보 조회 실패:`, error);
      return [];
    }
  }

  /**
   * 테이블의 컬럼명 목록 조회 (간단 버전)
   */
  private async getTableColumnNames(tableName: string): Promise<string[]> {
    try {
      const result = await query<{ column_name: string }>(
        `SELECT column_name
         FROM information_schema.columns
         WHERE table_name = $1
         AND table_schema = 'public'`,
        [tableName]
      );

      return result.map((row) => row.column_name);
    } catch (error) {
      console.error(`❌ 테이블 ${tableName} 컬럼 정보 조회 실패:`, error);
      return [];
    }
  }

  /**
   * 테이블의 Primary Key 컬럼 조회 (공개 메서드로 변경)
   */
  async getTablePrimaryKeys(tableName: string): Promise<string[]> {
    try {
      const result = await query<{ column_name: string }>(
        `SELECT kcu.column_name
         FROM information_schema.table_constraints tc
         JOIN information_schema.key_column_usage kcu
           ON tc.constraint_name = kcu.constraint_name
         WHERE tc.table_name = $1
           AND tc.constraint_type = 'PRIMARY KEY'
           AND tc.table_schema = 'public'`,
        [tableName]
      );

      return result.map((row) => row.column_name);
    } catch (error) {
      console.error(`❌ 테이블 ${tableName} Primary Key 조회 실패:`, error);
      return [];
    }
  }

  /**
   * VIEW인 경우 원본(base) 테이블명을 반환, 일반 테이블이면 그대로 반환
   */
  async resolveBaseTable(tableName: string): Promise<string> {
    try {
      const result = await query<{ table_type: string }>(
        `SELECT table_type FROM information_schema.tables
         WHERE table_name = $1 AND table_schema = 'public'`,
        [tableName]
      );

      if (result.length === 0 || result[0].table_type !== 'VIEW') {
        return tableName;
      }

      // VIEW의 FROM 절에서 첫 번째 테이블을 추출
      const viewDef = await query<{ view_definition: string }>(
        `SELECT view_definition FROM information_schema.views
         WHERE table_name = $1 AND table_schema = 'public'`,
        [tableName]
      );

      if (viewDef.length > 0) {
        const definition = viewDef[0].view_definition;
        // PostgreSQL은 뷰 정의를 "FROM (테이블명 별칭 LEFT JOIN ...)" 형태로 저장
        const fromMatch = definition.match(/FROM\s+\(?(?:public\.)?(\w+)\s/i);
        if (fromMatch) {
          const baseTable = fromMatch[1];
          console.log(`🔄 VIEW ${tableName} → 원본 테이블 ${baseTable} 으로 전환`);
          return baseTable;
        }
      }

      return tableName;
    } catch (error) {
      console.error(`❌ VIEW 원본 테이블 조회 실패:`, error);
      return tableName;
    }
  }

  /**
   * 폼 데이터 저장 (실제 테이블에 직접 저장)
   */
  async saveFormData(
    screenId: number,
    tableNameInput: string,
    data: Record<string, any>,
    ipAddress?: string
  ): Promise<FormDataResult> {
    // VIEW인 경우 원본 테이블로 전환
    const tableName = await this.resolveBaseTable(tableNameInput);
    try {
      console.log("💾 서비스: 실제 테이블에 폼 데이터 저장 시작:", {
        screenId,
        tableName,
        originalTable: tableNameInput !== tableName ? tableNameInput : undefined,
        data,
      });

      // 테이블의 실제 컬럼 정보와 Primary Key 조회
      const tableColumns = await this.getTableColumnNames(tableName);
      const primaryKeys = await this.getTablePrimaryKeys(tableName);
      console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);
      console.log(`🔑 테이블 ${tableName}의 Primary Key:`, primaryKeys);

      // 메타데이터 제거 (실제 테이블 컬럼이 아님)
      const {
        created_by,
        updated_by,
        writer,
        company_code,
        screen_id,
        ...actualData
      } = data;

      // 기본 데이터 준비
      const dataToInsert: any = { ...actualData };

      // 테이블에 존재하는 공통 필드들만 추가
      if (tableColumns.includes("created_at")) {
        dataToInsert.created_at = new Date();
      }
      if (tableColumns.includes("updated_at")) {
        dataToInsert.updated_at = new Date();
      }
      if (tableColumns.includes("regdate") && !dataToInsert.regdate) {
        dataToInsert.regdate = new Date();
      }
      // created_date는 항상 현재 시간으로 설정 (기존 값 무시)
      if (tableColumns.includes("created_date")) {
        dataToInsert.created_date = new Date();
      }
      if (tableColumns.includes("updated_date") && !dataToInsert.updated_date) {
        dataToInsert.updated_date = new Date();
      }

      // 작성자 정보 추가 (writer 컬럼 우선, 없으면 created_by/updated_by)
      if (writer && tableColumns.includes("writer")) {
        dataToInsert.writer = writer;
      }
      if (created_by && tableColumns.includes("created_by")) {
        dataToInsert.created_by = created_by;
      }
      if (updated_by && tableColumns.includes("updated_by")) {
        dataToInsert.updated_by = updated_by;
      }
      if (company_code && tableColumns.includes("company_code")) {
        // company_code가 UUID 형태(36자)라면 하이픈 제거하여 32자로 만듦
        let processedCompanyCode = company_code;
        if (
          typeof company_code === "string" &&
          company_code.length === 36 &&
          company_code.includes("-")
        ) {
          processedCompanyCode = company_code.replace(/-/g, "");
          console.log(
            `🔧 company_code 길이 조정: "${company_code}" -> "${processedCompanyCode}" (${processedCompanyCode.length}자)`
          );
        }
        // 여전히 32자를 초과하면 앞의 32자만 사용
        if (
          typeof processedCompanyCode === "string" &&
          processedCompanyCode.length > 32
        ) {
          processedCompanyCode = processedCompanyCode.substring(0, 32);
          console.log(
            `⚠️ company_code 길이 제한: 앞의 32자로 자름 -> "${processedCompanyCode}"`
          );
        }
        dataToInsert.company_code = processedCompanyCode;
      }

      // 날짜/시간 문자열을 적절한 형태로 변환
      Object.keys(dataToInsert).forEach((key) => {
        const value = dataToInsert[key];

        // 날짜/시간 관련 컬럼명 패턴 체크 (regdate, created_at, updated_at 등)
        if (
          typeof value === "string" &&
          (key.toLowerCase().includes("date") ||
            key.toLowerCase().includes("time") ||
            key.toLowerCase().includes("created") ||
            key.toLowerCase().includes("updated") ||
            key.toLowerCase().includes("reg"))
        ) {
          // YYYY-MM-DD HH:mm:ss 형태의 문자열을 Date 객체로 변환
          if (value.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) {
            console.log(`📅 날짜시간 변환: ${key} = "${value}" -> Date 객체`);
            dataToInsert[key] = new Date(value);
          }
          // YYYY-MM-DD 형태의 문자열은 그대로 유지 (DATE 타입으로 저장)
          else if (value.match(/^\d{4}-\d{2}-\d{2}$/)) {
            console.log(
              `📅 날짜 유지: ${key} = "${value}" -> 문자열 그대로 (DATE 타입)`
            );
            // dataToInsert[key] = value; // 문자열 그대로 유지 (이미 올바른 형식)
          }
        }
      });

      // 📝 RepeaterInput 데이터 처리 (JSON 배열을 개별 레코드로 분해)
      const repeaterData: Array<{
        data: Record<string, any>[];
        targetTable?: string;
        componentId: string;
      }> = [];
      Object.keys(dataToInsert).forEach((key) => {
        const value = dataToInsert[key];

        // 🔥 RepeaterInput 데이터인지 확인 (배열 객체 또는 JSON 문자열)
        let parsedArray: any[] | null = null;

        // 1️⃣ 이미 배열 객체인 경우 (ModalRepeaterTable, SelectedItemsDetailInput 등)
        if (Array.isArray(value) && value.length > 0) {
          parsedArray = value;
          console.log(
            `🔄 배열 객체 Repeater 데이터 감지: ${key}, ${parsedArray.length}개 항목`
          );
        }
        // 2️⃣ JSON 문자열인 경우 (레거시 RepeaterInput)
        else if (
          typeof value === "string" &&
          value.trim().startsWith("[") &&
          value.trim().endsWith("]")
        ) {
          try {
            parsedArray = JSON.parse(value);
            console.log(
              `🔄 JSON 문자열 Repeater 데이터 감지: ${key}, ${parsedArray?.length || 0}개 항목`
            );
          } catch (parseError) {
            console.log(`⚠️ JSON 파싱 실패: ${key}`);
          }
        }

        // 파싱된 배열이 있으면 처리
        if (
          parsedArray &&
          Array.isArray(parsedArray) &&
          parsedArray.length > 0
        ) {
          // 컴포넌트 설정에서 targetTable 추출 (컴포넌트 ID를 통해)
          // 프론트엔드에서 { data: [...], targetTable: "..." } 형식으로 전달될 수 있음
          let targetTable: string | undefined;
          let actualData = parsedArray;

          // 첫 번째 항목에 _targetTable이 있는지 확인 (프론트엔드에서 메타데이터 전달)
          if (parsedArray[0] && parsedArray[0]._targetTable) {
            targetTable = parsedArray[0]._targetTable;
            actualData = parsedArray.map(({ _targetTable, ...item }) => item);
          }

          repeaterData.push({
            data: actualData,
            targetTable,
            componentId: key,
          });
          delete dataToInsert[key]; // 원본 배열 데이터는 제거

          console.log(`✅ Repeater 데이터 추가: ${key}`, {
            targetTable: targetTable || "없음 (화면 설계에서 설정 필요)",
            itemCount: actualData.length,
            firstItem: actualData[0],
          });
        }
      });

      // 🔥 Repeater targetTable이 메인 테이블과 같으면 분리해서 저장
      const separateRepeaterData: typeof repeaterData = [];
      const mergedRepeaterData: typeof repeaterData = [];

      repeaterData.forEach((repeater) => {
        if (repeater.targetTable && repeater.targetTable !== tableName) {
          // 다른 테이블: 나중에 별도 저장
          separateRepeaterData.push(repeater);
        } else {
          // 같은 테이블: 메인 INSERT와 병합 (헤더+품목을 한 번에)
          mergedRepeaterData.push(repeater);
        }
      });

      console.log(`🔄 Repeater 데이터 분류:`, {
        separate: separateRepeaterData.length, // 별도 테이블
        merged: mergedRepeaterData.length, // 메인 테이블과 병합
      });

      // 존재하지 않는 컬럼 제거
      Object.keys(dataToInsert).forEach((key) => {
        if (!tableColumns.includes(key)) {
          console.log(
            `⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제거됨`
          );
          delete dataToInsert[key];
        }
      });

      console.log("🎯 실제 테이블에 삽입할 데이터:", {
        tableName,
        dataToInsert,
      });

      // 카테고리 타입 컬럼의 라벨 값을 코드 값으로 변환 (엑셀 업로드 등 지원)
      console.log("🏷️ 카테고리 라벨→코드 변환 시작...");
      const companyCodeForCategory = company_code || "*";
      const { convertedData: categoryConvertedData, conversions } = 
        await tableCategoryValueService.convertCategoryLabelsToCodesForData(
          tableName,
          companyCodeForCategory,
          dataToInsert
        );
      
      if (conversions.length > 0) {
        console.log(`🏷️ 카테고리 라벨→코드 변환 완료: ${conversions.length}개`, conversions);
        // 변환된 데이터로 교체
        Object.assign(dataToInsert, categoryConvertedData);
      } else {
        console.log("🏷️ 카테고리 라벨→코드 변환 없음 (카테고리 컬럼 없거나 이미 코드 값)");
      }

      // 테이블 컬럼 정보 조회하여 타입 변환 적용
      console.log("🔍 테이블 컬럼 정보 조회 중...");
      const columnInfo = await this.getTableColumnInfo(tableName);
      console.log("📊 테이블 컬럼 정보:", columnInfo);

      // 각 컬럼의 타입에 맞게 데이터 변환
      Object.keys(dataToInsert).forEach((columnName) => {
        const column = columnInfo.find((col) => col.column_name === columnName);
        if (column) {
          const originalValue = dataToInsert[columnName];
          const convertedValue = this.convertValueForPostgreSQL(
            originalValue,
            column.data_type
          );

          if (originalValue !== convertedValue) {
            console.log(
              `🔄 타입 변환: ${columnName} (${column.data_type}) = "${originalValue}" -> ${convertedValue}`
            );
            dataToInsert[columnName] = convertedValue;
          }
        }
      });

      console.log("✅ 타입 변환 완료된 데이터:", dataToInsert);

      // 동적 SQL을 사용하여 실제 테이블에 UPSERT
      const columns = Object.keys(dataToInsert);
      const values: any[] = Object.values(dataToInsert);
      const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");

      let upsertQuery: string;

      if (primaryKeys.length > 0) {
        // Primary Key가 있는 경우 UPSERT 사용
        const conflictColumns = primaryKeys.join(", ");
        const updateSet = columns
          .filter((col) => !primaryKeys.includes(col)) // Primary Key는 UPDATE에서 제외
          .map((col) => `${col} = EXCLUDED.${col}`)
          .join(", ");

        if (updateSet) {
          upsertQuery = `
            INSERT INTO ${tableName} (${columns.join(", ")})
            VALUES (${placeholders})
            ON CONFLICT (${conflictColumns})
            DO UPDATE SET ${updateSet}
            RETURNING *
          `;
        } else {
          // 업데이트할 컬럼이 없는 경우 (Primary Key만 있는 테이블)
          upsertQuery = `
            INSERT INTO ${tableName} (${columns.join(", ")})
            VALUES (${placeholders})
            ON CONFLICT (${conflictColumns})
            DO NOTHING
            RETURNING *
          `;
        }
      } else {
        // Primary Key가 없는 경우 일반 INSERT
        upsertQuery = `
          INSERT INTO ${tableName} (${columns.join(", ")})
          VALUES (${placeholders})
          RETURNING *
        `;
      }

      console.log("📝 실행할 UPSERT SQL:", upsertQuery);
      console.log("📊 SQL 파라미터:", values);

      // 로그 트리거를 위한 세션 변수 설정 및 UPSERT 실행 (트랜잭션 내에서)
      const userId = data.updated_by || data.created_by || "system";
      const clientIp = ipAddress || "unknown";

      let result: any[];

      // 🔥 메인 테이블과 병합할 Repeater가 있으면 각 품목별로 INSERT
      if (mergedRepeaterData.length > 0) {
        console.log(
          `🔄 메인 테이블 병합 모드: ${mergedRepeaterData.length}개 Repeater를 개별 레코드로 저장`
        );

        result = [];

        for (const repeater of mergedRepeaterData) {
          for (const item of repeater.data) {
            // 헤더 + 품목을 병합
            // item에서 created_date 제거 (dataToInsert의 현재 시간 유지)
            const { created_date: _, ...itemWithoutCreatedDate } = item;
            const rawMergedData = {
              ...dataToInsert,
              ...itemWithoutCreatedDate,
            };

            // 🆕 새 레코드 저장 시 id 제거하여 새 UUID 생성되도록 함
            // _existingRecord가 명시적으로 true인 경우에만 기존 레코드로 처리 (UPDATE)
            // 그 외의 경우는 모두 새 레코드로 처리 (INSERT)
            const isExistingRecord = rawMergedData._existingRecord === true;

            if (!isExistingRecord) {
              // 새 레코드: id 제거하여 새 UUID 자동 생성
              const oldId = rawMergedData.id;
              delete rawMergedData.id;
              console.log(`🆕 새 레코드로 처리 (id 제거됨: ${oldId})`);
            } else {
              console.log(`📝 기존 레코드 수정 (id 유지: ${rawMergedData.id})`);
            }

            // 메타 플래그 제거
            delete rawMergedData._isNewItem;
            delete rawMergedData._existingRecord;

            // 🆕 실제 테이블 컬럼만 필터링 (조인/계산 컬럼 제외)
            const validColumnNames = columnInfo.map((col) => col.column_name);
            const mergedData: Record<string, any> = {};

            Object.keys(rawMergedData).forEach((columnName) => {
              // 실제 테이블 컬럼인지 확인
              if (validColumnNames.includes(columnName)) {
                const column = columnInfo.find(
                  (col) => col.column_name === columnName
                );
                if (column) {
                  // 타입 변환
                  mergedData[columnName] = this.convertValueForPostgreSQL(
                    rawMergedData[columnName],
                    column.data_type
                  );
                } else {
                  mergedData[columnName] = rawMergedData[columnName];
                }
              } else {
                console.log(
                  `⚠️ 조인/계산 컬럼 제외: ${columnName} (값: ${rawMergedData[columnName]})`
                );
              }
            });

            const mergedColumns = Object.keys(mergedData);
            const mergedValues: any[] = Object.values(mergedData);
            const mergedPlaceholders = mergedValues
              .map((_, index) => `$${index + 1}`)
              .join(", ");

            let mergedUpsertQuery: string;
            if (primaryKeys.length > 0) {
              const conflictColumns = primaryKeys.join(", ");
              const updateSet = mergedColumns
                .filter((col) => !primaryKeys.includes(col))
                .map((col) => `${col} = EXCLUDED.${col}`)
                .join(", ");

              mergedUpsertQuery = updateSet
                ? `INSERT INTO ${tableName} (${mergedColumns.join(", ")})
                   VALUES (${mergedPlaceholders})
                   ON CONFLICT (${conflictColumns})
                   DO UPDATE SET ${updateSet}
                   RETURNING *`
                : `INSERT INTO ${tableName} (${mergedColumns.join(", ")})
                   VALUES (${mergedPlaceholders})
                   ON CONFLICT (${conflictColumns})
                   DO NOTHING
                   RETURNING *`;
            } else {
              mergedUpsertQuery = `INSERT INTO ${tableName} (${mergedColumns.join(", ")})
                                   VALUES (${mergedPlaceholders})
                                   RETURNING *`;
            }

            console.log(`📝 병합 INSERT:`, { mergedData });

            const itemResult = await transaction(async (client) => {
              await client.query(`SET LOCAL app.user_id = '${userId}'`);
              await client.query(`SET LOCAL app.ip_address = '${clientIp}'`);
              const res = await client.query(mergedUpsertQuery, mergedValues);
              return res.rows[0];
            });

            result.push(itemResult);
          }
        }

        console.log(`✅ 병합 저장 완료: ${result.length}개 레코드`);
      } else {
        // 일반 모드: 헤더만 저장
        result = await transaction(async (client) => {
          await client.query(`SET LOCAL app.user_id = '${userId}'`);
          await client.query(`SET LOCAL app.ip_address = '${clientIp}'`);
          const res = await client.query(upsertQuery, values);
          return res.rows;
        });

        console.log("✅ 서비스: 실제 테이블 저장 성공:", result);
      }

      // 결과를 표준 형식으로 변환
      const insertedRecord = Array.isArray(result) ? result[0] : result;

      // 📝 별도 테이블 Repeater 데이터 저장
      if (separateRepeaterData.length > 0) {
        console.log(
          `🔄 별도 테이블 Repeater 저장 시작: ${separateRepeaterData.length}개`
        );

        for (const repeater of separateRepeaterData) {
          const targetTableName = repeater.targetTable || tableName;
          console.log(
            `📝 Repeater "${repeater.componentId}" → 테이블 "${targetTableName}"에 ${repeater.data.length}개 항목 저장`
          );

          // 대상 테이블의 컬럼 및 기본키 정보 조회
          const targetTableColumns =
            await this.getTableColumns(targetTableName);
          const targetPrimaryKeys = await this.getPrimaryKeys(targetTableName);

          // 컬럼명만 추출
          const targetColumnNames = targetTableColumns.map(
            (col) => col.columnName
          );

          // 각 항목을 저장
          for (let i = 0; i < repeater.data.length; i++) {
            const item = repeater.data[i];
            const itemData: Record<string, any> = {
              ...item,
              created_by,
              updated_by,
              regdate: new Date(),
              // 🔥 멀티테넌시: company_code 필수 추가
              company_code: data.company_code || company_code,
            };

            // 🔥 별도 테이블인 경우에만 외래키 추가
            // (같은 테이블이면 이미 병합 모드에서 처리됨)

            // 대상 테이블에 존재하는 컬럼만 필터링
            Object.keys(itemData).forEach((key) => {
              if (!targetColumnNames.includes(key)) {
                delete itemData[key];
              }
            });

            // 타입 변환 적용
            Object.keys(itemData).forEach((columnName) => {
              const column = targetTableColumns.find(
                (col) => col.columnName === columnName
              );
              if (column) {
                itemData[columnName] = this.convertValueForPostgreSQL(
                  itemData[columnName],
                  column.dataType
                );
              }
            });

            // UPSERT 쿼리 생성
            const itemColumns = Object.keys(itemData);
            const itemValues: any[] = Object.values(itemData);
            const itemPlaceholders = itemValues
              .map((_, index) => `$${index + 1}`)
              .join(", ");

            let itemUpsertQuery: string;
            if (targetPrimaryKeys.length > 0) {
              const conflictColumns = targetPrimaryKeys.join(", ");
              const updateSet = itemColumns
                .filter((col) => !targetPrimaryKeys.includes(col))
                .map((col) => `${col} = EXCLUDED.${col}`)
                .join(", ");

              if (updateSet) {
                itemUpsertQuery = `
                  INSERT INTO ${targetTableName} (${itemColumns.join(", ")})
                  VALUES (${itemPlaceholders})
                  ON CONFLICT (${conflictColumns})
                  DO UPDATE SET ${updateSet}
                  RETURNING *
                `;
              } else {
                itemUpsertQuery = `
                  INSERT INTO ${targetTableName} (${itemColumns.join(", ")})
                  VALUES (${itemPlaceholders})
                  ON CONFLICT (${conflictColumns})
                  DO NOTHING
                  RETURNING *
                `;
              }
            } else {
              itemUpsertQuery = `
                INSERT INTO ${targetTableName} (${itemColumns.join(", ")})
                VALUES (${itemPlaceholders})
                RETURNING *
              `;
            }

            console.log(
              `  📝 항목 ${i + 1}/${repeater.data.length} 저장:`,
              itemData
            );
            await query<any>(itemUpsertQuery, itemValues);
          }

          console.log(`  ✅ Repeater "${repeater.componentId}" 저장 완료`);
        }

        console.log(`✅ 모든 RepeaterInput 데이터 저장 완료`);
      }

      // 🔥 조건부 연결 실행 (INSERT 트리거)
      try {
        if (company_code) {
          await EventTriggerService.executeEventTriggers(
            "insert",
            tableName,
            insertedRecord as Record<string, any>,
            company_code
          );
          console.log("🚀 조건부 연결 트리거 실행 완료 (INSERT)");
        }
      } catch (triggerError) {
        console.error("⚠️ 조건부 연결 트리거 실행 오류:", triggerError);
        // 트리거 오류는 로그만 남기고 메인 저장 프로세스는 계속 진행
      }

      // 🎯 제어관리 실행 (새로 추가)
      try {
        // savedData 또는 insertedRecord에서 company_code 추출
        const recordCompanyCode =
          (insertedRecord as Record<string, any>)?.company_code ||
          dataToInsert.company_code ||
          "*";

        await this.executeDataflowControlIfConfigured(
          screenId,
          tableName,
          insertedRecord as Record<string, any>,
          "insert",
          created_by || "system",
          recordCompanyCode
        );
      } catch (controlError) {
        console.error("⚠️ 제어관리 실행 오류:", controlError);
        // 제어관리 오류는 로그만 남기고 메인 저장 프로세스는 계속 진행
      }

      return {
        id: insertedRecord.id || insertedRecord.objid || 0,
        screenId: screenId,
        tableName: tableName,
        data: insertedRecord as Record<string, any>,
        createdAt: insertedRecord.created_at || new Date(),
        updatedAt: insertedRecord.updated_at || new Date(),
        createdBy: insertedRecord.created_by || created_by || "system",
        updatedBy: insertedRecord.updated_by || updated_by || "system",
      };
    } catch (error) {
      console.error("❌ 서비스: 실제 테이블 저장 실패:", error);
      throw new Error(`실제 테이블 저장 실패: ${error}`);
    }
  }

  /**
   * 폼 데이터 부분 업데이트 (변경된 필드만 업데이트)
   */
  async updateFormDataPartial(
    id: string | number, // 🔧 UUID 문자열도 지원
    tableNameInput: string,
    originalData: Record<string, any>,
    newData: Record<string, any>
  ): Promise<PartialUpdateResult> {
    // VIEW인 경우 원본 테이블로 전환
    const tableName = await this.resolveBaseTable(tableNameInput);
    try {
      console.log("🔄 서비스: 부분 업데이트 시작:", {
        id,
        tableName,
        originalTable: tableNameInput !== tableName ? tableNameInput : undefined,
        originalData,
        newData,
      });

      // 테이블의 실제 컬럼 정보 조회
      const tableColumns = await this.getTableColumnNames(tableName);
      console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);

      // 변경된 필드만 찾기
      const changedFields: Record<string, any> = {};

      for (const [key, value] of Object.entries(newData)) {
        // 메타데이터 필드 제외
        if (
          ["created_by", "updated_by", "company_code", "screen_id"].includes(
            key
          )
        ) {
          continue;
        }

        // 테이블에 존재하지 않는 컬럼 제외
        if (!tableColumns.includes(key)) {
          console.log(
            `⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제외됨`
          );
          continue;
        }

        // 값이 실제로 변경된 경우만 포함
        if (originalData[key] !== value) {
          changedFields[key] = value;
          console.log(
            `📝 변경된 필드: ${key} = "${originalData[key]}" → "${value}"`
          );
        }
      }

      // 변경된 필드가 없으면 업데이트 건너뛰기
      if (Object.keys(changedFields).length === 0) {
        console.log("📋 변경된 필드가 없습니다. 업데이트를 건너뜁니다.");
        return {
          success: true,
          data: originalData,
          message: "변경사항이 없어 업데이트하지 않았습니다.",
        };
      }

      // 업데이트 관련 필드 추가 (변경사항이 있는 경우에만)
      if (tableColumns.includes("updated_at")) {
        changedFields.updated_at = new Date();
      }
      // updated_date 컬럼도 지원 (sales_order_mng 등)
      if (tableColumns.includes("updated_date")) {
        changedFields.updated_date = new Date();
        console.log("📅 updated_date 자동 추가:", changedFields.updated_date);
      }

      console.log("🎯 실제 업데이트할 필드들:", changedFields);

      // 동적으로 기본키 조회
      const primaryKeys = await this.getTablePrimaryKeys(tableName);
      if (!primaryKeys || primaryKeys.length === 0) {
        throw new Error(`테이블 ${tableName}의 기본키를 찾을 수 없습니다.`);
      }

      const primaryKeyColumn = primaryKeys[0];
      console.log(`🔑 테이블 ${tableName}의 기본키: ${primaryKeyColumn}`);

      // 🆕 컬럼 타입 조회 (타입 캐스팅용)
      const columnTypesQuery = `
        SELECT column_name, data_type 
        FROM information_schema.columns 
        WHERE table_name = $1 AND table_schema = 'public'
      `;
      const columnTypesResult = await query<{
        column_name: string;
        data_type: string;
      }>(columnTypesQuery, [tableName]);
      const columnTypes: Record<string, string> = {};
      columnTypesResult.forEach((row) => {
        columnTypes[row.column_name] = row.data_type;
      });

      console.log("📊 컬럼 타입 정보:", columnTypes);

      // 🆕 동적 UPDATE SQL 생성 (타입 캐스팅 포함)
      const setClause = Object.keys(changedFields)
        .map((key, index) => {
          const dataType = columnTypes[key];
          // 숫자 타입인 경우 명시적 캐스팅
          if (
            dataType === "integer" ||
            dataType === "bigint" ||
            dataType === "smallint"
          ) {
            return `${key} = $${index + 1}::integer`;
          } else if (
            dataType === "numeric" ||
            dataType === "decimal" ||
            dataType === "real" ||
            dataType === "double precision"
          ) {
            return `${key} = $${index + 1}::numeric`;
          } else if (dataType === "boolean") {
            return `${key} = $${index + 1}::boolean`;
          } else if (dataType === "jsonb" || dataType === "json") {
            // 🆕 JSONB/JSON 타입은 명시적 캐스팅
            return `${key} = $${index + 1}::jsonb`;
          } else {
            // 문자열 타입은 캐스팅 불필요
            return `${key} = $${index + 1}`;
          }
        })
        .join(", ");

      // 🆕 JSONB 타입 값은 JSON 문자열로 변환, 빈 문자열은 null로 변환
      const values: any[] = Object.keys(changedFields).map((key) => {
        const value = changedFields[key];
        const dataType = columnTypes[key];

        // 🔧 빈 문자열은 null로 변환 (날짜 필드 등에서 값을 지울 때 필요)
        if (value === "" || value === undefined) {
          console.log(`🔄 빈 값 → null 변환: ${key}`);
          return null;
        }

        // JSONB/JSON 타입이고 배열/객체인 경우 JSON 문자열로 변환
        if (
          (dataType === "jsonb" || dataType === "json") &&
          (Array.isArray(value) ||
            (typeof value === "object" && value !== null))
        ) {
          return JSON.stringify(value);
        }
        return value;
      });
      values.push(id); // WHERE 조건용 ID 추가

      // 🔑 Primary Key 타입에 맞게 캐스팅
      const pkDataType = columnTypes[primaryKeyColumn];
      let pkCast = "";
      if (
        pkDataType === "integer" ||
        pkDataType === "bigint" ||
        pkDataType === "smallint"
      ) {
        pkCast = "::integer";
      } else if (pkDataType === "numeric" || pkDataType === "decimal") {
        pkCast = "::numeric";
      } else if (pkDataType === "uuid") {
        pkCast = "::uuid";
      }
      // text, varchar 등은 캐스팅 불필요

      const updateQuery = `
        UPDATE ${tableName} 
        SET ${setClause}
        WHERE ${primaryKeyColumn} = $${values.length}${pkCast}
        RETURNING *
      `;

      console.log("📝 실행할 부분 UPDATE SQL:", updateQuery);
      console.log("📊 SQL 파라미터:", values);

      const result = await query<any>(updateQuery, values);

      console.log("✅ 서비스: 부분 업데이트 성공:", result);

      const updatedRecord = Array.isArray(result) ? result[0] : result;

      return {
        success: true,
        data: updatedRecord,
        message: "데이터가 성공적으로 업데이트되었습니다.",
      };
    } catch (error: any) {
      console.error("❌ 서비스: 부분 업데이트 실패:", error);
      throw new Error(`부분 업데이트 실패: ${error}`);
    }
  }

  /**
   * 폼 데이터 업데이트 (실제 테이블에서 직접 업데이트)
   */
  async updateFormData(
    id: string | number,
    tableNameInput: string,
    data: Record<string, any>
  ): Promise<FormDataResult> {
    // VIEW인 경우 원본 테이블로 전환
    const tableName = await this.resolveBaseTable(tableNameInput);
    try {
      console.log("🔄 서비스: 실제 테이블에서 폼 데이터 업데이트 시작:", {
        id,
        tableName,
        originalTable: tableNameInput !== tableName ? tableNameInput : undefined,
        data,
      });

      // 테이블의 실제 컬럼 정보 조회
      const tableColumns = await this.getTableColumnNames(tableName);
      console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);

      // 메타데이터 제거
      const { created_by, updated_by, company_code, screen_id, ...actualData } =
        data;

      // 기본 데이터 준비
      const dataToUpdate: any = { ...actualData };

      // 테이블에 존재하는 업데이트 관련 필드들만 추가
      if (tableColumns.includes("updated_at")) {
        dataToUpdate.updated_at = new Date();
      }
      if (tableColumns.includes("updated_date")) {
        dataToUpdate.updated_date = new Date();
      }
      if (tableColumns.includes("regdate") && !dataToUpdate.regdate) {
        dataToUpdate.regdate = new Date();
      }

      // 수정자 정보가 있고 해당 컬럼이 존재한다면 추가
      if (updated_by && tableColumns.includes("updated_by")) {
        dataToUpdate.updated_by = updated_by;
      }

      // 존재하지 않는 컬럼 제거
      Object.keys(dataToUpdate).forEach((key) => {
        if (!tableColumns.includes(key)) {
          console.log(
            `⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제거됨`
          );
          delete dataToUpdate[key];
        }
      });

      // 컬럼 타입에 맞는 데이터 변환 (UPDATE용)
      const columnInfo = await this.getTableColumnInfo(tableName);
      console.log(`📊 테이블 ${tableName}의 컬럼 타입 정보:`, columnInfo);

      // 각 컬럼의 타입에 맞게 데이터 변환
      Object.keys(dataToUpdate).forEach((columnName) => {
        const column = columnInfo.find((col) => col.column_name === columnName);
        if (column) {
          const originalValue = dataToUpdate[columnName];
          const convertedValue = this.convertValueForPostgreSQL(
            originalValue,
            column.data_type
          );

          if (originalValue !== convertedValue) {
            console.log(
              `🔄 UPDATE 타입 변환: ${columnName} (${column.data_type}) = "${originalValue}" -> ${convertedValue}`
            );
            dataToUpdate[columnName] = convertedValue;
          }
        }
      });

      console.log("✅ UPDATE 타입 변환 완료된 데이터:", dataToUpdate);

      console.log("🎯 실제 테이블에서 업데이트할 데이터:", {
        tableName,
        id,
        dataToUpdate,
      });

      // 동적 UPDATE SQL 생성
      const setClause = Object.keys(dataToUpdate)
        .map((key, index) => `${key} = $${index + 1}`)
        .join(", ");

      const values: any[] = Object.values(dataToUpdate);
      values.push(id); // WHERE 조건용 ID 추가

      // 동적으로 기본키 조회
      const primaryKeys = await this.getTablePrimaryKeys(tableName);
      if (!primaryKeys || primaryKeys.length === 0) {
        throw new Error(`테이블 ${tableName}의 기본키를 찾을 수 없습니다.`);
      }

      const primaryKeyColumn = primaryKeys[0]; // 첫 번째 기본키 사용
      console.log(`🔑 테이블 ${tableName}의 기본키: ${primaryKeyColumn}`);

      // 기본키 데이터 타입 조회하여 적절한 캐스팅 적용
      const primaryKeyInfo = await query<{ data_type: string }>(
        `SELECT data_type
         FROM information_schema.columns
         WHERE table_name = $1
         AND column_name = $2
         AND table_schema = 'public'`,
        [tableName, primaryKeyColumn]
      );

      let typeCastSuffix = "";
      if (primaryKeyInfo.length > 0) {
        const dataType = primaryKeyInfo[0].data_type;
        console.log(`🔍 기본키 ${primaryKeyColumn}의 데이터 타입: ${dataType}`);

        if (dataType.includes("character") || dataType.includes("text")) {
          typeCastSuffix = "::text";
        } else if (dataType.includes("bigint")) {
          typeCastSuffix = "::bigint";
        } else if (
          dataType.includes("integer") ||
          dataType.includes("numeric")
        ) {
          typeCastSuffix = "::numeric";
        }
      }

      const updateQuery = `
        UPDATE ${tableName} 
        SET ${setClause}
        WHERE ${primaryKeyColumn} = $${values.length}${typeCastSuffix}
        RETURNING *
      `;

      console.log("📝 실행할 UPDATE SQL:", updateQuery);
      console.log("📊 SQL 파라미터:", values);

      const result = await query<any>(updateQuery, values);

      console.log("✅ 서비스: 실제 테이블 업데이트 성공:", result);

      const updatedRecord = Array.isArray(result) ? result[0] : result;

      // 🔥 조건부 연결 실행 (UPDATE 트리거)
      try {
        if (company_code) {
          await EventTriggerService.executeEventTriggers(
            "update",
            tableName,
            updatedRecord as Record<string, any>,
            company_code
          );
          console.log("🚀 조건부 연결 트리거 실행 완료 (UPDATE)");
        }
      } catch (triggerError) {
        console.error("⚠️ 조건부 연결 트리거 실행 오류:", triggerError);
        // 트리거 오류는 로그만 남기고 메인 업데이트 프로세스는 계속 진행
      }

      // 🎯 제어관리 실행 (UPDATE 트리거)
      try {
        // updatedRecord에서 company_code 추출
        const recordCompanyCode =
          (updatedRecord as Record<string, any>)?.company_code ||
          company_code ||
          "*";

        await this.executeDataflowControlIfConfigured(
          0, // UPDATE는 screenId를 알 수 없으므로 0으로 설정 (추후 개선 필요)
          tableName,
          updatedRecord as Record<string, any>,
          "update",
          updated_by || "system",
          recordCompanyCode
        );
      } catch (controlError) {
        console.error("⚠️ 제어관리 실행 오류:", controlError);
        // 제어관리 오류는 로그만 남기고 메인 업데이트 프로세스는 계속 진행
      }

      return {
        id: updatedRecord.id || updatedRecord.objid || id,
        screenId: 0, // 실제 테이블에는 screenId가 없으므로 0으로 설정
        tableName: tableName,
        data: updatedRecord as Record<string, any>,
        createdAt: updatedRecord.created_at || new Date(),
        updatedAt: updatedRecord.updated_at || new Date(),
        createdBy: updatedRecord.created_by || "system",
        updatedBy: updatedRecord.updated_by || updated_by || "system",
      };
    } catch (error) {
      console.error("❌ 서비스: 실제 테이블 업데이트 실패:", error);
      throw new Error(`실제 테이블 업데이트 실패: ${error}`);
    }
  }

  /**
   * 폼 데이터 삭제 (실제 테이블에서 직접 삭제)
   * @param id 삭제할 레코드 ID
   * @param tableName 테이블명
   * @param companyCode 회사 코드
   * @param userId 사용자 ID
   * @param screenId 화면 ID (제어관리 실행용, 선택사항)
   */
  async deleteFormData(
    id: string | number,
    tableName: string,
    companyCode?: string,
    userId?: string,
    screenId?: number
  ): Promise<void> {
    try {
      // VIEW인 경우 원본 테이블로 전환 (VIEW에는 기본키가 없으므로)
      const actualTable = await this.resolveBaseTable(tableName);

      console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", {
        id,
        tableName: actualTable,
        originalTable: tableName !== actualTable ? tableName : undefined,
      });

      // 1. 먼저 테이블의 기본키 컬럼명과 데이터 타입을 동적으로 조회
      const primaryKeyQuery = `
        SELECT kcu.column_name, c.data_type
        FROM information_schema.table_constraints tc
        JOIN information_schema.key_column_usage kcu 
          ON tc.constraint_name = kcu.constraint_name
        JOIN information_schema.columns c
          ON kcu.column_name = c.column_name 
          AND kcu.table_name = c.table_name
        WHERE tc.table_name = $1 
          AND tc.constraint_type = 'PRIMARY KEY'
        LIMIT 1
      `;

      console.log("🔍 기본키 조회 SQL:", primaryKeyQuery);
      console.log("🔍 테이블명:", actualTable);

      const primaryKeyResult = await query<{
        column_name: string;
        data_type: string;
      }>(primaryKeyQuery, [actualTable]);

      if (!primaryKeyResult || primaryKeyResult.length === 0) {
        throw new Error(`테이블 ${actualTable}의 기본키를 찾을 수 없습니다.`);
      }

      const primaryKeyInfo = primaryKeyResult[0];
      const primaryKeyColumn = primaryKeyInfo.column_name;
      const primaryKeyDataType = primaryKeyInfo.data_type;
      console.log("🔑 발견된 기본키:", {
        column: primaryKeyColumn,
        dataType: primaryKeyDataType,
      });

      // 2. 데이터 타입에 맞는 타입 캐스팅 적용
      let typeCastSuffix = "";
      if (
        primaryKeyDataType.includes("character") ||
        primaryKeyDataType.includes("text")
      ) {
        typeCastSuffix = "::text";
      } else if (
        primaryKeyDataType.includes("integer") ||
        primaryKeyDataType.includes("bigint")
      ) {
        typeCastSuffix = "::bigint";
      } else if (
        primaryKeyDataType.includes("numeric") ||
        primaryKeyDataType.includes("decimal")
      ) {
        typeCastSuffix = "::numeric";
      }

      // 3. 동적으로 발견된 기본키와 타입 캐스팅을 사용한 DELETE SQL 생성
      const deleteQuery = `
        DELETE FROM ${actualTable} 
        WHERE ${primaryKeyColumn} = $1${typeCastSuffix}
        RETURNING *
      `;

      console.log("📝 실행할 DELETE SQL:", deleteQuery);
      console.log("📊 SQL 파라미터:", [id]);

      // 🔥 트랜잭션 내에서 app.user_id 설정 후 DELETE 실행 (이력 트리거용)
      const result = await transaction(async (client) => {
        // 이력 트리거에서 사용할 사용자 정보 설정
        if (userId) {
          await client.query(`SET LOCAL app.user_id = '${userId}'`);
        }
        const res = await client.query(deleteQuery, [id]);
        return res.rows;
      });

      // 삭제된 행이 없으면 레코드를 찾을 수 없는 것
      if (!result || !Array.isArray(result) || result.length === 0) {
        throw new Error(`테이블 ${actualTable}에서 ID '${id}'에 해당하는 레코드를 찾을 수 없습니다.`);
      }

      console.log("✅ 서비스: 실제 테이블 삭제 성공:", result);

      // 🔥 조건부 연결 실행 (DELETE 트리거)
      try {
        if (
          companyCode &&
          result &&
          Array.isArray(result) &&
          result.length > 0
        ) {
          const deletedRecord = result[0] as Record<string, any>;
          await EventTriggerService.executeEventTriggers(
            "delete",
            tableName,
            deletedRecord,
            companyCode
          );
          console.log("🚀 조건부 연결 트리거 실행 완료 (DELETE)");
        }
      } catch (triggerError) {
        console.error("⚠️ 조건부 연결 트리거 실행 오류:", triggerError);
        // 트리거 오류는 로그만 남기고 메인 삭제 프로세스는 계속 진행
      }

      // 🎯 제어관리 실행 (DELETE 트리거)
      try {
        if (result && Array.isArray(result) && result.length > 0) {
          const deletedRecord = result[0] as Record<string, any>;
          // deletedRecord에서 company_code 추출
          const recordCompanyCode =
            deletedRecord?.company_code || companyCode || "*";

          // screenId가 전달되지 않으면 제어관리를 실행하지 않음
          if (screenId && screenId > 0) {
            await this.executeDataflowControlIfConfigured(
              screenId,
              tableName,
              deletedRecord,
              "delete",
              userId || "system",
              recordCompanyCode
            );
          } else {
            console.log("ℹ️ screenId가 전달되지 않아 제어관리를 건너뜁니다. (screenId:", screenId, ")");
          }
        }
      } catch (controlError) {
        console.error("⚠️ 제어관리 실행 오류:", controlError);
        // 제어관리 오류는 로그만 남기고 메인 삭제 프로세스는 계속 진행
      }
    } catch (error) {
      console.error("❌ 서비스: 실제 테이블 삭제 실패:", error);
      throw new Error(`실제 테이블 삭제 실패: ${error}`);
    }
  }

  /**
   * 단일 폼 데이터 조회
   */
  async getFormData(id: number): Promise<FormDataResult | null> {
    try {
      console.log("📄 서비스: 폼 데이터 단건 조회 시작:", { id });

      const result = await queryOne<{
        id: number;
        screen_id: number;
        table_name: string;
        form_data: any;
        created_at: Date | null;
        updated_at: Date | null;
        created_by: string;
        updated_by: string;
      }>(
        `SELECT id, screen_id, table_name, form_data, created_at, updated_at, created_by, updated_by
         FROM dynamic_form_data
         WHERE id = $1`,
        [id]
      );

      if (!result) {
        console.log("❌ 서비스: 폼 데이터를 찾을 수 없음");
        return null;
      }

      console.log("✅ 서비스: 폼 데이터 단건 조회 성공");

      return {
        id: result.id,
        screenId: result.screen_id,
        tableName: result.table_name,
        data: result.form_data as Record<string, any>,
        createdAt: result.created_at,
        updatedAt: result.updated_at,
        createdBy: result.created_by,
        updatedBy: result.updated_by,
      };
    } catch (error) {
      console.error("❌ 서비스: 폼 데이터 단건 조회 실패:", error);
      throw new Error(`폼 데이터 조회 실패: ${error}`);
    }
  }

  /**
   * 화면별 폼 데이터 목록 조회 (페이징)
   */
  async getFormDataList(
    screenId: number,
    params: {
      page: number;
      size: number;
      search?: string;
      sortBy?: string;
      sortOrder?: "asc" | "desc";
    }
  ): Promise<PaginatedFormData> {
    try {
      console.log("📋 서비스: 폼 데이터 목록 조회 시작:", { screenId, params });

      const {
        page,
        size,
        search,
        sortBy = "created_at",
        sortOrder = "desc",
      } = params;
      const offset = (page - 1) * size;

      // 정렬 컬럼 검증 (SQL Injection 방지)
      const allowedSortColumns = ["created_at", "updated_at", "id"];
      const validSortBy = allowedSortColumns.includes(sortBy)
        ? sortBy
        : "created_at";
      const validSortOrder = sortOrder === "asc" ? "ASC" : "DESC";

      // 검색 조건 및 파라미터 구성
      const queryParams: any[] = [screenId];
      let searchCondition = "";

      if (search) {
        searchCondition = ` AND (
          form_data::text ILIKE $2
          OR table_name ILIKE $2
        )`;
        queryParams.push(`%${search}%`);
      }

      // 데이터 조회 쿼리
      const dataQuery = `
        SELECT id, screen_id, table_name, form_data, created_at, updated_at, created_by, updated_by
        FROM dynamic_form_data
        WHERE screen_id = $1
        ${searchCondition}
        ORDER BY ${validSortBy} ${validSortOrder}
        LIMIT ${size} OFFSET ${offset}
      `;

      // 전체 개수 조회 쿼리
      const countQuery = `
        SELECT COUNT(*) as total
        FROM dynamic_form_data
        WHERE screen_id = $1
        ${searchCondition}
      `;

      // 병렬 실행
      const [results, countResult] = await Promise.all([
        query<{
          id: number;
          screen_id: number;
          table_name: string;
          form_data: any;
          created_at: Date | null;
          updated_at: Date | null;
          created_by: string;
          updated_by: string;
        }>(dataQuery, queryParams),
        query<{ total: string }>(countQuery, queryParams),
      ]);

      const totalCount = parseInt(countResult[0]?.total || "0");

      const formDataResults: FormDataResult[] = results.map((result) => ({
        id: result.id,
        screenId: result.screen_id,
        tableName: result.table_name,
        data: result.form_data as Record<string, any>,
        createdAt: result.created_at,
        updatedAt: result.updated_at,
        createdBy: result.created_by,
        updatedBy: result.updated_by,
      }));

      const totalPages = Math.ceil(totalCount / size);

      console.log("✅ 서비스: 폼 데이터 목록 조회 성공:", {
        totalCount,
        totalPages,
        currentPage: page,
      });

      return {
        content: formDataResults,
        totalElements: totalCount,
        totalPages,
        currentPage: page,
        size,
      };
    } catch (error) {
      console.error("❌ 서비스: 폼 데이터 목록 조회 실패:", error);
      throw new Error(`폼 데이터 목록 조회 실패: ${error}`);
    }
  }

  /**
   * 폼 데이터 검증
   */
  async validateFormData(
    tableName: string,
    data: Record<string, any>
  ): Promise<ValidationResult> {
    try {
      console.log("✅ 서비스: 폼 데이터 검증 시작:", { tableName, data });

      const errors: ValidationError[] = [];

      // 기본 검증 로직 (실제로는 테이블 스키마를 확인해야 함)
      Object.entries(data).forEach(([key, value]) => {
        // 예시: 빈 값 검증
        if (value === null || value === undefined || value === "") {
          // 특정 필드가 required인지 확인하는 로직이 필요
          // 지금은 간단히 모든 필드를 선택사항으로 처리
        }

        // 예시: 데이터 타입 검증
        // 실제로는 테이블 스키마의 컬럼 타입과 비교해야 함
      });

      const result: ValidationResult = {
        valid: errors.length === 0,
        errors,
      };

      console.log("✅ 서비스: 폼 데이터 검증 완료:", result);

      return result;
    } catch (error) {
      console.error("❌ 서비스: 폼 데이터 검증 실패:", error);
      throw new Error(`폼 데이터 검증 실패: ${error}`);
    }
  }

  /**
   * 테이블 컬럼 정보 조회 (PostgreSQL 시스템 테이블 활용)
   */
  async getTableColumns(tableName: string): Promise<TableColumn[]> {
    try {
      console.log("📊 서비스: 테이블 컬럼 정보 조회 시작:", { tableName });

      // PostgreSQL의 information_schema를 사용하여 컬럼 정보 조회
      const columns = await query<{
        column_name: string;
        data_type: string;
        is_nullable: string;
        column_default: string | null;
        character_maximum_length: number | null;
      }>(
        `SELECT
          column_name,
          data_type,
          is_nullable,
          column_default,
          character_maximum_length
        FROM information_schema.columns
        WHERE table_name = $1
        AND table_schema = 'public'
        ORDER BY ordinal_position`,
        [tableName]
      );

      // Primary key 정보 조회
      const primaryKeys = await query<{ column_name: string }>(
        `SELECT
          kcu.column_name
        FROM
          information_schema.table_constraints tc
          JOIN information_schema.key_column_usage kcu
            ON tc.constraint_name = kcu.constraint_name
        WHERE
          tc.constraint_type = 'PRIMARY KEY'
          AND tc.table_name = $1
          AND tc.table_schema = 'public'`,
        [tableName]
      );

      const primaryKeyColumns = new Set(
        primaryKeys.map((pk) => pk.column_name)
      );

      const result: TableColumn[] = columns.map((col) => ({
        columnName: col.column_name,
        dataType: col.data_type,
        nullable: col.is_nullable === "YES",
        primaryKey: primaryKeyColumns.has(col.column_name),
        maxLength: col.character_maximum_length,
        defaultValue: col.column_default,
      }));

      console.log("✅ 서비스: 테이블 컬럼 정보 조회 성공:", result);

      return result;
    } catch (error) {
      console.error("❌ 서비스: 테이블 컬럼 정보 조회 실패:", error);
      throw new Error(`테이블 컬럼 정보 조회 실패: ${error}`);
    }
  }

  /**
   * 테이블의 기본키 컬럼명 목록 조회
   */
  async getPrimaryKeys(tableName: string): Promise<string[]> {
    try {
      console.log("🔑 서비스: 테이블 기본키 조회 시작:", { tableName });

      const result = await query<{ column_name: string }>(
        `SELECT a.attname AS column_name
         FROM pg_index i
         JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
         WHERE i.indrelid = $1::regclass AND i.indisprimary`,
        [tableName]
      );

      const primaryKeys = result.map((row) => row.column_name);
      console.log("✅ 서비스: 테이블 기본키 조회 성공:", primaryKeys);

      return primaryKeys;
    } catch (error) {
      console.error("❌ 서비스: 테이블 기본키 조회 실패:", error);
      throw new Error(`테이블 기본키 조회 실패: ${error}`);
    }
  }

  /**
   * 제어관리 실행 (화면에 설정된 경우)
   * 다중 제어를 순서대로 순차 실행 지원
   */
  private async executeDataflowControlIfConfigured(
    screenId: number,
    tableName: string,
    savedData: Record<string, any>,
    triggerType: "insert" | "update" | "delete",
    userId: string = "system",
    companyCode: string = "*"
  ): Promise<void> {
    try {
      console.log(`🎯 제어관리 설정 확인 중... (screenId: ${screenId})`);

      // V2 레이아웃에서 layout_data jsonb 조회
      const v2Layouts = await query<{
        layout_id: number;
        layout_data: any;
      }>(
        `SELECT layout_id, layout_data
         FROM screen_layouts_v2
         WHERE screen_id = $1 AND company_code = $2`,
        [screenId, companyCode]
      );

      if (v2Layouts.length === 0) {
        console.log(`ℹ️ V2 레이아웃이 없습니다. (화면 ID: ${screenId}, company: ${companyCode})`);
        return;
      }

      // layout_data.components 배열에서 버튼 컴포넌트 추출
      const layoutData = v2Layouts[0].layout_data;
      const components: any[] = layoutData?.components || [];

      console.log(`📋 V2 컴포넌트 조회 결과: ${components.length}개`);

      let controlConfigFound = false;
      for (const comp of components) {
        const overrides = comp?.overrides || {};

        const isButtonComponent =
          overrides?.type === "v2-button-primary" ||
          (comp?.url || "").includes("v2-button-primary");

        // triggerType에 맞는 액션 타입 매칭: insert/update -> save, delete -> delete
        const buttonActionType = overrides?.action?.type;
        const isMatchingAction =
          (triggerType === "delete" && buttonActionType === "delete") ||
          ((triggerType === "insert" || triggerType === "update") && buttonActionType === "save");

        console.log(`🔍 V2 컴포넌트 검사:`, {
          componentId: comp?.id,
          type: overrides?.type,
          actionType: buttonActionType,
          enableDataflowControl: overrides?.enableDataflowControl,
          hasDataflowConfig: !!overrides?.dataflowConfig,
        });

        if (
          isButtonComponent &&
          isMatchingAction &&
          overrides?.enableDataflowControl === true
        ) {
          const dataflowConfig = overrides?.dataflowConfig;
          const flowControls = dataflowConfig?.flowControls || [];

          if (flowControls.length > 0) {
            controlConfigFound = true;
            console.log(`🎯 다중 제어관리 설정 발견: ${flowControls.length}개`);

            const sortedControls = [...flowControls].sort(
              (a: any, b: any) => (a.order || 0) - (b.order || 0)
            );

            await this.executeMultipleFlowControls(
              sortedControls,
              savedData,
              screenId,
              tableName,
              triggerType,
              userId,
              companyCode
            );
          } else if (dataflowConfig?.selectedDiagramId) {
            controlConfigFound = true;
            const diagramId = dataflowConfig.selectedDiagramId;
            const relationshipId = dataflowConfig.selectedRelationshipId;

            console.log(`🎯 단일 제어관리 설정 발견:`, {
              componentId: comp?.id,
              diagramId,
              relationshipId,
              triggerType,
            });

            await this.executeSingleFlowControl(
              diagramId,
              relationshipId,
              savedData,
              screenId,
              tableName,
              triggerType,
              userId,
              companyCode
            );
          }

          break;
        }
      }

      if (!controlConfigFound) {
        console.log(`ℹ️ 제어관리 설정이 없습니다. (화면 ID: ${screenId})`);
      }
    } catch (error) {
      console.error("❌ 제어관리 설정 확인 및 실행 오류:", error);
      // 에러를 다시 던지지 않음 - 메인 저장 프로세스에 영향 주지 않기 위해
    }
  }

  /**
   * 다중 제어 순차 실행
   */
  private async executeMultipleFlowControls(
    flowControls: Array<{
      id: string;
      flowId: number;
      flowName: string;
      executionTiming: string;
      order: number;
    }>,
    savedData: Record<string, any>,
    screenId: number,
    tableName: string,
    triggerType: "insert" | "update" | "delete",
    userId: string,
    companyCode: string
  ): Promise<void> {
    console.log(`🚀 다중 제어 순차 실행 시작: ${flowControls.length}개`);

    const { NodeFlowExecutionService } = await import(
      "./nodeFlowExecutionService"
    );

    const results: Array<{
      order: number;
      flowId: number;
      flowName: string;
      success: boolean;
      message: string;
      duration: number;
    }> = [];

    for (let i = 0; i < flowControls.length; i++) {
      const control = flowControls[i];
      const startTime = Date.now();

      console.log(
        `\n📍 [${i + 1}/${flowControls.length}] 제어 실행: ${control.flowName} (flowId: ${control.flowId})`
      );

      try {
        // 유효하지 않은 flowId 스킵
        if (!control.flowId || control.flowId <= 0) {
          console.warn(`⚠️ 유효하지 않은 flowId, 스킵: ${control.flowId}`);
          results.push({
            order: control.order,
            flowId: control.flowId,
            flowName: control.flowName,
            success: false,
            message: "유효하지 않은 flowId",
            duration: 0,
          });
          continue;
        }

        const executionResult = await NodeFlowExecutionService.executeFlow(
          control.flowId,
          {
            sourceData: [savedData],
            dataSourceType: "formData",
            buttonId: `${triggerType}-button`,
            screenId: screenId,
            userId: userId,
            companyCode: companyCode,
            formData: savedData,
          }
        );

        const duration = Date.now() - startTime;

        results.push({
          order: control.order,
          flowId: control.flowId,
          flowName: control.flowName,
          success: executionResult.success,
          message: executionResult.message,
          duration,
        });

        if (executionResult.success) {
          console.log(
            `✅ [${i + 1}/${flowControls.length}] 제어 성공: ${control.flowName} (${duration}ms)`
          );
        } else {
          console.error(
            `❌ [${i + 1}/${flowControls.length}] 제어 실패: ${control.flowName} - ${executionResult.message}`
          );
          // 이전 제어 실패 시 다음 제어 실행 중단
          console.warn(`⚠️ 이전 제어 실패로 인해 나머지 제어 실행 중단`);
          break;
        }
      } catch (error: any) {
        const duration = Date.now() - startTime;
        console.error(
          `❌ [${i + 1}/${flowControls.length}] 제어 실행 오류: ${control.flowName}`,
          error
        );

        results.push({
          order: control.order,
          flowId: control.flowId,
          flowName: control.flowName,
          success: false,
          message: error.message || "실행 오류",
          duration,
        });

        // 오류 발생 시 다음 제어 실행 중단
        console.warn(`⚠️ 제어 실행 오류로 인해 나머지 제어 실행 중단`);
        break;
      }
    }

    // 실행 결과 요약
    const successCount = results.filter((r) => r.success).length;
    const failCount = results.filter((r) => !r.success).length;
    const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);

    console.log(`\n📊 다중 제어 실행 완료:`, {
      total: flowControls.length,
      executed: results.length,
      success: successCount,
      failed: failCount,
      totalDuration: `${totalDuration}ms`,
    });
  }

  /**
   * 단일 제어 실행 (기존 로직, 하위 호환성)
   */
  private async executeSingleFlowControl(
    diagramId: number,
    relationshipId: string | null,
    savedData: Record<string, any>,
    screenId: number,
    tableName: string,
    triggerType: "insert" | "update" | "delete",
    userId: string,
    companyCode: string
  ): Promise<void> {
    let controlResult: any;

    if (!relationshipId) {
      // 노드 플로우 실행
      console.log(`🚀 노드 플로우 실행 (flowId: ${diagramId})`);
      const { NodeFlowExecutionService } = await import(
        "./nodeFlowExecutionService"
      );

      const executionResult = await NodeFlowExecutionService.executeFlow(
        diagramId,
        {
          sourceData: [savedData],
          dataSourceType: "formData",
          buttonId: "save-button",
          screenId: screenId,
          userId: userId,
          companyCode: companyCode,
          formData: savedData,
        }
      );

      controlResult = {
        success: executionResult.success,
        message: executionResult.message,
        executedActions: executionResult.nodes?.map((node) => ({
          nodeId: node.nodeId,
          status: node.status,
          duration: node.duration,
        })),
        errors: executionResult.nodes
          ?.filter((node) => node.status === "failed")
          .map((node) => node.error || "실행 실패"),
      };
    } else {
      // 관계 기반 제어관리 실행
      console.log(
        `🎯 관계 기반 제어관리 실행 (relationshipId: ${relationshipId})`
      );
      controlResult = await this.dataflowControlService.executeDataflowControl(
        diagramId,
        relationshipId,
        triggerType,
        savedData,
        tableName,
        userId
      );
    }

    console.log(`🎯 제어관리 실행 결과:`, controlResult);

    if (controlResult.success) {
      console.log(`✅ 제어관리 실행 성공: ${controlResult.message}`);
      if (
        controlResult.executedActions &&
        controlResult.executedActions.length > 0
      ) {
        console.log(`📊 실행된 액션들:`, controlResult.executedActions);
      }

      if (controlResult.errors && controlResult.errors.length > 0) {
        console.warn(
          `⚠️ 제어관리 실행 중 일부 오류 발생:`,
          controlResult.errors
        );
      }
    } else {
      console.warn(`⚠️ 제어관리 실행 실패: ${controlResult.message}`);
    }
  }

  /**
   * 특정 테이블의 특정 필드 값만 업데이트
   * (다른 테이블의 레코드 업데이트 지원)
   */
  async updateFieldValue(
    tableName: string,
    keyField: string,
    keyValue: any,
    updateField: string,
    updateValue: any,
    companyCode: string,
    userId: string
  ): Promise<{ affectedRows: number }> {
    const pool = getPool();
    const client = await pool.connect();

    try {
      console.log("🔄 [updateFieldValue] 업데이트 실행:", {
        tableName,
        keyField,
        keyValue,
        updateField,
        updateValue,
        companyCode,
      });

      // 테이블 컬럼 정보 조회 (updated_by, updated_at 존재 여부 확인)
      const columnQuery = `
        SELECT column_name 
        FROM information_schema.columns 
        WHERE table_name = $1 AND column_name IN ('updated_by', 'updated_at', 'company_code')
      `;
      const columnResult = await client.query(columnQuery, [tableName]);
      const existingColumns = columnResult.rows.map(
        (row: any) => row.column_name
      );

      const hasUpdatedBy = existingColumns.includes("updated_by");
      const hasUpdatedAt = existingColumns.includes("updated_at");
      const hasCompanyCode = existingColumns.includes("company_code");

      console.log("🔍 [updateFieldValue] 테이블 컬럼 확인:", {
        hasUpdatedBy,
        hasUpdatedAt,
        hasCompanyCode,
      });

      // 동적 SET 절 구성
      let setClause = `"${updateField}" = $1`;
      const params: any[] = [updateValue];
      let paramIndex = 2;

      if (hasUpdatedBy) {
        setClause += `, updated_by = $${paramIndex}`;
        params.push(userId);
        paramIndex++;
      }

      if (hasUpdatedAt) {
        setClause += `, updated_at = NOW()`;
      }

      // WHERE 절 구성
      let whereClause = `"${keyField}" = $${paramIndex}`;
      params.push(keyValue);
      paramIndex++;

      // 멀티테넌시: company_code 조건 추가 (최고관리자는 제외, 컬럼이 있는 경우만)
      if (hasCompanyCode && companyCode && companyCode !== "*") {
        whereClause += ` AND company_code = $${paramIndex}`;
        params.push(companyCode);
        paramIndex++;
      }

      const sqlQuery = `
        UPDATE "${tableName}"
        SET ${setClause}
        WHERE ${whereClause}
      `;

      console.log("🔍 [updateFieldValue] 쿼리:", sqlQuery);
      console.log("🔍 [updateFieldValue] 파라미터:", params);

      const result = await client.query(sqlQuery, params);

      console.log("✅ [updateFieldValue] 결과:", {
        affectedRows: result.rowCount,
      });

      return { affectedRows: result.rowCount || 0 };
    } catch (error) {
      console.error("❌ [updateFieldValue] 오류:", error);
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * 위치 이력 저장 (연속 위치 추적용)
   */
  async saveLocationHistory(data: {
    userId: string;
    companyCode: string;
    latitude: number;
    longitude: number;
    accuracy?: number;
    altitude?: number;
    speed?: number;
    heading?: number;
    tripId?: string;
    tripStatus?: string;
    departure?: string;
    arrival?: string;
    departureName?: string;
    destinationName?: string;
    recordedAt?: string;
    vehicleId?: number;
  }): Promise<{ id: number }> {
    const pool = getPool();
    const client = await pool.connect();

    try {
      console.log("📍 [saveLocationHistory] 저장 시작:", data);

      const sqlQuery = `
        INSERT INTO vehicle_location_history (
          user_id,
          company_code,
          latitude,
          longitude,
          accuracy,
          altitude,
          speed,
          heading,
          trip_id,
          trip_status,
          departure,
          arrival,
          departure_name,
          destination_name,
          recorded_at,
          vehicle_id
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
        RETURNING id
      `;

      const params = [
        data.userId,
        data.companyCode,
        data.latitude,
        data.longitude,
        data.accuracy || null,
        data.altitude || null,
        data.speed || null,
        data.heading || null,
        data.tripId || null,
        data.tripStatus || "active",
        data.departure || null,
        data.arrival || null,
        data.departureName || null,
        data.destinationName || null,
        data.recordedAt ? new Date(data.recordedAt) : new Date(),
        data.vehicleId || null,
      ];

      const result = await client.query(sqlQuery, params);

      console.log("✅ [saveLocationHistory] 저장 완료:", {
        id: result.rows[0]?.id,
      });

      return { id: result.rows[0]?.id };
    } catch (error) {
      console.error("❌ [saveLocationHistory] 오류:", error);
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * 위치 이력 조회 (경로 조회용)
   */
  async getLocationHistory(params: {
    companyCode: string;
    tripId?: string;
    userId?: string;
    startDate?: string;
    endDate?: string;
    limit?: number;
  }): Promise<any[]> {
    const pool = getPool();
    const client = await pool.connect();

    try {
      console.log("📍 [getLocationHistory] 조회 시작:", params);

      const conditions: string[] = [];
      const queryParams: any[] = [];
      let paramIndex = 1;

      // 멀티테넌시: company_code 필터
      if (params.companyCode && params.companyCode !== "*") {
        conditions.push(`company_code = $${paramIndex}`);
        queryParams.push(params.companyCode);
        paramIndex++;
      }

      // trip_id 필터
      if (params.tripId) {
        conditions.push(`trip_id = $${paramIndex}`);
        queryParams.push(params.tripId);
        paramIndex++;
      }

      // user_id 필터
      if (params.userId) {
        conditions.push(`user_id = $${paramIndex}`);
        queryParams.push(params.userId);
        paramIndex++;
      }

      // 날짜 범위 필터
      if (params.startDate) {
        conditions.push(`recorded_at >= $${paramIndex}`);
        queryParams.push(new Date(params.startDate));
        paramIndex++;
      }

      if (params.endDate) {
        conditions.push(`recorded_at <= $${paramIndex}`);
        queryParams.push(new Date(params.endDate));
        paramIndex++;
      }

      const whereClause =
        conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
      const limitClause = params.limit ? `LIMIT ${params.limit}` : "LIMIT 1000";

      const sqlQuery = `
        SELECT 
          id,
          user_id,
          vehicle_id,
          latitude,
          longitude,
          accuracy,
          altitude,
          speed,
          heading,
          trip_id,
          trip_status,
          departure,
          arrival,
          departure_name,
          destination_name,
          recorded_at,
          created_at,
          company_code
        FROM vehicle_location_history
        ${whereClause}
        ORDER BY recorded_at ASC
        ${limitClause}
      `;

      console.log("🔍 [getLocationHistory] 쿼리:", sqlQuery);
      console.log("🔍 [getLocationHistory] 파라미터:", queryParams);

      const result = await client.query(sqlQuery, queryParams);

      console.log("✅ [getLocationHistory] 조회 완료:", {
        count: result.rowCount,
      });

      return result.rows;
    } catch (error) {
      console.error("❌ [getLocationHistory] 오류:", error);
      throw error;
    } finally {
      client.release();
    }
  }
}

// 싱글톤 인스턴스 생성 및 export
export const dynamicFormService = new DynamicFormService();
