import { query, queryOne } from "../database/db";
import { logger } from "../utils/logger";
import {
  EntityJoinConfig,
  BatchLookupRequest,
  BatchLookupResponse,
} from "../types/tableManagement";
import { referenceCacheService } from "./referenceCacheService";

/**
 * Entity 조인 기능을 제공하는 서비스
 * ID값을 의미있는 데이터로 자동 변환하는 스마트 테이블 시스템
 */
export class EntityJoinService {
  /**
   * 테이블의 Entity 컬럼들을 감지하여 조인 설정 생성
   * @param tableName 테이블명
   * @param screenEntityConfigs 화면별 엔티티 설정 (선택사항)
   * @param companyCode 회사코드 (회사별 설정 우선, 없으면 전체 조회)
   */
  async detectEntityJoins(
    tableName: string,
    screenEntityConfigs?: Record<string, any>,
    companyCode?: string
  ): Promise<EntityJoinConfig[]> {
    try {
      logger.info(`Entity 컬럼 감지 시작: ${tableName} (companyCode: ${companyCode || 'all'})`);

      // table_type_columns에서 entity 및 category 타입인 컬럼들 조회
      // 회사코드가 있으면 해당 회사 + '*' 만 조회, 회사별 우선
      const entityColumns = await query<{
        column_name: string;
        input_type: string;
        reference_table: string;
        reference_column: string;
        display_column: string | null;
      }>(
        `SELECT DISTINCT ON (column_name) 
         column_name, input_type, reference_table, reference_column, display_column
         FROM table_type_columns
         WHERE table_name = $1 
         AND input_type IN ('entity', 'category')
         AND reference_table IS NOT NULL
         AND reference_table != ''
         ${companyCode ? `AND company_code IN ($2, '*')` : ''}
         ORDER BY column_name, 
           CASE WHEN company_code = '*' THEN 1 ELSE 0 END`,
        companyCode ? [tableName, companyCode] : [tableName]
      );

      logger.info(`🔍 Entity 컬럼 조회 결과: ${entityColumns.length}개 발견`);
      entityColumns.forEach((col, index) => {
        logger.info(
          `  ${index + 1}. ${col.column_name} -> ${col.reference_table}.${col.reference_column} (display: ${col.display_column})`
        );
      });

      const joinConfigs: EntityJoinConfig[] = [];

      // 🎯 writer 컬럼 자동 감지 및 조인 설정 추가
      const tableColumns = await query<{ column_name: string }>(
        `SELECT column_name 
         FROM information_schema.columns 
         WHERE table_name = $1 
         AND table_schema = 'public'
         AND column_name = 'writer'`,
        [tableName]
      );

      if (tableColumns.length > 0) {
        const writerJoinConfig: EntityJoinConfig = {
          sourceTable: tableName,
          sourceColumn: "writer",
          referenceTable: "user_info",
          referenceColumn: "user_id",
          displayColumns: ["user_name"],
          displayColumn: "user_name",
          aliasColumn: "writer_name",
          separator: " - ",
        };

        if (await this.validateJoinConfig(writerJoinConfig)) {
          joinConfigs.push(writerJoinConfig);
        }
      }

      for (const column of entityColumns) {
        // 카테고리 타입인 경우 자동으로 category_values 테이블 참조 설정
        let referenceTable = column.reference_table;
        let referenceColumn = column.reference_column;
        let displayColumn = column.display_column;

        if (column.input_type === "category") {
          // 카테고리 타입: reference 정보가 비어있어도 자동 설정
          referenceTable = referenceTable || "category_values";
          referenceColumn = referenceColumn || "value_code";
          displayColumn = displayColumn || "value_label";

          logger.info(`🏷️ 카테고리 타입 자동 설정: ${column.column_name}`, {
            referenceTable,
            referenceColumn,
            displayColumn,
          });
        }

        logger.info(`🔍 Entity 컬럼 상세 정보:`, {
          column_name: column.column_name,
          input_type: column.input_type,
          reference_table: referenceTable,
          reference_column: referenceColumn,
          display_column: displayColumn,
        });

        if (!column.column_name || !referenceTable || !referenceColumn) {
          logger.warn(`⚠️ 필수 정보 누락으로 스킵: ${column.column_name}`);
          continue;
        }

        // 화면별 엔티티 설정이 있으면 우선 사용, 없으면 기본값 사용
        const screenConfig = screenEntityConfigs?.[column.column_name];
        let displayColumns: string[] = [];
        let separator = " - ";

        logger.info(`🔍 조건 확인 - 컬럼: ${column.column_name}`, {
          hasScreenConfig: !!screenConfig,
          hasDisplayColumns: screenConfig?.displayColumns,
          displayColumn: column.display_column,
        });

        if (screenConfig && screenConfig.displayColumns) {
          // 화면에서 설정된 표시 컬럼들 사용 (기본 테이블 + 조인 테이블 조합 지원)
          displayColumns = screenConfig.displayColumns;
          separator = screenConfig.separator || " - ";
          console.log(`🎯 화면별 엔티티 설정 적용: ${column.column_name}`, {
            displayColumns,
            separator,
            screenConfig,
          });
        } else if (displayColumn && displayColumn !== "none") {
          // 기존 설정된 단일 표시 컬럼 사용 (none이 아닌 경우만)
          displayColumns = [displayColumn];
          logger.info(
            `🔧 기존 display_column 사용: ${column.column_name} → ${displayColumn}`
          );
        } else {
          // display_column이 "none"이거나 없는 경우 참조 테이블의 표시용 컬럼 자동 감지
          logger.info(`🔍 ${referenceTable}의 표시 컬럼 자동 감지 중...`);

          // 참조 테이블의 모든 컬럼 이름 가져오기
          const tableColumnsResult = await query<{ column_name: string }>(
            `SELECT column_name 
             FROM information_schema.columns 
             WHERE table_name = $1 
             AND table_schema = 'public'
             ORDER BY ordinal_position`,
            [referenceTable]
          );

          if (tableColumnsResult.length > 0) {
            const allColumns = tableColumnsResult.map((col) => col.column_name);

            // 🆕 표시용 컬럼 자동 감지 (우선순위 순서)
            // 1. *_name 컬럼 (item_name, customer_name 등)
            // 2. name 컬럼
            // 3. label 컬럼
            // 4. title 컬럼
            // 5. 참조 컬럼 (referenceColumn)
            const nameColumn = allColumns.find(
              (col) => col.endsWith("_name") && col !== "company_name"
            );
            const simpleNameColumn = allColumns.find((col) => col === "name");
            const labelColumn = allColumns.find(
              (col) => col === "label" || col.endsWith("_label")
            );
            const titleColumn = allColumns.find((col) => col === "title");

            // 우선순위에 따라 표시 컬럼 선택
            const displayColumn =
              nameColumn ||
              simpleNameColumn ||
              labelColumn ||
              titleColumn ||
              referenceColumn;
            displayColumns = [displayColumn];

            logger.info(
              `✅ ${referenceTable}의 표시 컬럼 자동 감지: ${displayColumn} (전체 ${allColumns.length}개 중)`
            );
          } else {
            // 테이블 컬럼을 못 찾으면 기본값 사용
            displayColumns = [referenceColumn];
            logger.warn(
              `⚠️ ${referenceTable}의 컬럼 조회 실패, 기본값 사용: ${referenceColumn}`
            );
          }
        }

        // 🎯 별칭 컬럼명 생성 - 사용자가 선택한 displayColumns 기반으로 동적 생성
        // 단일 컬럼: manager + user_name → manager_user_name
        // 여러 컬럼: 첫 번째 컬럼 기준 (나머지는 개별 alias로 처리됨)
        const firstDisplayColumn = displayColumns[0] || "name";
        const aliasColumn = `${column.column_name}_${firstDisplayColumn}`;
        
        logger.info(`🔧 별칭 컬럼명 생성: ${column.column_name} + ${firstDisplayColumn} → ${aliasColumn}`);

        const joinConfig: EntityJoinConfig = {
          sourceTable: tableName,
          sourceColumn: column.column_name,
          referenceTable: referenceTable, // 카테고리의 경우 자동 설정된 값 사용
          referenceColumn: referenceColumn, // 카테고리의 경우 자동 설정된 값 사용
          displayColumns: displayColumns,
          displayColumn: displayColumns[0], // 하위 호환성
          aliasColumn: aliasColumn,
          separator: separator,
        };

        logger.info(`🔧 기본 조인 설정 생성:`, {
          sourceTable: joinConfig.sourceTable,
          sourceColumn: joinConfig.sourceColumn,
          referenceTable: joinConfig.referenceTable,
          aliasColumn: joinConfig.aliasColumn,
          displayColumns: joinConfig.displayColumns,
        });

        // 조인 설정 유효성 검증
        logger.info(
          `🔍 조인 설정 검증 중: ${joinConfig.sourceColumn} -> ${joinConfig.referenceTable}`
        );
        if (await this.validateJoinConfig(joinConfig)) {
          joinConfigs.push(joinConfig);
          logger.info(`✅ 조인 설정 추가됨: ${joinConfig.aliasColumn}`);
        } else {
          logger.warn(`❌ 조인 설정 검증 실패: ${joinConfig.sourceColumn}`);
        }
      }

      logger.info(`🎯 Entity 조인 설정 생성 완료: ${joinConfigs.length}개`);
      joinConfigs.forEach((config, index) => {
        logger.info(
          `  ${index + 1}. ${config.sourceColumn} -> ${config.referenceTable}.${config.referenceColumn} AS ${config.aliasColumn}`
        );
      });
      return joinConfigs;
    } catch (error) {
      logger.error(`Entity 조인 감지 실패: ${tableName}`, error);
      return [];
    }
  }

