/**
 * 동적 데이터 서비스
 *
 * 주요 특징:
 * 1. 화이트리스트 제거 - 모든 테이블에 동적으로 접근 가능
 * 2. 블랙리스트 방식 - 시스템 중요 테이블만 접근 금지
 * 3. 자동 회사별 필터링 - company_code 컬럼 자동 감지 및 필터 적용
 * 4. SQL 인젝션 방지 - 정규식 기반 테이블명/컬럼명 검증
 *
 * 보안:
 * - 테이블명은 영문, 숫자, 언더스코어만 허용
 * - 시스템 테이블(pg_*, information_schema 등) 접근 금지
 * - company_code 컬럼이 있는 테이블은 자동으로 회사별 격리
 * - 최고 관리자(company_code = "*")만 전체 데이터 조회 가능
 */
import { query, queryOne } from "../database/db";
import { pool } from "../database/db"; // 🆕 Entity 조인을 위한 pool import
import { buildDataFilterWhereClause } from "../utils/dataFilterUtil"; // 🆕 데이터 필터 유틸
import { v4 as uuidv4 } from "uuid"; // 🆕 UUID 생성

interface GetTableDataParams {
  tableName: string;
  limit?: number;
  offset?: number;
  orderBy?: string;
  filters?: Record<string, string>;
  userCompany?: string;
}

interface ServiceResponse<T> {
  success: boolean;
  data?: T;
  message?: string;
  error?: string;
}

/**
 * 접근 금지 테이블 목록 (블랙리스트)
 * 시스템 중요 테이블 및 보안상 접근 금지할 테이블
 */
const BLOCKED_TABLES = [
  "pg_catalog",
  "pg_statistic",
  "pg_database",
  "pg_user",
  "information_schema",
  "session_tokens", // 세션 토큰 테이블
  "password_history", // 패스워드 이력
];

/**
 * 테이블 이름 검증 정규식
 * SQL 인젝션 방지: 영문, 숫자, 언더스코어만 허용
 */
const TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

class DataService {
  /**
   * 중복 데이터 제거 (메모리 내 처리)
   */
  private deduplicateData(
    data: any[],
    config: {
      groupByColumn: string;
      keepStrategy: "latest" | "earliest" | "base_price" | "current_date";
      sortColumn?: string;
    }
  ): any[] {
    if (!data || data.length === 0) return data;

    // 그룹별로 데이터 분류
    const groups: Record<string, any[]> = {};

    for (const row of data) {
      const groupKey = row[config.groupByColumn];
      if (groupKey === undefined || groupKey === null) continue;

      if (!groups[groupKey]) {
        groups[groupKey] = [];
      }
      groups[groupKey].push(row);
    }

    // 각 그룹에서 하나의 행만 선택
    const result: any[] = [];

    for (const [groupKey, rows] of Object.entries(groups)) {
      if (rows.length === 0) continue;

      let selectedRow: any;

      switch (config.keepStrategy) {
        case "latest":
          // 정렬 컬럼 기준 최신 (가장 큰 값)
          if (config.sortColumn) {
            rows.sort((a, b) => {
              const aVal = a[config.sortColumn!];
              const bVal = b[config.sortColumn!];
              if (aVal === bVal) return 0;
              if (aVal > bVal) return -1;
              return 1;
            });
          }
          selectedRow = rows[0];
          break;

        case "earliest":
          // 정렬 컬럼 기준 최초 (가장 작은 값)
          if (config.sortColumn) {
            rows.sort((a, b) => {
              const aVal = a[config.sortColumn!];
              const bVal = b[config.sortColumn!];
              if (aVal === bVal) return 0;
              if (aVal < bVal) return -1;
              return 1;
            });
          }
          selectedRow = rows[0];
          break;

        case "base_price":
          // base_price = true인 행 찾기
          selectedRow = rows.find((row) => row.base_price === true) || rows[0];
          break;

        case "current_date":
          // start_date <= CURRENT_DATE <= end_date 조건에 맞는 행
          const today = new Date();
          today.setHours(0, 0, 0, 0); // 시간 제거

          selectedRow =
            rows.find((row) => {
              const startDate = row.start_date
                ? new Date(row.start_date)
                : null;
              const endDate = row.end_date ? new Date(row.end_date) : null;

              if (startDate) startDate.setHours(0, 0, 0, 0);
              if (endDate) endDate.setHours(0, 0, 0, 0);

              const afterStart = !startDate || today >= startDate;
              const beforeEnd = !endDate || today <= endDate;

              return afterStart && beforeEnd;
            }) || rows[0]; // 조건에 맞는 행이 없으면 첫 번째 행
          break;

        default:
          selectedRow = rows[0];
      }

      result.push(selectedRow);
    }

    return result;
  }

  /**
   * 테이블 접근 검증 (공통 메서드)
   */
  private async validateTableAccess(
    tableName: string
  ): Promise<{ valid: boolean; error?: ServiceResponse<any> }> {
    // 1. 테이블명 형식 검증 (SQL 인젝션 방지)
    if (!TABLE_NAME_REGEX.test(tableName)) {
      return {
        valid: false,
        error: {
          success: false,
          message: `유효하지 않은 테이블명입니다: ${tableName}`,
          error: "INVALID_TABLE_NAME",
        },
      };
    }

    // 2. 블랙리스트 검증
    if (BLOCKED_TABLES.includes(tableName)) {
      return {
        valid: false,
        error: {
          success: false,
          message: `접근이 금지된 테이블입니다: ${tableName}`,
          error: "TABLE_ACCESS_DENIED",
        },
      };
    }

    // 3. 테이블 존재 여부 확인
    const tableExists = await this.checkTableExists(tableName);
    if (!tableExists) {
      return {
        valid: false,
        error: {
          success: false,
          message: `테이블을 찾을 수 없습니다: ${tableName}`,
          error: "TABLE_NOT_FOUND",
        },
      };
    }

    return { valid: true };
  }