  /**
   * 날짜 컬럼을 YYYY-MM-DD 형식으로 변환하는 SQL 표현식
   */
  private formatDateColumn(
    tableAlias: string,
    columnName: string,
    dataType?: string
  ): string {
    // date, timestamp 타입이면 TO_CHAR로 변환
    if (
      dataType &&
      (dataType.includes("date") || dataType.includes("timestamp"))
    ) {
      return `TO_CHAR(${tableAlias}.${columnName}, 'YYYY-MM-DD')`;
    }
    // 기본은 TEXT 캐스팅
    return `${tableAlias}.${columnName}::TEXT`;
  }

  /**
   * Entity 조인이 포함된 SQL 쿼리 생성
   */
  buildJoinQuery(
    tableName: string,
    joinConfigs: EntityJoinConfig[],
    selectColumns: string[],
    whereClause: string = "",
    orderBy: string = "",
    limit?: number,
    offset?: number,
    columnTypes?: Map<string, string>, // 컬럼명 → 데이터 타입 매핑
    referenceTableColumns?: Map<string, string[]> // 🆕 참조 테이블별 전체 컬럼 목록
  ): { query: string; aliasMap: Map<string, string> } {
    try {
      // 기본 SELECT 컬럼들 (날짜는 YYYY-MM-DD 형식, 나머지는 TEXT 캐스팅)
      // 🔧 "*"는 전체 조회하되, 날짜 타입 타임존 문제를 피하기 위해
      // jsonb_build_object를 사용하여 명시적으로 변환
      let baseColumns: string;
      if (selectColumns.length === 1 && selectColumns[0] === "*") {
        // main.* 사용 시 날짜 타입 필드만 TO_CHAR로 변환
        // PostgreSQL의 날짜 → 타임스탬프 자동 변환으로 인한 타임존 문제 방지
        baseColumns = `main.*`;
        logger.info(
          `⚠️ [buildJoinQuery] main.* 사용 - 날짜 타임존 변환 주의 필요`
        );
      } else {
        baseColumns = selectColumns
          .map((col) => {
            const dataType = columnTypes?.get(col);
            const formattedCol = this.formatDateColumn("main", col, dataType);
            return `${formattedCol} AS ${col}`;
          })
          .join(", ");
      }

      // Entity 조인 컬럼들 (COALESCE로 NULL을 빈 문자열로 처리)
      // 별칭 매핑 생성 (JOIN 절과 동일한 로직)
      const aliasMap = new Map<string, string>();
      const usedAliasesForColumns = new Set<string>();

      // joinConfigs를 참조 테이블 + 소스 컬럼별로 중복 제거하여 별칭 생성
      // (category_values는 같은 테이블이라도 sourceColumn마다 별도 JOIN 필요)
      const uniqueReferenceTableConfigs = joinConfigs.reduce((acc, config) => {
        if (
          !acc.some(
            (existingConfig) =>
              existingConfig.referenceTable === config.referenceTable &&
              existingConfig.sourceColumn === config.sourceColumn
          )
        ) {
          acc.push(config);
        }
        return acc;
      }, [] as EntityJoinConfig[]);

      logger.info(
        `🔧 별칭 생성 시작: ${uniqueReferenceTableConfigs.length}개 고유 테이블+컬럼 조합`
      );

      uniqueReferenceTableConfigs.forEach((config) => {
        let baseAlias = config.referenceTable.substring(0, 3);
        let alias = baseAlias;
        let counter = 1;

        while (usedAliasesForColumns.has(alias)) {
          alias = `${baseAlias}${counter}`;
          counter++;
        }
        usedAliasesForColumns.add(alias);
        // 같은 테이블이라도 sourceColumn이 다르면 별도 별칭 생성 (category_values 대응)
        const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
        aliasMap.set(aliasKey, alias);
        logger.info(
          `🔧 별칭 생성: ${config.referenceTable}.${config.sourceColumn} → ${alias}`
        );
      });

      // 🔧 생성된 별칭 중복 방지를 위한 Set
      const generatedAliases = new Set<string>();

      const joinColumns = uniqueReferenceTableConfigs
        .map((config) => {
          const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
          const alias = aliasMap.get(aliasKey);
          const resultColumns: string[] = [];

          // 🆕 참조 테이블의 전체 컬럼 목록이 있으면 모든 컬럼을 SELECT
          const refTableCols = referenceTableColumns?.get(
            `${config.referenceTable}:${config.sourceColumn}`
          ) || referenceTableColumns?.get(config.referenceTable);

          if (refTableCols && refTableCols.length > 0) {
            // 메타 컬럼은 제외 (메인 테이블과 중복되거나 불필요)
            const skipColumns = new Set(["company_code", "created_date", "updated_date", "writer"]);

            for (const col of refTableCols) {
              if (skipColumns.has(col)) continue;

              const colAlias = `${config.sourceColumn}_${col}`;
              if (generatedAliases.has(colAlias)) continue;

              resultColumns.push(
                `COALESCE(${alias}."${col}"::TEXT, '') AS "${colAlias}"`
              );
              generatedAliases.add(colAlias);
            }

            // _label 필드도 추가 (기존 호환성)
            const labelAlias = `${config.sourceColumn}_label`;
            if (!generatedAliases.has(labelAlias)) {
              // 표시용 컬럼 자동 감지: *_name > name > label > referenceColumn
              const nameCol = refTableCols.find((c) => c.endsWith("_name") && c !== "company_name");
              const displayCol = nameCol || refTableCols.find((c) => c === "name") || config.referenceColumn;
              resultColumns.push(
                `COALESCE(${alias}."${displayCol}"::TEXT, '') AS "${labelAlias}"`
              );
              generatedAliases.add(labelAlias);
            }
          } else {
            // 🔄 기존 로직 (참조 테이블 컬럼 목록이 없는 경우 - fallback)
            const displayColumns = config.displayColumns || [config.displayColumn];

            if (displayColumns.length === 0 || !displayColumns[0]) {
              resultColumns.push(
                `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.aliasColumn}`
              );
            } else if (displayColumns.length === 1) {
              const col = displayColumns[0];
              const isJoinTableColumn =
                config.referenceTable && config.referenceTable !== tableName;

              if (isJoinTableColumn) {
                resultColumns.push(
                  `COALESCE(${alias}.${col}::TEXT, '') AS ${config.aliasColumn}`
                );
                const labelAlias = `${config.sourceColumn}_label`;
                if (!generatedAliases.has(labelAlias)) {
                  resultColumns.push(
                    `COALESCE(${alias}.${col}::TEXT, '') AS ${labelAlias}`
                  );
                  generatedAliases.add(labelAlias);
                }
              } else {
                resultColumns.push(
                  `COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}`
                );
              }
            } else {
              displayColumns.forEach((col) => {
                const isJoinTableColumn =
                  config.referenceTable && config.referenceTable !== tableName;
                const individualAlias = `${config.sourceColumn}_${col}`;
                if (generatedAliases.has(individualAlias)) return;

                if (isJoinTableColumn) {
                  resultColumns.push(
                    `COALESCE(${alias}.${col}::TEXT, '') AS ${individualAlias}`
                  );
                } else {
                  resultColumns.push(
                    `COALESCE(main.${col}::TEXT, '') AS ${individualAlias}`
                  );
                }
                generatedAliases.add(individualAlias);
              });
            }
          }

          return resultColumns.join(", ");
        })
        .filter(Boolean)
        .join(", ");

      // SELECT 절 구성
      const selectClause = joinColumns
        ? `${baseColumns}, ${joinColumns}`
        : baseColumns;

      // FROM 절 (메인 테이블)
      const fromClause = `FROM ${tableName} main`;

      // LEFT JOIN 절들 (위에서 생성한 별칭 매핑 사용, 각 sourceColumn마다 별도 JOIN)
      // 멀티테넌시: 모든 조인에 company_code 조건 추가 (다른 회사 데이터 혼합 방지)
      const joinClauses = uniqueReferenceTableConfigs
        .map((config) => {
          const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
          const alias = aliasMap.get(aliasKey);

          // category_values는 특별한 조인 조건 필요 (회사별 필터링)
          // is_active 필터 제거: 비활성화된 카테고리도 라벨로 표시되어야 함
          if (config.referenceTable === "category_values") {
            return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}' AND ${alias}.company_code = main.company_code`;
          }

          // user_info는 전역 테이블이므로 company_code 조건 없이 조인
          if (config.referenceTable === "user_info") {
            return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT`;
          }

          // 일반 테이블: company_code가 있으면 같은 회사 데이터만 조인 (멀티테넌시)
          // supplier_mng, customer_mng, item_info 등 회사별 데이터 테이블
          // ::TEXT 캐스팅으로 varchar/integer 등 타입 불일치 방지
          return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT AND ${alias}.company_code = main.company_code`;
        })
        .join("\n");

      // WHERE 절
      const whereSQL = whereClause ? `WHERE ${whereClause}` : "";

      // ORDER BY 절
      const orderSQL = orderBy ? `ORDER BY ${orderBy}` : "";

      // LIMIT 및 OFFSET
      let limitSQL = "";
      if (limit !== undefined) {
        limitSQL = `LIMIT ${limit}`;
        if (offset !== undefined) {
          limitSQL += ` OFFSET ${offset}`;
        }
      }

      // 최종 쿼리 조합
      const query = [
        `SELECT ${selectClause}`,
        fromClause,
        joinClauses,
        whereSQL,
        orderSQL,
        limitSQL,
      ]
        .filter(Boolean)
        .join("\n");

      logger.info(`🔍 생성된 Entity 조인 쿼리:`, query);
      return {
        query: query,
        aliasMap: aliasMap,
      };
    } catch (error) {
      logger.error("Entity 조인 쿼리 생성 실패", error);
      throw error;
    }
  }

  /**
   * 조인 전략 결정 (테이블 크기 기반)
   */
  async determineJoinStrategy(
    joinConfigs: EntityJoinConfig[]
  ): Promise<"full_join" | "cache_lookup" | "hybrid"> {
    try {
      const strategies = await Promise.all(
        joinConfigs.map(async (config) => {
          // 여러 컬럼을 조합하는 경우 캐시 전략 사용 불가
          if (config.displayColumns && config.displayColumns.length > 1) {
            console.log(
              `🎯 여러 컬럼 조합으로 인해 조인 전략 사용: ${config.sourceColumn}`,
              config.displayColumns
            );
            return "join";
          }

          // category_values는 특수 조인 조건이 필요하므로 캐시 불가
          if (config.referenceTable === "category_values") {
            logger.info(
              `🎯 category_values는 캐시 전략 불가: ${config.sourceColumn}`
            );
            return "join";
          }

          // 참조 테이블의 캐시 가능성 확인
          const displayCol =
            config.displayColumn ||
            config.displayColumns?.[0] ||
            config.referenceColumn;
          logger.info(
            `🔍 캐시 확인용 표시 컬럼: ${config.referenceTable} - ${displayCol}`
          );

          const cachedData = await referenceCacheService.getCachedReference(
            config.referenceTable,
            config.referenceColumn,
            displayCol
          );

          return cachedData ? "cache" : "join";
        })
      );

      // 모두 캐시 가능한 경우
      if (strategies.every((s) => s === "cache")) {
        return "cache_lookup";
      }

      // 혼합인 경우
      if (strategies.includes("cache") && strategies.includes("join")) {
        return "hybrid";
      }

      // 기본은 조인
      return "full_join";
    } catch (error) {
      logger.error("조인 전략 결정 실패", error);
      return "full_join"; // 안전한 기본값
    }
  }