  /**
   * 테이블 데이터 조회
   */
  async getTableData(
    params: GetTableDataParams
  ): Promise<ServiceResponse<any[]>> {
    const {
      tableName,
      limit = 10,
      offset = 0,
      orderBy,
      filters = {},
      userCompany,
    } = params;

    try {
      // 테이블 접근 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      // 동적 SQL 쿼리 생성
      let sql = `SELECT * FROM "${tableName}"`;
      const queryParams: any[] = [];
      let paramIndex = 1;

      // WHERE 조건 생성
      const whereConditions: string[] = [];

      // 4. 회사별 필터링 자동 적용 (company_code 컬럼이 있는 경우)
      if (userCompany && userCompany !== "*") {
        const hasCompanyCode = await this.checkColumnExists(
          tableName,
          "company_code"
        );
        if (hasCompanyCode) {
          whereConditions.push(`company_code = $${paramIndex}`);
          queryParams.push(userCompany);
          paramIndex++;
          console.log(
            `🏢 회사별 필터링 적용: ${tableName}.company_code = ${userCompany}`
          );
        }
      }

      // 사용자 정의 필터 추가
      for (const [key, value] of Object.entries(filters)) {
        if (
          value &&
          key !== "limit" &&
          key !== "offset" &&
          key !== "orderBy" &&
          key !== "userLang" &&
          key !== "page" &&
          key !== "pageSize" &&
          key !== "size"
        ) {
          // 컬럼명 검증 (SQL 인젝션 방지)
          if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
            continue; // 유효하지 않은 컬럼명은 무시
          }

          whereConditions.push(`"${key}" ILIKE $${paramIndex}`);
          queryParams.push(`%${value}%`);
          paramIndex++;
        }
      }

      // WHERE 절 추가
      if (whereConditions.length > 0) {
        sql += ` WHERE ${whereConditions.join(" AND ")}`;
      }

      // ORDER BY 절 추가
      if (orderBy) {
        // ORDER BY 검증 (SQL 인젝션 방지)
        const orderParts = orderBy.split(" ");
        const columnName = orderParts[0];
        const direction = orderParts[1]?.toUpperCase();

        if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(columnName)) {
          const validDirection = direction === "DESC" ? "DESC" : "ASC";
          sql += ` ORDER BY "${columnName}" ${validDirection}`;
        }
      } else {
        // 기본 정렬: 최신순 (가능한 컬럼 시도)
        const dateColumns = [
          "created_date",
          "regdate",
          "reg_date",
          "updated_date",
          "upd_date",
        ];
        const tableColumns = await this.getTableColumnsSimple(tableName);
        const availableDateColumn = dateColumns.find((col) =>
          tableColumns.some((tableCol) => tableCol.column_name === col)
        );

        if (availableDateColumn) {
          sql += ` ORDER BY "${availableDateColumn}" DESC`;
        }
      }

      // LIMIT과 OFFSET 추가
      sql += ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
      queryParams.push(limit, offset);

      console.log("🔍 실행할 쿼리:", sql);
      console.log("📊 쿼리 파라미터:", queryParams);

      // 쿼리 실행
      const result = await query<any>(sql, queryParams);