  /**
   * 조인 설정 유효성 검증
   */
  private async validateJoinConfig(config: EntityJoinConfig): Promise<boolean> {
    try {
      logger.info("🔍 조인 설정 검증 상세:", {
        sourceColumn: config.sourceColumn,
        referenceTable: config.referenceTable,
        referenceColumn: config.referenceColumn,
        displayColumns: config.displayColumns,
        displayColumn: config.displayColumn,
        aliasColumn: config.aliasColumn,
      });

      // 참조 테이블 존재 확인
      const tableExists = await query<{ exists: number }>(
        `SELECT 1 as exists FROM information_schema.tables 
         WHERE table_name = $1
         LIMIT 1`,
        [config.referenceTable]
      );

      if (tableExists.length === 0) {
        logger.warn(`참조 테이블이 존재하지 않음: ${config.referenceTable}`);
        return false;
      }

      // 참조 컬럼(JOIN 키) 존재 확인 - 참조 테이블에 reference_column이 실제로 있는지 검증
      if (config.referenceColumn) {
        const refColExists = await query<{ exists: number }>(
          `SELECT 1 as exists FROM information_schema.columns 
           WHERE table_name = $1 
           AND column_name = $2
           LIMIT 1`,
          [config.referenceTable, config.referenceColumn]
        );

        if (refColExists.length === 0) {
          // reference_column이 없으면 'id' 컬럼으로 자동 대체 시도
          const idColExists = await query<{ exists: number }>(
            `SELECT 1 as exists FROM information_schema.columns 
             WHERE table_name = $1 
             AND column_name = 'id'
             LIMIT 1`,
            [config.referenceTable]
          );

          if (idColExists.length > 0) {
            logger.warn(
              `⚠️ 참조 컬럼 ${config.referenceTable}.${config.referenceColumn}이 존재하지 않음 → 'id'로 자동 대체`
            );
            config.referenceColumn = "id";
          } else {
            logger.warn(
              `❌ 참조 컬럼 ${config.referenceTable}.${config.referenceColumn}이 존재하지 않고 'id' 컬럼도 없음 → 스킵`
            );
            return false;
          }
        } else {
          logger.info(
            `✅ 참조 컬럼 확인 완료: ${config.referenceTable}.${config.referenceColumn}`
          );
        }
      }

      // 표시 컬럼 존재 확인 (displayColumns[0] 사용)
      const displayColumn = config.displayColumns?.[0] || config.displayColumn;
      logger.info(
        `🔍 표시 컬럼 확인: ${displayColumn} (from displayColumns: ${config.displayColumns}, displayColumn: ${config.displayColumn})`
      );

      // 🚨 display_column이 항상 "none"이므로, 표시 컬럼이 없어도 조인 허용
      if (displayColumn && displayColumn !== "none") {
        const columnExists = await query<{ exists: number }>(
          `SELECT 1 as exists FROM information_schema.columns 
           WHERE table_name = $1 
           AND column_name = $2
           LIMIT 1`,
          [config.referenceTable, displayColumn]
        );

        if (columnExists.length === 0) {
          logger.warn(
            `표시 컬럼이 존재하지 않음: ${config.referenceTable}.${displayColumn}`
          );
          return false;
        }
        logger.info(
          `✅ 표시 컬럼 확인 완료: ${config.referenceTable}.${displayColumn}`
        );
      } else {
        logger.info(
          `🔧 표시 컬럼 검증 생략: display_column이 none이거나 설정되지 않음`
        );
      }

      return true;
    } catch (error) {
      logger.error("조인 설정 검증 실패", error);
      return false;
    }
  }

  /**
   * 카운트 쿼리 생성 (페이징용)
   */
  buildCountQuery(
    tableName: string,
    joinConfigs: EntityJoinConfig[],
    whereClause: string = ""
  ): string {
    try {
      // 별칭 매핑 생성 (buildJoinQuery와 동일한 로직)
      const aliasMap = new Map<string, string>();
      const usedAliases = new Set<string>();

      // joinConfigs를 참조 테이블 + 소스 컬럼별로 중복 제거하여 별칭 생성
      const uniqueReferenceTableConfigs = joinConfigs.reduce((acc, config) => {
        if (
          !acc.some(
            (existingConfig) =>
              existingConfig.referenceTable === config.referenceTable &&
              existingConfig.sourceColumn === config.sourceColumn
          )
        ) {
          acc.push(config);
        }
        return acc;
      }, [] as EntityJoinConfig[]);

      uniqueReferenceTableConfigs.forEach((config) => {
        let baseAlias = config.referenceTable.substring(0, 3);
        let alias = baseAlias;
        let counter = 1;

        while (usedAliases.has(alias)) {
          alias = `${baseAlias}${counter}`;
          counter++;
        }
        usedAliases.add(alias);
        const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
        aliasMap.set(aliasKey, alias);
      });

      // JOIN 절들 (COUNT에서는 SELECT 컬럼 불필요)
      const joinClauses = uniqueReferenceTableConfigs
        .map((config) => {
          const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
          const alias = aliasMap.get(aliasKey);

          // category_values는 특별한 조인 조건 필요 (회사별 필터링만)
          // is_active 필터 제거: 비활성화된 카테고리도 라벨로 표시되어야 함
          if (config.referenceTable === "category_values") {
            return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}' AND ${alias}.company_code = main.company_code`;
          }

          return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT`;
        })
        .join("\n");

      // WHERE 절
      const whereSQL = whereClause ? `WHERE ${whereClause}` : "";

      // COUNT 쿼리 조합
      const query = [
        `SELECT COUNT(*) as total`,
        `FROM ${tableName} main`,
        joinClauses,
        whereSQL,
      ]
        .filter(Boolean)
        .join("\n");

      return query;
    } catch (error) {
      logger.error("COUNT 쿼리 생성 실패", error);
      throw error;
    }
  }

  /**
   * 참조 테이블의 컬럼 목록 조회 (UI용)
   */
  async getReferenceTableColumns(tableName: string, companyCode?: string): Promise<
    Array<{
      columnName: string;
      displayName: string;
      dataType: string;
      inputType?: string;
    }>
  > {
    try {
      // 1. 테이블의 기본 컬럼 정보 조회 (모든 데이터 타입 포함)
      const columns = 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'
        ORDER BY ordinal_position`,
        [tableName]
      );

      // 2. table_type_columns 테이블에서 라벨과 input_type 정보 조회
      // 회사코드가 있으면 해당 회사 + '*' 만, 회사별 우선
      const columnLabels = await query<{
        column_name: string;
        column_label: string | null;
        input_type: string | null;
      }>(
        `SELECT DISTINCT ON (column_name) column_name, column_label, input_type
         FROM table_type_columns
         WHERE table_name = $1
         ${companyCode ? `AND company_code IN ($2, '*')` : ''}
         ORDER BY column_name, 
           CASE WHEN company_code = '*' THEN 1 ELSE 0 END`,
        companyCode ? [tableName, companyCode] : [tableName]
      );

      // 3. 라벨 및 inputType 정보를 맵으로 변환
      const labelMap = new Map<string, { label: string; inputType: string }>();
      columnLabels.forEach((col) => {
        if (col.column_name) {
          labelMap.set(col.column_name, {
            label: col.column_label || col.column_name,
            inputType: col.input_type || "text",
          });
        }
      });

      // 4. 컬럼 정보와 라벨/inputType 정보 결합
      return columns.map((col) => {
        const labelInfo = labelMap.get(col.column_name);
        return {
        columnName: col.column_name,
          displayName: labelInfo?.label || col.column_name,
        dataType: col.data_type,
          inputType: labelInfo?.inputType || "text",
        };
      });
    } catch (error) {
      logger.error(`참조 테이블 컬럼 조회 실패: ${tableName}`, error);
      return [];
    }
  }