      return {
        success: true,
        data: result,
      };
    } catch (error) {
      console.error(`데이터 조회 오류 (${tableName}):`, error);
      return {
        success: false,
        message: "데이터 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 테이블 컬럼 정보 조회
   */
  async getTableColumns(tableName: string): Promise<ServiceResponse<any[]>> {
    try {
      // 테이블 접근 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      const columns = await this.getTableColumnsSimple(tableName);

      // PK 컬럼 정보 조회
      const pkColumns = await this.getPrimaryKeyColumns(tableName);

      // 컬럼 라벨 정보 추가
      const columnsWithLabels = await Promise.all(
        columns.map(async (column) => {
          const label = await this.getColumnLabel(
            tableName,
            column.column_name
          );
          return {
            columnName: column.column_name,
            columnLabel: label || column.column_name,
            dataType: column.data_type,
            isNullable: column.is_nullable === "YES",
            defaultValue: column.column_default,
            isPrimaryKey: pkColumns.includes(column.column_name), // PK 여부 추가
          };
        })
      );

      return {
        success: true,
        data: columnsWithLabels,
      };
    } catch (error) {
      console.error(`컬럼 정보 조회 오류 (${tableName}):`, error);
      return {
        success: false,
        message: "컬럼 정보 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 테이블의 Primary Key 컬럼 목록 조회
   */
  private async getPrimaryKeyColumns(tableName: string): Promise<string[]> {
    try {
      const result = await query<{ attname: string }>(
        `SELECT a.attname
         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]
      );

      return result.map((row) => row.attname);
    } catch (error) {
      console.error(`PK 컬럼 조회 오류 (${tableName}):`, error);
      return [];
    }
  }

  /**
   * 테이블 존재 여부 확인
   */
  private async checkTableExists(tableName: string): Promise<boolean> {
    try {
      const result = await query<{ exists: boolean }>(
        `SELECT EXISTS (
          SELECT FROM information_schema.tables 
          WHERE table_schema = 'public' 
          AND table_name = $1
        )`,
        [tableName]
      );

      return result[0]?.exists || false;
    } catch (error) {
      console.error("테이블 존재 확인 오류:", error);
      return false;
    }
  }

  /**
   * 특정 컬럼 존재 여부 확인
   */
  async checkColumnExists(
    tableName: string,
    columnName: string
  ): Promise<boolean> {
    try {
      const result = await query<{ exists: boolean }>(
        `SELECT EXISTS (
          SELECT FROM information_schema.columns 
          WHERE table_schema = 'public' 
          AND table_name = $1
          AND column_name = $2
        )`,
        [tableName, columnName]
      );

      return result[0]?.exists || false;
    } catch (error) {
      console.error("컬럼 존재 확인 오류:", error);
      return false;
    }
  }

  /**
   * 테이블 컬럼 정보 조회 (간단 버전)
   */
  private async getTableColumnsSimple(tableName: string): Promise<any[]> {
    const result = await query<any>(
      `SELECT column_name, data_type, is_nullable, column_default
      FROM information_schema.columns 
      WHERE table_name = $1 
      AND table_schema = 'public'
      ORDER BY ordinal_position`,
      [tableName]
    );

    return result;
  }

  /**
   * 컬럼 라벨 조회
   */
  private async getColumnLabel(
    tableName: string,
    columnName: string
  ): Promise<string | null> {
    try {
      // table_type_columns 테이블에서 라벨 조회
      const result = await query<{ column_label: string }>(
        `SELECT column_label 
        FROM table_type_columns 
        WHERE table_name = $1 AND column_name = $2 AND company_code = '*'
        LIMIT 1`,
        [tableName, columnName]
      );

      return result[0]?.column_label || null;
    } catch (error) {
      // table_type_columns 테이블이 없거나 오류가 발생하면 null 반환
      return null;
    }
  }

  /**
   * 레코드 상세 조회 (Entity Join 지원 + 그룹핑 기반 다중 레코드 조회)
   */
  async getRecordDetail(
    tableName: string,
    id: string | number,
    enableEntityJoin: boolean = false,
    groupByColumns: string[] = [],
    primaryKeyColumn?: string // 🆕 클라이언트에서 전달한 Primary Key 컬럼명
  ): Promise<ServiceResponse<any>> {
    try {
      // 테이블 접근 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      // 🆕 클라이언트에서 전달한 Primary Key 컬럼이 있으면 우선 사용
      let pkColumn = primaryKeyColumn || "";
      
      // Primary Key 컬럼이 없으면 자동 감지
      if (!pkColumn) {
        const pkResult = await query<{ attname: string }>(
          `SELECT a.attname
           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]
        );

        pkColumn = "id"; // 기본값
        if (pkResult.length > 0) {
          pkColumn = pkResult[0].attname;
        }
        console.log(`🔑 [getRecordDetail] 자동 감지된 Primary Key:`, pkResult);
      } else {
        console.log(`🔑 [getRecordDetail] 클라이언트 제공 Primary Key: ${pkColumn}`);
      }

      console.log(`🔑 [getRecordDetail] 테이블: ${tableName}, Primary Key 컬럼: ${pkColumn}, 조회 ID: ${id}`);

      // 🆕 Entity Join이 활성화된 경우
      if (enableEntityJoin) {
        const { EntityJoinService } = await import("./entityJoinService");
        const entityJoinService = new EntityJoinService();

        // Entity Join 구성 감지
        const joinConfigs =
          await entityJoinService.detectEntityJoins(tableName);

        if (joinConfigs.length > 0) {
          console.log(`✅ Entity Join 감지: ${joinConfigs.length}개`);

          // Entity Join 쿼리 생성 (개별 파라미터로 전달)
          const { query: joinQuery } = entityJoinService.buildJoinQuery(
            tableName,
            joinConfigs,
            ["*"],
            `main."${pkColumn}" = $1` // 🔧 main. 접두사 추가하여 모호성 해결
          );

          const result = await pool.query(joinQuery, [id]);

          if (result.rows.length === 0) {
            return {
              success: false,
              message: "레코드를 찾을 수 없습니다.",
              error: "RECORD_NOT_FOUND",
            };
          }

          // 🔧 날짜 타입 타임존 문제 해결: Date 객체를 YYYY-MM-DD 문자열로 변환
          const normalizeDates = (rows: any[]) => {
            return rows.map((row) => {
              const normalized: any = {};
              for (const [key, value] of Object.entries(row)) {
                if (value instanceof Date) {
                  // Date 객체를 YYYY-MM-DD 형식으로 변환 (타임존 무시)
                  const year = value.getFullYear();
                  const month = String(value.getMonth() + 1).padStart(2, "0");
                  const day = String(value.getDate()).padStart(2, "0");
                  normalized[key] = `${year}-${month}-${day}`;
                } else {
                  normalized[key] = value;
                }
              }
              return normalized;
            });
          };

          const normalizedRows = normalizeDates(result.rows);
          console.log(
            `✅ Entity Join 데이터 조회 성공 (날짜 정규화됨):`,
            normalizedRows[0]
          );

          // 🆕 groupByColumns가 있으면 그룹핑 기반 다중 레코드 조회
          if (groupByColumns.length > 0) {
            const baseRecord = result.rows[0];

            // 그룹핑 컬럼들의 값 추출
            const groupConditions: string[] = [];
            const groupValues: any[] = [];
            let paramIndex = 1;

            for (const col of groupByColumns) {
              const value = normalizedRows[0][col];
              if (value !== undefined && value !== null) {
                groupConditions.push(`main."${col}" = $${paramIndex}`);
                groupValues.push(value);
                paramIndex++;
              }
            }

            if (groupConditions.length > 0) {
              const groupWhereClause = groupConditions.join(" AND ");

              console.log(
                `🔍 그룹핑 조회: ${groupByColumns.join(", ")}`,
                groupValues
              );

              // 그룹핑 기준으로 모든 레코드 조회
              const { query: groupQuery } = entityJoinService.buildJoinQuery(
                tableName,
                joinConfigs,
                ["*"],
                groupWhereClause
              );

              const groupResult = await pool.query(groupQuery, groupValues);

              const normalizedGroupRows = normalizeDates(groupResult.rows);
              console.log(
                `✅ 그룹 레코드 조회 성공: ${normalizedGroupRows.length}개`
              );

              return {
                success: true,
                data: normalizedGroupRows, // 🔧 배열로 반환!
              };
            }
          }

          return {
            success: true,
            data: normalizedRows[0], // 그룹핑 없으면 단일 레코드
          };
        }
      }

      // 기본 쿼리 (Entity Join 없음)
      const queryText = `SELECT * FROM "${tableName}" WHERE "${pkColumn}" = $1`;
      const result = await query<any>(queryText, [id]);

      if (result.length === 0) {
        return {
          success: false,
          message: "레코드를 찾을 수 없습니다.",
          error: "RECORD_NOT_FOUND",
        };
      }

      return {
        success: true,
        data: result[0],
      };
    } catch (error) {
      console.error(`레코드 상세 조회 오류 (${tableName}/${id}):`, error);
      return {
        success: false,
        message: "레코드 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 조인된 데이터 조회 (🆕 Entity 조인 지원)
   */
  async getJoinedData(
    leftTable: string,
    rightTable: string,
    leftColumn: string,
    rightColumn: string,
    leftValue?: string | number,
    userCompany?: string,
    dataFilter?: any, // 🆕 데이터 필터
    enableEntityJoin?: boolean, // 🆕 Entity 조인 활성화
    displayColumns?: Array<{ name: string; label?: string }>, // 🆕 표시 컬럼 (item_info.item_name 등)
    deduplication?: {
      // 🆕 중복 제거 설정
      enabled: boolean;
      groupByColumn: string;
      keepStrategy: "latest" | "earliest" | "base_price" | "current_date";
      sortColumn?: string;
    }
  ): Promise<ServiceResponse<any[]>> {
    try {
      // 왼쪽 테이블 접근 검증
      const leftValidation = await this.validateTableAccess(leftTable);
      if (!leftValidation.valid) {
        return leftValidation.error!;
      }

      // 오른쪽 테이블 접근 검증
      const rightValidation = await this.validateTableAccess(rightTable);
      if (!rightValidation.valid) {
        return rightValidation.error!;
      }

      // 🆕 Entity 조인이 활성화된 경우 entityJoinService 사용
      if (enableEntityJoin) {
        try {
          const { entityJoinService } = await import("./entityJoinService");
          const joinConfigs =
            await entityJoinService.detectEntityJoins(rightTable);

          // 🆕 displayColumns에서 추가 조인 필요한 컬럼 감지 (item_info.item_name 등)
          if (displayColumns && Array.isArray(displayColumns)) {
            // 테이블별로 요청된 컬럼들을 그룹핑
            const tableColumns: Record<string, Set<string>> = {};

            for (const col of displayColumns) {
              if (col.name && col.name.includes(".")) {
                const [refTable, refColumn] = col.name.split(".");
                if (!tableColumns[refTable]) {
                  tableColumns[refTable] = new Set();
                }
                tableColumns[refTable].add(refColumn);
              }
            }

            // 각 테이블별로 처리
            for (const [refTable, refColumns] of Object.entries(tableColumns)) {
              // 이미 조인 설정에 있는지 확인
              const existingJoins = joinConfigs.filter(
                (jc) => jc.referenceTable === refTable
              );

              if (existingJoins.length > 0) {
                // 기존 조인이 있으면, 각 컬럼을 개별 조인으로 분리
                for (const refColumn of refColumns) {
                  // 이미 해당 컬럼을 표시하는 조인이 있는지 확인
                  const existingJoin = existingJoins.find(
                    (jc) =>
                      jc.displayColumns.length === 1 &&
                      jc.displayColumns[0] === refColumn
                  );

                  if (!existingJoin) {
                    // 없으면 새 조인 설정 복제하여 추가
                    const baseJoin = existingJoins[0];
                    const newJoin = {
                      ...baseJoin,
                      displayColumns: [refColumn],
                      aliasColumn: `${baseJoin.sourceColumn}_${refColumn}`, // 고유한 별칭 생성 (예: item_id_size)
                      // ⚠️ 중요: referenceTable과 referenceColumn을 명시하여 JOIN된 테이블에서 가져옴
                      referenceTable: refTable,
                      referenceColumn: baseJoin.referenceColumn, // item_number 등
                    };
                    joinConfigs.push(newJoin);
                    console.log(
                      `📌 추가 표시 컬럼: ${refTable}.${refColumn} (새 조인 생성, alias: ${newJoin.aliasColumn})`
                    );
                  }
                }
              } else {
                console.warn(`⚠️ 조인 설정 없음: ${refTable}`);
              }
            }
          }

          if (joinConfigs.length > 0) {
            console.log(
              `🔗 조인 모드에서 Entity 조인 적용: ${joinConfigs.length}개 설정`
            );

            // WHERE 조건 생성
            const whereConditions: string[] = [];
            const values: any[] = [];
            let paramIndex = 1;

            // 좌측 테이블 조인 조건 (leftValue로 필터링)
            // rightColumn을 직접 사용 (customer_item_mapping.customer_id = 'CUST-0002')
            if (leftValue !== undefined && leftValue !== null) {
              whereConditions.push(`main."${rightColumn}" = $${paramIndex}`);
              values.push(leftValue);
              paramIndex++;
            }

            // 회사별 필터링
            if (userCompany && userCompany !== "*") {
              const hasCompanyCode = await this.checkColumnExists(
                rightTable,
                "company_code"
              );
              if (hasCompanyCode) {
                whereConditions.push(`main.company_code = $${paramIndex}`);
                values.push(userCompany);
                paramIndex++;
              }
            }

            // 데이터 필터 적용 (buildDataFilterWhereClause 사용)
            if (
              dataFilter &&
              dataFilter.enabled &&
              dataFilter.filters &&
              dataFilter.filters.length > 0
            ) {
              const { buildDataFilterWhereClause } = await import(
                "../utils/dataFilterUtil"
              );
              const filterResult = buildDataFilterWhereClause(
                dataFilter,
                "main",
                paramIndex
              );
              if (filterResult.whereClause) {
                whereConditions.push(filterResult.whereClause);
                values.push(...filterResult.params);
                paramIndex += filterResult.params.length;
                console.log(
                  `🔍 Entity 조인에 데이터 필터 적용 (${rightTable}):`,
                  filterResult.whereClause
                );
                console.log(`📊 필터 파라미터:`, filterResult.params);
              }
            }

            const whereClause =
              whereConditions.length > 0 ? whereConditions.join(" AND ") : "";

            // Entity 조인 쿼리 빌드
            // buildJoinQuery가 자동으로 main.* 처리하므로 ["*"]만 전달
            const selectColumns = ["*"];

            const { query: finalQuery, aliasMap } =
              entityJoinService.buildJoinQuery(
                rightTable,
                joinConfigs,
                selectColumns,
                whereClause,
                "",
                undefined,
                undefined
              );

            console.log(`🔍 Entity 조인 쿼리 실행 (전체):`, finalQuery);
            console.log(`🔍 파라미터:`, values);

            const result = await pool.query(finalQuery, values);

            // 🔧 날짜 타입 타임존 문제 해결
            const normalizeDates = (rows: any[]) => {
              return rows.map((row) => {
                const normalized: any = {};
                for (const [key, value] of Object.entries(row)) {
                  if (value instanceof Date) {
                    const year = value.getFullYear();
                    const month = String(value.getMonth() + 1).padStart(2, "0");
                    const day = String(value.getDate()).padStart(2, "0");
                    normalized[key] = `${year}-${month}-${day}`;
                  } else {
                    normalized[key] = value;
                  }
                }
                return normalized;
              });
            };

            const normalizedRows = normalizeDates(result.rows);
            console.log(
              `✅ Entity 조인 성공! 반환된 데이터 개수: ${normalizedRows.length}개 (날짜 정규화됨)`
            );

            // 🆕 중복 제거 처리
            let finalData = normalizedRows;
            if (deduplication?.enabled && deduplication.groupByColumn) {
              console.log(
                `🔄 중복 제거 시작: 기준 컬럼 = ${deduplication.groupByColumn}, 전략 = ${deduplication.keepStrategy}`
              );
              finalData = this.deduplicateData(normalizedRows, deduplication);
              console.log(
                `✅ 중복 제거 완료: ${normalizedRows.length}개 → ${finalData.length}개`
              );
            }

            return {
              success: true,
              data: finalData,
            };
          }
        } catch (error) {
          console.error("Entity 조인 처리 실패, 기본 조인으로 폴백:", error);
          // Entity 조인 실패 시 기본 조인으로 폴백
        }
      }

      // 기본 조인 쿼리 (Entity 조인 미사용 또는 실패 시)
      let queryText = `
        SELECT DISTINCT r.* 
        FROM "${rightTable}" r
        INNER JOIN "${leftTable}" l 
        ON l."${leftColumn}" = r."${rightColumn}"
      `;

      const values: any[] = [];
      const whereConditions: string[] = [];
      let paramIndex = 1;

      // 좌측 값 필터링
      if (leftValue !== undefined && leftValue !== null) {
        whereConditions.push(`l."${leftColumn}" = $${paramIndex}`);
        values.push(leftValue);
        paramIndex++;
      }

      // 우측 테이블 회사별 필터링 (company_code 컬럼이 있는 경우)
      if (userCompany && userCompany !== "*") {
        const hasCompanyCode = await this.checkColumnExists(
          rightTable,
          "company_code"
        );
        if (hasCompanyCode) {
          whereConditions.push(`r.company_code = $${paramIndex}`);
          values.push(userCompany);
          paramIndex++;
          console.log(
            `🏢 우측 패널 회사별 필터링 적용: ${rightTable}.company_code = ${userCompany}`
          );
        }
      }

      // 🆕 데이터 필터 적용 (우측 패널에 대해, 테이블 별칭 "r" 사용)
      if (
        dataFilter &&
        dataFilter.enabled &&
        dataFilter.filters &&
        dataFilter.filters.length > 0
      ) {
        const filterResult = buildDataFilterWhereClause(
          dataFilter,
          "r",
          paramIndex
        );
        if (filterResult.whereClause) {
          whereConditions.push(filterResult.whereClause);
          values.push(...filterResult.params);
          paramIndex += filterResult.params.length;
          console.log(
            `🔍 데이터 필터 적용 (${rightTable}):`,
            filterResult.whereClause
          );
        }
      }

      // WHERE 절 추가
      if (whereConditions.length > 0) {
        queryText += ` WHERE ${whereConditions.join(" AND ")}`;
      }

      console.log("🔍 조인 쿼리 실행:", queryText);
      console.log("📊 조인 쿼리 파라미터:", values);

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

      // 🆕 중복 제거 처리
      let finalData = result;
      if (deduplication?.enabled && deduplication.groupByColumn) {
        console.log(
          `🔄 중복 제거 시작: 기준 컬럼 = ${deduplication.groupByColumn}, 전략 = ${deduplication.keepStrategy}`
        );
        finalData = this.deduplicateData(result, deduplication);
        console.log(
          `✅ 중복 제거 완료: ${result.length}개 → ${finalData.length}개`
        );
      }

      return {
        success: true,
        data: finalData,
      };
    } catch (error) {
      console.error(
        `조인 데이터 조회 오류 (${leftTable} → ${rightTable}):`,
        error
      );
      return {
        success: false,
        message: "조인 데이터 조회 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 레코드 생성
   */
  async createRecord(
    tableName: string,
    data: Record<string, any>
  ): Promise<ServiceResponse<any>> {
    try {
      // 테이블 접근 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      // 🆕 테이블에 존재하는 컬럼만 필터링 (존재하지 않는 컬럼 제외)
      const tableColumns = await this.getTableColumnsSimple(tableName);
      const validColumnNames = new Set(
        tableColumns.map((col: any) => col.column_name)
      );

      const invalidColumns: string[] = [];
      const filteredData = Object.fromEntries(
        Object.entries(data).filter(([key]) => {
          if (validColumnNames.has(key)) {
            return true;
          }
          invalidColumns.push(key);
          return false;
        })
      );

      if (invalidColumns.length > 0) {
        console.log(
          `⚠️ [createRecord] 테이블에 없는 컬럼 제외: ${invalidColumns.join(", ")}`
        );
      }

      const columns = Object.keys(filteredData);
      const values = Object.values(filteredData);
      const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
      const columnNames = columns.map((col) => `"${col}"`).join(", ");

      const queryText = `
        INSERT INTO "${tableName}" (${columnNames})
        VALUES (${placeholders})
        RETURNING *
      `;

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

      return {
        success: true,
        data: result[0],
      };
    } catch (error) {
      console.error(`레코드 생성 오류 (${tableName}):`, error);
      return {
        success: false,
        message: "레코드 생성 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 레코드 수정
   */
  async updateRecord(
    tableName: string,
    id: string | number,
    data: Record<string, any>
  ): Promise<ServiceResponse<any>> {
    try {
      // 테이블 접근 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      // _relationInfo 추출 (조인 관계 업데이트용)
      const relationInfo = data._relationInfo;
      let cleanData = { ...data };
      delete cleanData._relationInfo;

      // 🆕 테이블에 존재하는 컬럼만 필터링 (존재하지 않는 컬럼 제외)
      const tableColumns = await this.getTableColumnsSimple(tableName);
      const validColumnNames = new Set(
        tableColumns.map((col: any) => col.column_name)
      );

      const invalidColumns: string[] = [];
      cleanData = Object.fromEntries(
        Object.entries(cleanData).filter(([key]) => {
          if (validColumnNames.has(key)) {
            return true;
          }
          invalidColumns.push(key);
          return false;
        })
      );

      if (invalidColumns.length > 0) {
        console.log(
          `⚠️ [updateRecord] 테이블에 없는 컬럼 제외: ${invalidColumns.join(", ")}`
        );
      }

      // Primary Key 컬럼 찾기
      const pkResult = await query<{ attname: string }>(
        `SELECT a.attname
         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]
      );

      let pkColumn = "id";
      if (pkResult.length > 0) {
        pkColumn = pkResult[0].attname;
      }

      const columns = Object.keys(cleanData);
      const values = Object.values(cleanData);
      const setClause = columns
        .map((col, index) => `"${col}" = $${index + 1}`)
        .join(", ");

      const queryText = `
        UPDATE "${tableName}"
        SET ${setClause}
        WHERE "${pkColumn}" = $${values.length + 1}
        RETURNING *
      `;

      values.push(id);
      const result = await query<any>(queryText, values);

      if (result.length === 0) {
        return {
          success: false,
          message: "레코드를 찾을 수 없습니다.",
          error: "RECORD_NOT_FOUND",
        };
      }

      // 🔗 조인 관계가 있는 경우, 연결된 테이블의 FK도 업데이트
      if (
        relationInfo &&
        relationInfo.rightTable &&
        relationInfo.leftColumn &&
        relationInfo.rightColumn
      ) {
        const { rightTable, leftColumn, rightColumn, oldLeftValue } =
          relationInfo;
        const newLeftValue = cleanData[leftColumn];

        // leftColumn 값이 변경된 경우에만 우측 테이블 업데이트
        if (newLeftValue !== undefined && newLeftValue !== oldLeftValue) {
          console.log("🔗 조인 관계 FK 업데이트:", {
            rightTable,
            rightColumn,
            oldValue: oldLeftValue,
            newValue: newLeftValue,
          });

          try {
            const updateRelatedQuery = `
              UPDATE "${rightTable}"
              SET "${rightColumn}" = $1
              WHERE "${rightColumn}" = $2
            `;
            const updateResult = await query(updateRelatedQuery, [
              newLeftValue,
              oldLeftValue,
            ]);
            console.log(
              `✅ 연결된 ${rightTable} 테이블의 ${updateResult.length}개 레코드 업데이트 완료`
            );
          } catch (relError) {
            console.error("❌ 연결된 테이블 업데이트 실패:", relError);
            // 연결된 테이블 업데이트 실패 시 롤백은 하지 않고 경고만 로그
          }
        }
      }

      return {
        success: true,
        data: result[0],
      };
    } catch (error) {
      console.error(`레코드 수정 오류 (${tableName}/${id}):`, error);
      return {
        success: false,
        message: "레코드 수정 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 레코드 삭제
   */
  async deleteRecord(
    tableName: string,
    id: string | number | Record<string, any>
  ): Promise<ServiceResponse<void>> {
    try {
      // 테이블 접근 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      // Primary Key 컬럼 찾기 (복합키 지원)
      const pkResult = await query<{ attname: string }>(
        `SELECT a.attname
         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
         ORDER BY a.attnum`,
        [tableName]
      );

      console.log(`🔍 테이블 ${tableName}의 Primary Key 조회 결과:`, {
        pkColumns: pkResult.map((r) => r.attname),
        pkCount: pkResult.length,
        inputId: typeof id === "object" ? JSON.stringify(id).substring(0, 200) + "..." : id,
        inputIdType: typeof id,
      });

      let whereClauses: string[] = [];
      let params: any[] = [];

      if (pkResult.length > 1) {
        // 복합키인 경우: id가 객체여야 함
        console.log(
          `🔑 복합키 테이블: ${tableName}, PK: [${pkResult.map((r) => r.attname).join(", ")}]`
        );

        if (typeof id === "object" && !Array.isArray(id)) {
          // id가 객체인 경우: { user_id: 'xxx', dept_code: 'yyy' }
          pkResult.forEach((pk, index) => {
            whereClauses.push(`"${pk.attname}" = $${index + 1}`);
            params.push(id[pk.attname]);
          });
        } else {
          // id가 문자열/숫자인 경우: 첫 번째 PK만 사용 (하위 호환성)
          whereClauses.push(`"${pkResult[0].attname}" = $1`);
          params.push(id);
        }
      } else {
        // 단일키인 경우
        const pkColumn = pkResult.length > 0 ? pkResult[0].attname : "id";
        whereClauses.push(`"${pkColumn}" = $1`);
        params.push(typeof id === "object" ? id[pkColumn] : id);
      }

      const queryText = `DELETE FROM "${tableName}" WHERE ${whereClauses.join(" AND ")} RETURNING *`;
      console.log(`🗑️ 삭제 쿼리:`, queryText, params);

      const result = await query<any>(queryText, params);

      // 삭제된 행이 없으면 실패 처리
      if (result.length === 0) {
        console.warn(
          `⚠️ 레코드 삭제 실패: ${tableName}, 해당 조건에 맞는 레코드가 없습니다.`,
          { whereClauses, params }
        );
        return {
          success: false,
          message: "삭제할 레코드를 찾을 수 없습니다. 이미 삭제되었거나 권한이 없습니다.",
          error: "RECORD_NOT_FOUND",
        };
      }

      console.log(
        `✅ 레코드 삭제 완료: ${tableName}, 영향받은 행: ${result.length}`
      );

      return {
        success: true,
        data: result[0], // 삭제된 레코드 정보 반환
      };
    } catch (error) {
      console.error(`레코드 삭제 오류 (${tableName}):`, error);
      return {
        success: false,
        message: "레코드 삭제 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 조건에 맞는 모든 레코드 삭제 (그룹 삭제)
   * @param tableName 테이블명
   * @param filterConditions 삭제 조건
   * @param userCompany 사용자 회사 코드 (멀티테넌시 필터링)
   */
  async deleteGroupRecords(
    tableName: string,
    filterConditions: Record<string, any>,
    userCompany?: string
  ): Promise<ServiceResponse<{ deleted: number }>> {
    try {
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      const whereConditions: string[] = [];
      const whereValues: any[] = [];
      let paramIndex = 1;

      // 사용자 필터 조건 추가
      for (const [key, value] of Object.entries(filterConditions)) {
        whereConditions.push(`"${key}" = $${paramIndex}`);
        whereValues.push(value);
        paramIndex++;
      }

      if (whereConditions.length === 0) {
        return {
          success: false,
          message: "삭제 조건이 없습니다.",
          error: "NO_CONDITIONS",
        };
      }

      // 🔒 멀티테넌시: company_code 필터링 (최고 관리자 제외)
      const hasCompanyCode = await this.checkColumnExists(tableName, "company_code");
      if (hasCompanyCode && userCompany && userCompany !== "*") {
        whereConditions.push(`"company_code" = $${paramIndex}`);
        whereValues.push(userCompany);
        paramIndex++;
        console.log(`🔒 멀티테넌시 필터 적용: company_code = ${userCompany}`);
      }

      const whereClause = whereConditions.join(" AND ");
      const deleteQuery = `DELETE FROM "${tableName}" WHERE ${whereClause} RETURNING *`;

      console.log(`🗑️ 그룹 삭제:`, { 
        tableName, 
        conditions: filterConditions,
        userCompany,
        whereClause,
      });

      const result = await pool.query(deleteQuery, whereValues);

      console.log(`✅ 그룹 삭제 성공: ${result.rowCount}개`);

      return { success: true, data: { deleted: result.rowCount || 0 } };
    } catch (error) {
      console.error("그룹 삭제 오류:", error);
      return {
        success: false,
        message: "그룹 삭제 실패",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * 그룹화된 데이터 UPSERT
   * - 부모 키(예: customer_id, item_id)와 레코드 배열을 받아
   * - 기존 DB의 레코드들과 비교하여 INSERT/UPDATE/DELETE 수행
   * - 각 레코드의 모든 필드 조합을 고유 키로 사용
   */
  async upsertGroupedRecords(
    tableName: string,
    parentKeys: Record<string, any>,
    records: Array<Record<string, any>>,
    userCompany?: string,
    userId?: string,
    deleteOrphans: boolean = true
  ): Promise<
    ServiceResponse<{ inserted: number; updated: number; deleted: number; savedIds?: any[] }>
  > {
    try {
      // 테이블 접근 권한 검증
      const validation = await this.validateTableAccess(tableName);
      if (!validation.valid) {
        return validation.error!;
      }

      // Primary Key 감지
      const pkColumns = await this.getPrimaryKeyColumns(tableName);
      if (!pkColumns || pkColumns.length === 0) {
        return {
          success: false,
          message: `테이블 '${tableName}'의 Primary Key를 찾을 수 없습니다.`,
          error: "PRIMARY_KEY_NOT_FOUND",
        };
      }
      const pkColumn = pkColumns[0]; // 첫 번째 PK 사용

      console.log(`🔍 UPSERT 시작: ${tableName}`, {
        parentKeys,
        newRecordsCount: records.length,
        primaryKey: pkColumn,
      });

      // 1. 기존 DB 레코드 조회 (parentKeys 기준)
      const whereConditions: string[] = [];
      const whereValues: any[] = [];
      let paramIndex = 1;

      for (const [key, value] of Object.entries(parentKeys)) {
        whereConditions.push(`"${key}" = $${paramIndex}`);
        whereValues.push(value);
        paramIndex++;
      }

      const whereClause = whereConditions.join(" AND ");
      const selectQuery = `SELECT * FROM "${tableName}" WHERE ${whereClause}`;

      console.log(`📋 기존 레코드 조회:`, {
        query: selectQuery,
        values: whereValues,
      });

      const existingRecords = await pool.query(selectQuery, whereValues);

      console.log(`✅ 기존 레코드: ${existingRecords.rows.length}개`);

      // 2. id 기반 UPSERT: 레코드에 id(PK)가 있으면 UPDATE, 없으면 INSERT
      let inserted = 0;
      let updated = 0;
      let deleted = 0;

      // 날짜 필드를 YYYY-MM-DD 형식으로 변환하는 헬퍼 함수
      const normalizeDateValue = (value: any): any => {
        if (value == null) return value;
        if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
          return value.split("T")[0];
        }
        return value;
      };

      const existingIds = new Set(existingRecords.rows.map((r: any) => r[pkColumn]));
      const processedIds = new Set<string>(); // UPDATE 처리된 id 추적

      for (const newRecord of records) {
        // 날짜 필드 정규화
        const normalizedRecord: Record<string, any> = {};
        for (const [key, value] of Object.entries(newRecord)) {
          normalizedRecord[key] = normalizeDateValue(value);
        }

        const recordId = normalizedRecord[pkColumn]; // 프론트에서 보낸 기존 레코드의 id

        if (recordId && existingIds.has(recordId)) {
          // ===== UPDATE: id(PK)가 DB에 존재 → 해당 레코드 업데이트 =====
          const fullRecord = { ...parentKeys, ...normalizedRecord };
          const updateFields: string[] = [];
          const updateValues: any[] = [];
          let paramIdx = 1;

          for (const [key, value] of Object.entries(fullRecord)) {
            if (key !== pkColumn) {
              updateFields.push(`"${key}" = $${paramIdx}`);
              updateValues.push(value);
              paramIdx++;
            }
          }

          if (updateFields.length > 0) {
            updateValues.push(recordId);
            const updateQuery = `
              UPDATE "${tableName}"
              SET ${updateFields.join(", ")}, updated_date = NOW()
              WHERE "${pkColumn}" = $${paramIdx}
            `;
            await pool.query(updateQuery, updateValues);
            updated++;
            processedIds.add(recordId);
            console.log(`✏️ UPDATE by id: ${pkColumn} = ${recordId}`);
          }
        } else {
          // ===== INSERT: id 없음 또는 DB에 없음 → 새 레코드 삽입 =====
          const { [pkColumn]: _removedId, created_date: _cd, ...cleanRecord } = normalizedRecord;
          const fullRecord = { ...parentKeys, ...cleanRecord };
          const newId = uuidv4();
          const recordWithMeta: Record<string, any> = {
            ...fullRecord,
            [pkColumn]: newId,
            created_date: "NOW()",
            updated_date: "NOW()",
          };

          if (!recordWithMeta.company_code && userCompany && userCompany !== "*") {
            recordWithMeta.company_code = userCompany;
          }
          if (!recordWithMeta.writer && userId) {
            recordWithMeta.writer = userId;
          }

          const insertPlaceholders: string[] = [];
          const insertValues: any[] = [];
          let paramIdx = 1;

          for (const field of Object.keys(recordWithMeta)) {
            if (recordWithMeta[field] === "NOW()") {
              insertPlaceholders.push("NOW()");
            } else {
              insertPlaceholders.push(`$${paramIdx}`);
              insertValues.push(recordWithMeta[field]);
              paramIdx++;
            }
          }

          const insertQuery = `
            INSERT INTO "${tableName}" (${Object.keys(recordWithMeta)
              .map((f) => `"${f}"`)
              .join(", ")})
            VALUES (${insertPlaceholders.join(", ")})
          `;
          await pool.query(insertQuery, insertValues);
          inserted++;
          processedIds.add(newId);
          console.log(`➕ INSERT: 새 레코드 ${pkColumn} = ${newId}`);
        }
      }

      // 3. 고아 레코드 삭제: deleteOrphans=true일 때만 (EDIT 모드)
      // CREATE 모드에서는 기존 레코드를 건드리지 않음
      if (deleteOrphans) {
        for (const existingRow of existingRecords.rows) {
          const existId = existingRow[pkColumn];
          if (!processedIds.has(existId)) {
            const deleteQuery = `DELETE FROM "${tableName}" WHERE "${pkColumn}" = $1`;
            await pool.query(deleteQuery, [existId]);
            deleted++;
            console.log(`🗑️ DELETE orphan: ${pkColumn} = ${existId}`);
          }
        }
      }

      const savedIds = Array.from(processedIds);
      console.log(`✅ UPSERT 완료:`, { inserted, updated, deleted, savedIds });

      return {
        success: true,
        data: { inserted, updated, deleted, savedIds },
      };
    } catch (error) {
      console.error(`UPSERT 오류 (${tableName}):`, error);
      return {
        success: false,
        message: "데이터 저장 중 오류가 발생했습니다.",
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }
}

export const dataService = new DataService();