  /**
   * 콤마 구분 다중값 해결 (겸직 부서 등)
   * entity join이 NULL인데 소스값에 콤마가 있으면 개별 코드를 각각 조회해서 라벨로 변환
   */
  async resolveCommaValues(
    data: Record<string, any>[],
    joinConfigs: EntityJoinConfig[]
  ): Promise<Record<string, any>[]> {
    if (!data.length || !joinConfigs.length) return data;

    for (const config of joinConfigs) {
      const sourceCol = config.sourceColumn;
      const displayCol = config.displayColumns?.[0] || config.displayColumn;
      if (!displayCol || displayCol === "none") continue;

      const aliasCol = config.aliasColumn || `${sourceCol}_${displayCol}`;
      const labelCol = `${sourceCol}_label`;

      const codesSet = new Set<string>();
      const rowsToResolve: number[] = [];

      data.forEach((row, idx) => {
        const srcVal = row[sourceCol];
        if (!srcVal || typeof srcVal !== "string" || !srcVal.includes(",")) return;

        const joinedVal = row[aliasCol] || row[labelCol];
        if (joinedVal && joinedVal !== "") return;

        rowsToResolve.push(idx);
        srcVal.split(",").map((v: string) => v.trim()).filter(Boolean).forEach((code: string) => codesSet.add(code));
      });

      if (codesSet.size === 0) continue;

      const codes = Array.from(codesSet);
      const refCol = config.referenceColumn || "id";
      const placeholders = codes.map((_, i) => `$${i + 1}`).join(",");
      try {
        const result = await query<Record<string, any>>(
          `SELECT "${refCol}"::TEXT as _key, "${displayCol}"::TEXT as _label 
           FROM ${config.referenceTable} 
           WHERE "${refCol}"::TEXT IN (${placeholders})`,
          codes
        );

        const labelMap = new Map<string, string>();
        result.forEach((r) => labelMap.set(r._key, r._label));

        for (const idx of rowsToResolve) {
          const srcVal = data[idx][sourceCol] as string;
          const resolvedLabels = srcVal
            .split(",")
            .map((v: string) => v.trim())
            .filter(Boolean)
            .map((code: string) => labelMap.get(code) || code)
            .join(", ");

          data[idx][aliasCol] = resolvedLabels;
          data[idx][labelCol] = resolvedLabels;
        }

        logger.info(`콤마 구분 entity 값 해결: ${sourceCol} → ${codesSet.size}개 코드, ${rowsToResolve.length}개 행`);
      } catch (err) {
        logger.warn(`콤마 구분 entity 값 해결 실패: ${sourceCol}`, err);
      }
    }

    return data;
  }
}

export const entityJoinService = new EntityJoinService();
