/**
 * 마스터-디테일 엑셀 처리 서비스
 * 
 * 분할 패널 화면의 마스터-디테일 구조를 자동 감지하고
 * 엑셀 다운로드/업로드 시 JOIN 및 그룹화 처리를 수행합니다.
 */

import { query, queryOne, transaction, getPool } from "../database/db";
import { logger } from "../utils/logger";

// ================================
// 인터페이스 정의
// ================================

/**
 * 마스터-디테일 관계 정보
 */
export interface MasterDetailRelation {
  masterTable: string;
  detailTable: string;
  masterKeyColumn: string;  // 마스터 테이블의 키 컬럼 (예: order_no)
  detailFkColumn: string;   // 디테일 테이블의 FK 컬럼 (예: order_no)
  masterColumns: ColumnInfo[];
  detailColumns: ColumnInfo[];
}

/**
 * 컬럼 정보
 */
export interface ColumnInfo {
  name: string;
  label: string;
  inputType: string;
  isFromMaster: boolean;
}

/**
 * 분할 패널 설정
 */
export interface SplitPanelConfig {
  leftPanel: {
    tableName: string;
    columns: Array<{ name: string; label: string; width?: number }>;
  };
  rightPanel: {
    tableName: string;
    columns: Array<{ name: string; label: string; width?: number }>;
    relation?: {
      type: string;
      foreignKey?: string;
      leftColumn?: string;
      // 복합키 지원 (새로운 방식)
      keys?: Array<{
        leftColumn: string;
        rightColumn: string;
      }>;
    };
  };
}

/**
 * 엑셀 다운로드 결과
 */
export interface ExcelDownloadData {
  headers: string[];       // 컬럼 라벨들
  columns: string[];       // 컬럼명들
  data: Record<string, any>[];
  masterColumns: string[]; // 마스터 컬럼 목록
  detailColumns: string[]; // 디테일 컬럼 목록
  joinKey: string;         // 조인 키
}

/**
 * 엑셀 업로드 결과
 */
export interface ExcelUploadResult {
  success: boolean;
  masterInserted: number;
  masterUpdated: number;
  detailInserted: number;
  detailUpdated: number;
  detailDeleted: number;
  errors: string[];
}

// ================================
// 서비스 클래스
// ================================

class MasterDetailExcelService {
  
  /**
   * 화면 ID로 분할 패널 설정 조회
   */
  async getSplitPanelConfig(screenId: number): Promise<SplitPanelConfig | null> {
    try {
      logger.info(`분할 패널 설정 조회: screenId=${screenId}`);

      // screen_layouts에서 split-panel-layout 컴포넌트 찾기
      const result = await queryOne<any>(
        `SELECT properties->>'componentConfig' as config
         FROM screen_layouts
         WHERE screen_id = $1
           AND component_type = 'component'
           AND properties->>'componentType' = 'split-panel-layout'
         LIMIT 1`,
        [screenId]
      );

      if (!result || !result.config) {
        logger.info(`분할 패널 없음: screenId=${screenId}`);
        return null;
      }

      const config = typeof result.config === "string" 
        ? JSON.parse(result.config) 
        : result.config;

      logger.info(`분할 패널 설정 발견:`, {
        leftTable: config.leftPanel?.tableName,
        rightTable: config.rightPanel?.tableName,
        relation: config.rightPanel?.relation,
      });

      return {
        leftPanel: config.leftPanel,
        rightPanel: config.rightPanel,
      };
    } catch (error: any) {
      logger.error(`분할 패널 설정 조회 실패: ${error.message}`);
      return null;
    }
  }

  /**
   * table_type_columns에서 Entity 관계 정보 조회
   * 디테일 테이블에서 마스터 테이블을 참조하는 컬럼 찾기
   */
  async getEntityRelation(
    detailTable: string,
    masterTable: string
  ): Promise<{ detailFkColumn: string; masterKeyColumn: string } | null> {
    try {
      logger.info(`Entity 관계 조회: ${detailTable} -> ${masterTable}`);

      const result = await queryOne<any>(
        `SELECT column_name, reference_column
         FROM table_type_columns
         WHERE table_name = $1
           AND input_type = 'entity'
           AND reference_table = $2
           AND company_code = '*'
         LIMIT 1`,
        [detailTable, masterTable]
      );

      if (!result) {
        logger.warn(`Entity 관계 없음: ${detailTable} -> ${masterTable}`);
        return null;
      }

      logger.info(`Entity 관계 발견: ${detailTable}.${result.column_name} -> ${masterTable}.${result.reference_column}`);

      return {
        detailFkColumn: result.column_name,
        masterKeyColumn: result.reference_column,
      };
    } catch (error: any) {
      logger.error(`Entity 관계 조회 실패: ${error.message}`);
      return null;
    }
  }

  /**
   * 테이블의 컬럼 라벨 정보 조회
   */
  async getColumnLabels(tableName: string): Promise<Map<string, string>> {
    try {
      const result = await query<any>(
        `SELECT column_name, column_label
         FROM table_type_columns
         WHERE table_name = $1 AND company_code = '*'`,
        [tableName]
      );

      const labelMap = new Map<string, string>();
      for (const row of result) {
        labelMap.set(row.column_name, row.column_label || row.column_name);
      }

      return labelMap;
    } catch (error: any) {
      logger.error(`컬럼 라벨 조회 실패: ${error.message}`);
      return new Map();
    }
  }

  /**
   * 마스터-디테일 관계 정보 조합
   */
  async getMasterDetailRelation(
    screenId: number
  ): Promise<MasterDetailRelation | null> {
    try {
      // 1. 분할 패널 설정 조회
      const splitPanel = await this.getSplitPanelConfig(screenId);
      if (!splitPanel) {
        return null;
      }

      const masterTable = splitPanel.leftPanel.tableName;
      const detailTable = splitPanel.rightPanel.tableName;

      if (!masterTable || !detailTable) {
        logger.warn("마스터 또는 디테일 테이블명 없음");
        return null;
      }

      // 2. 분할 패널의 relation 정보가 있으면 우선 사용
      // 🔥 keys 배열을 우선 사용 (새로운 복합키 지원 방식)
      let masterKeyColumn: string | undefined;
      let detailFkColumn: string | undefined;
      
      const relationKeys = splitPanel.rightPanel.relation?.keys;
      if (relationKeys && relationKeys.length > 0) {
        // keys 배열에서 첫 번째 키 사용
        masterKeyColumn = relationKeys[0].leftColumn;
        detailFkColumn = relationKeys[0].rightColumn;
        logger.info(`keys 배열에서 관계 정보 사용: ${masterKeyColumn} -> ${detailFkColumn}`);
      } else {
        // 하위 호환성: 기존 leftColumn/foreignKey 사용
        masterKeyColumn = splitPanel.rightPanel.relation?.leftColumn;
        detailFkColumn = splitPanel.rightPanel.relation?.foreignKey;
      }

      // 3. relation 정보가 없으면 table_type_columns에서 Entity 관계 조회
      if (!masterKeyColumn || !detailFkColumn) {
        const entityRelation = await this.getEntityRelation(detailTable, masterTable);
        if (entityRelation) {
          masterKeyColumn = entityRelation.masterKeyColumn;
          detailFkColumn = entityRelation.detailFkColumn;
        }
      }

      if (!masterKeyColumn || !detailFkColumn) {
        logger.warn("조인 키 정보를 찾을 수 없음");
        return null;
      }

      // 4. 컬럼 라벨 정보 조회
      const masterLabels = await this.getColumnLabels(masterTable);
      const detailLabels = await this.getColumnLabels(detailTable);

      // 5. 마스터 컬럼 정보 구성
      const masterColumns: ColumnInfo[] = splitPanel.leftPanel.columns.map(col => ({
        name: col.name,
        label: masterLabels.get(col.name) || col.label || col.name,
        inputType: "text",
        isFromMaster: true,
      }));

      // 6. 디테일 컬럼 정보 구성 (FK 컬럼 제외)
      const detailColumns: ColumnInfo[] = splitPanel.rightPanel.columns
        .filter(col => col.name !== detailFkColumn) // FK 컬럼 제외
        .map(col => ({
          name: col.name,
          label: detailLabels.get(col.name) || col.label || col.name,
          inputType: "text",
          isFromMaster: false,
        }));

      logger.info(`마스터-디테일 관계 구성 완료:`, {
        masterTable,
        detailTable,
        masterKeyColumn,
        detailFkColumn,
        masterColumnCount: masterColumns.length,
        detailColumnCount: detailColumns.length,
      });

      return {
        masterTable,
        detailTable,
        masterKeyColumn,
        detailFkColumn,
        masterColumns,
        detailColumns,
      };
    } catch (error: any) {
      logger.error(`마스터-디테일 관계 조회 실패: ${error.message}`);
      return null;
    }
  }

  /**
   * 마스터-디테일 JOIN 데이터 조회 (엑셀 다운로드용)
   */
  async getJoinedData(
    relation: MasterDetailRelation,
    companyCode: string,
    filters?: Record<string, any>
  ): Promise<ExcelDownloadData> {
    try {
      const { masterTable, detailTable, masterKeyColumn, detailFkColumn, masterColumns, detailColumns } = relation;

      // 조인 컬럼과 일반 컬럼 분리
      // 조인 컬럼 형식: "테이블명.컬럼명" (예: customer_mng.customer_name)
      const entityJoins: Array<{
        refTable: string;
        refColumn: string;
        sourceColumn: string;
        alias: string;
        displayColumn: string;
        tableAlias: string; // "m" (마스터) 또는 "d" (디테일) - JOIN 시 소스 테이블 구분
      }> = [];

      // SELECT 절 구성
      const selectParts: string[] = [];
      let aliasIndex = 0;

      // 마스터 컬럼 처리
      for (const col of masterColumns) {
        if (col.name.includes(".")) {
          // 조인 컬럼: 테이블명.컬럼명
          const [refTable, displayColumn] = col.name.split(".");
          const alias = `ej${aliasIndex++}`;
          
          // table_type_columns에서 FK 컬럼 찾기
          const fkColumn = await this.findForeignKeyColumn(masterTable, refTable);
          if (fkColumn) {
            entityJoins.push({
              refTable,
              refColumn: fkColumn.referenceColumn,
              sourceColumn: fkColumn.sourceColumn,
              alias,
              displayColumn,
              tableAlias: "m", // 마스터 테이블에서 조인
            });
            selectParts.push(`${alias}."${displayColumn}" AS "${col.name}"`);
          } else {
            // FK를 못 찾으면 NULL로 처리
            selectParts.push(`NULL AS "${col.name}"`);
          }
        } else {
          // 일반 컬럼
          selectParts.push(`m."${col.name}"`);
        }
      }

      // 디테일 컬럼 처리
      for (const col of detailColumns) {
        if (col.name.includes(".")) {
          // 조인 컬럼: 테이블명.컬럼명
          const [refTable, displayColumn] = col.name.split(".");
          const alias = `ej${aliasIndex++}`;
          
          // table_type_columns에서 FK 컬럼 찾기
          const fkColumn = await this.findForeignKeyColumn(detailTable, refTable);
          if (fkColumn) {
            entityJoins.push({
              refTable,
              refColumn: fkColumn.referenceColumn,
              sourceColumn: fkColumn.sourceColumn,
              alias,
              displayColumn,
              tableAlias: "d", // 디테일 테이블에서 조인
            });
            selectParts.push(`${alias}."${displayColumn}" AS "${col.name}"`);
          } else {
            selectParts.push(`NULL AS "${col.name}"`);
          }
        } else {
          // 일반 컬럼
          selectParts.push(`d."${col.name}"`);
        }
      }

      const selectClause = selectParts.join(", ");

      // 엔티티 조인 절 구성 (마스터/디테일 테이블 alias 구분)
      const entityJoinClauses = entityJoins.map(ej => 
        `LEFT JOIN "${ej.refTable}" ${ej.alias} ON ${ej.tableAlias}."${ej.sourceColumn}" = ${ej.alias}."${ej.refColumn}"`
      ).join("\n        ");

      // WHERE 절 구성
      const whereConditions: string[] = [];
      const params: any[] = [];
      let paramIndex = 1;

      // 회사 코드 필터 (최고 관리자 제외)
      if (companyCode && companyCode !== "*") {
        whereConditions.push(`m.company_code = $${paramIndex}`);
        params.push(companyCode);
        paramIndex++;
      }

      // 추가 필터 적용
      if (filters) {
        for (const [key, value] of Object.entries(filters)) {
          if (value !== undefined && value !== null && value !== "") {
            // 조인 컬럼인지 확인
            if (key.includes(".")) continue;
            // 마스터 테이블 컬럼인지 확인
            const isMasterCol = masterColumns.some(c => c.name === key);
            const tableAlias = isMasterCol ? "m" : "d";
            whereConditions.push(`${tableAlias}."${key}" = $${paramIndex}`);
            params.push(value);
            paramIndex++;
          }
        }
      }

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

      // 디테일 테이블의 id 컬럼 존재 여부 확인 (user_info 등 id가 없는 테이블 대응)
      const detailIdCheck = await queryOne<{ exists: boolean }>(
        `SELECT EXISTS (
          SELECT 1 FROM information_schema.columns 
          WHERE table_name = $1 AND column_name = 'id'
        ) as exists`,
        [detailTable]
      );
      const detailOrderColumn = detailIdCheck?.exists ? `d."id"` : `d."${detailFkColumn}"`;

      // JOIN 쿼리 실행
      const sql = `
        SELECT ${selectClause}
        FROM "${masterTable}" m
        LEFT JOIN "${detailTable}" d 
          ON m."${masterKeyColumn}" = d."${detailFkColumn}"
          AND m.company_code = d.company_code
        ${entityJoinClauses}
        ${whereClause}
        ORDER BY m."${masterKeyColumn}", ${detailOrderColumn}
      `;

      logger.info(`마스터-디테일 JOIN 쿼리:`, { sql, params });

      const data = await query<any>(sql, params);

      // 헤더 및 컬럼 정보 구성
      const headers = [...masterColumns.map(c => c.label), ...detailColumns.map(c => c.label)];
      const columns = [...masterColumns.map(c => c.name), ...detailColumns.map(c => c.name)];

      logger.info(`마스터-디테일 데이터 조회 완료: ${data.length}행`);

      return {
        headers,
        columns,
        data,
        masterColumns: masterColumns.map(c => c.name),
        detailColumns: detailColumns.map(c => c.name),
        joinKey: masterKeyColumn,
      };
    } catch (error: any) {
      logger.error(`마스터-디테일 데이터 조회 실패: ${error.message}`);
      throw error;
    }
  }

  /**
   * 특정 테이블에서 참조 테이블로의 FK 컬럼 찾기
   */
  private async findForeignKeyColumn(
    sourceTable: string,
    referenceTable: string
  ): Promise<{ sourceColumn: string; referenceColumn: string } | null> {
    try {
      const result = await query<{ column_name: string; reference_column: string }>(
        `SELECT column_name, reference_column 
         FROM table_type_columns 
         WHERE table_name = $1 
         AND reference_table = $2 
         AND input_type = 'entity'
         AND company_code = '*'
         LIMIT 1`,
        [sourceTable, referenceTable]
      );

      if (result.length > 0) {
        return {
          sourceColumn: result[0].column_name,
          referenceColumn: result[0].reference_column,
        };
      }
      return null;
    } catch (error) {
      logger.error(`FK 컬럼 조회 실패: ${sourceTable} -> ${referenceTable}`, error);
      return null;
    }
  }

  /**
   * 특정 테이블의 특정 컬럼이 채번 타입인지 확인하고, 채번 규칙 ID를 반환
   * numbering_rules 테이블에서 table_name + column_name + company_code로 직접 조회
   */
  private async detectNumberingRuleForColumn(
    tableName: string,
    columnName: string,
    companyCode?: string
  ): Promise<{ numberingRuleId: string } | null> {
    try {
      // 1. table_type_columns에서 numbering 타입인지 확인
      const companyCondition = companyCode && companyCode !== "*"
        ? `AND company_code IN ($3, '*')`
        : `AND company_code = '*'`;
      const ttcParams = companyCode && companyCode !== "*"
        ? [tableName, columnName, companyCode]
        : [tableName, columnName];

      const ttcResult = await query<any>(
        `SELECT input_type FROM table_type_columns
         WHERE table_name = $1 AND column_name = $2 ${companyCondition}
         AND input_type = 'numbering' LIMIT 1`,
        ttcParams
      );

      if (ttcResult.length === 0) return null;

      // 2. numbering_rules에서 table_name + column_name으로 규칙 조회
      const ruleCompanyCondition = companyCode && companyCode !== "*"
        ? `AND company_code IN ($3, '*')`
        : `AND company_code = '*'`;
      const ruleParams = companyCode && companyCode !== "*"
        ? [tableName, columnName, companyCode]
        : [tableName, columnName];

      const ruleResult = await query<any>(
        `SELECT rule_id FROM numbering_rules
         WHERE table_name = $1 AND column_name = $2 ${ruleCompanyCondition}
         ORDER BY CASE WHEN company_code = '*' THEN 1 ELSE 0 END
         LIMIT 1`,
        ruleParams
      );

      if (ruleResult.length > 0) {
        return { numberingRuleId: ruleResult[0].rule_id };
      }

      // 3. fallback: detail_settings.numberingRuleId (하위 호환)
      const fallbackResult = await query<any>(
        `SELECT detail_settings FROM table_type_columns
         WHERE table_name = $1 AND column_name = $2 ${companyCondition}
         AND input_type = 'numbering'
         ORDER BY CASE WHEN company_code = '*' THEN 1 ELSE 0 END`,
        ttcParams
      );

      for (const row of fallbackResult) {
        const settings = typeof row.detail_settings === "string"
          ? JSON.parse(row.detail_settings || "{}")
          : row.detail_settings;
        if (settings?.numberingRuleId) {
          return { numberingRuleId: settings.numberingRuleId };
        }
      }

      return null;
    } catch (error) {
      logger.error(`채번 컬럼 감지 실패: ${tableName}.${columnName}`, error);
      return null;
    }
  }

  /**
   * 특정 테이블의 모든 채번 컬럼을 한 번에 조회
   * numbering_rules 테이블에서 table_name + column_name으로 직접 조회
   * @returns Map<columnName, numberingRuleId>
   */
  private async detectAllNumberingColumns(
    tableName: string,
    companyCode?: string
  ): Promise<Map<string, string>> {
    const numberingCols = new Map<string, string>();
    try {
      // 1. table_type_columns에서 numbering 타입 컬럼 목록 조회
      const companyCondition = companyCode && companyCode !== "*"
        ? `AND company_code IN ($2, '*')`
        : `AND company_code = '*'`;
      const params = companyCode && companyCode !== "*"
        ? [tableName, companyCode]
        : [tableName];

      const ttcResult = await query<any>(
        `SELECT DISTINCT column_name FROM table_type_columns
         WHERE table_name = $1 AND input_type = 'numbering' ${companyCondition}`,
        params
      );

      // 2. 각 컬럼에 대해 numbering_rules에서 규칙 조회
      for (const row of ttcResult) {
        const ruleResult = await query<any>(
          `SELECT rule_id FROM numbering_rules
           WHERE table_name = $1 AND column_name = $2 ${companyCondition}
           ORDER BY CASE WHEN company_code = '*' THEN 1 ELSE 0 END
           LIMIT 1`,
          companyCode && companyCode !== "*"
            ? [tableName, row.column_name, companyCode]
            : [tableName, row.column_name]
        );

        if (ruleResult.length > 0) {
          numberingCols.set(row.column_name, ruleResult[0].rule_id);
        }
      }

      if (numberingCols.size > 0) {
        logger.info(`테이블 ${tableName} 채번 컬럼 감지:`, Object.fromEntries(numberingCols));
      }
    } catch (error) {
      logger.error(`테이블 ${tableName} 채번 컬럼 감지 실패:`, error);
    }
    return numberingCols;
  }

  /**
   * 디테일 테이블의 고유 키 컬럼 감지 (UPSERT 매칭용)
   * PK가 비즈니스 키이면 사용, auto-increment 'id'만이면 유니크 인덱스 탐색
   * @returns 고유 키 컬럼 배열 (빈 배열이면 매칭 불가 → INSERT만 수행)
   */
  private async detectUniqueKeyColumns(
    client: any,
    tableName: string
  ): Promise<string[]> {
    try {
      // 1. PK 컬럼 조회
      const pkResult = await client.query(
        `SELECT array_agg(a.attname ORDER BY x.n) AS columns
         FROM pg_constraint c
         JOIN pg_class t ON t.oid = c.conrelid
         JOIN pg_namespace n ON n.oid = t.relnamespace
         CROSS JOIN LATERAL unnest(c.conkey) WITH ORDINALITY AS x(attnum, n)
         JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = x.attnum
         WHERE n.nspname = 'public' AND t.relname = $1 AND c.contype = 'p'`,
        [tableName]
      );

      if (pkResult.rows.length > 0 && pkResult.rows[0].columns) {
        const pkCols: string[] = typeof pkResult.rows[0].columns === "string"
          ? pkResult.rows[0].columns.replace(/[{}]/g, "").split(",").map((s: string) => s.trim())
          : pkResult.rows[0].columns;
        
        // PK가 'id' 하나만 있으면 auto-increment이므로 사용 불가
        if (!(pkCols.length === 1 && pkCols[0] === "id")) {
          logger.info(`디테일 테이블 ${tableName} 고유 키 (PK): ${pkCols.join(", ")}`);
          return pkCols;
        }
      }

      // 2. PK가 'id'뿐이면 유니크 인덱스 탐색
      const uqResult = await client.query(
        `SELECT array_agg(a.attname ORDER BY x.n) AS columns
         FROM pg_index ix
         JOIN pg_class t ON t.oid = ix.indrelid
         JOIN pg_class i ON i.oid = ix.indexrelid
         JOIN pg_namespace n ON n.oid = t.relnamespace
         CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS x(attnum, n)
         JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = x.attnum
         WHERE n.nspname = 'public' AND t.relname = $1
           AND ix.indisunique = true AND ix.indisprimary = false
         GROUP BY i.relname
         LIMIT 1`,
        [tableName]
      );

      if (uqResult.rows.length > 0 && uqResult.rows[0].columns) {
        const uqCols: string[] = typeof uqResult.rows[0].columns === "string"
          ? uqResult.rows[0].columns.replace(/[{}]/g, "").split(",").map((s: string) => s.trim())
          : uqResult.rows[0].columns;
        logger.info(`디테일 테이블 ${tableName} 고유 키 (UNIQUE INDEX): ${uqCols.join(", ")}`);
        return uqCols;
      }

      logger.info(`디테일 테이블 ${tableName} 고유 키 없음 → INSERT 전용`);
      return [];
    } catch (error) {
      logger.error(`디테일 테이블 ${tableName} 고유 키 감지 실패:`, error);
      return [];
    }
  }

  /**
   * 마스터-디테일 데이터 업로드 (엑셀 업로드용)
   * 
   * 처리 로직:
   * 1. 마스터 키 컬럼이 채번 타입인지 확인
   * 2-A. 채번인 경우: 다른 마스터 컬럼 값으로 그룹화 → 키 자동 생성 → INSERT
   * 2-B. 채번 아닌 경우: 마스터 키 값으로 그룹화 → UPSERT
   * 3. 디테일 데이터 개별 행 UPSERT (고유 키 기반)
   */
  async uploadJoinedData(
    relation: MasterDetailRelation,
    data: Record<string, any>[],
    companyCode: string,
    userId?: string
  ): Promise<ExcelUploadResult> {
    const result: ExcelUploadResult = {
      success: false,
      masterInserted: 0,
      masterUpdated: 0,
      detailInserted: 0,
      detailUpdated: 0,
      detailDeleted: 0,
      errors: [],
    };

    const pool = getPool();
    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      const { masterTable, detailTable, masterKeyColumn, detailFkColumn, masterColumns, detailColumns } = relation;

      // 마스터/디테일 테이블의 실제 컬럼 존재 여부 확인 (writer, created_date 등 하드코딩 방지)
      const masterColsResult = await client.query(
        `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`,
        [masterTable]
      );
      const masterExistingCols = new Set(masterColsResult.rows.map((r: any) => r.column_name));

      const detailColsResult = await client.query(
        `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`,
        [detailTable]
      );
      const detailExistingCols = new Set(detailColsResult.rows.map((r: any) => r.column_name));

      // 마스터 키 컬럼의 채번 규칙 자동 감지 (회사별 설정 우선)
      const numberingInfo = await this.detectNumberingRuleForColumn(masterTable, masterKeyColumn, companyCode);
      const isAutoNumbering = !!numberingInfo;

      logger.info(`마스터 키 채번 감지:`, { 
        masterKeyColumn, 
        isAutoNumbering, 
        numberingRuleId: numberingInfo?.numberingRuleId 
      });

      // 데이터 그룹화
      const groupedData = new Map<string, Record<string, any>[]>();

      if (isAutoNumbering) {
        // 채번 모드: 마스터 키 제외한 다른 마스터 컬럼 값으로 그룹화
        const otherMasterCols = masterColumns.filter(c => c.name !== masterKeyColumn).map(c => c.name);
        
        for (const row of data) {
          // 다른 마스터 컬럼 값들을 조합해 그룹 키 생성
          const groupKey = otherMasterCols.map(col => row[col] ?? "").join("|||");
          if (!groupedData.has(groupKey)) {
            groupedData.set(groupKey, []);
          }
          groupedData.get(groupKey)!.push(row);
        }
        
        logger.info(`채번 모드 그룹화 완료: ${groupedData.size}개 그룹 (기준: ${otherMasterCols.join(", ")})`);
      } else {
        // 일반 모드: 마스터 키 값으로 그룹화
        for (const row of data) {
          const masterKey = row[masterKeyColumn];
          if (!masterKey) {
            result.errors.push(`마스터 키(${masterKeyColumn}) 값이 없는 행이 있습니다.`);
            continue;
          }
          if (!groupedData.has(masterKey)) {
            groupedData.set(masterKey, []);
          }
          groupedData.get(masterKey)!.push(row);
        }
        
        logger.info(`일반 모드 그룹화 완료: ${groupedData.size}개 마스터 그룹`);
      }

      // 디테일 테이블의 채번 컬럼 사전 감지 (1회 쿼리로 모든 채번 컬럼 조회)
      const detailNumberingCols = await this.detectAllNumberingColumns(detailTable, companyCode);
      // 마스터 테이블의 비-키 채번 컬럼도 감지
      const masterNumberingCols = await this.detectAllNumberingColumns(masterTable, companyCode);

      // 디테일 테이블의 고유 키 컬럼 감지 (UPSERT 매칭용)
      // PK가 비즈니스 키인 경우 사용, auto-increment 'id'만 있으면 유니크 인덱스 탐색
      const detailUniqueKeyCols = await this.detectUniqueKeyColumns(client, detailTable);

      // 각 그룹 처리
      for (const [groupKey, rows] of groupedData.entries()) {
        try {
          // 마스터 키 결정 (채번이면 자동 생성, 아니면 그룹 키 자체가 마스터 키)
          let masterKey: string;
          let existingMasterKey: string | null = null;
          
          // 마스터 데이터 추출 (첫 번째 행에서, 키 제외)
          const masterDataWithoutKey: Record<string, any> = {};
          for (const col of masterColumns) {
            if (col.name === masterKeyColumn) continue;
            if (rows[0][col.name] !== undefined) {
              masterDataWithoutKey[col.name] = rows[0][col.name];
            }
          }

          if (isAutoNumbering) {
            // 채번 모드: 동일한 마스터가 이미 DB에 있는지 먼저 확인
            // 마스터 키 제외한 다른 컬럼들로 매칭 (예: dept_name이 같은 부서가 있는지)
            const matchCols = Object.keys(masterDataWithoutKey)
              .filter(k => k !== "company_code" && k !== "writer" && k !== "created_date" && k !== "updated_date" && k !== "id"
                && masterDataWithoutKey[k] !== undefined && masterDataWithoutKey[k] !== null && masterDataWithoutKey[k] !== "");
            
            if (matchCols.length > 0) {
              const whereClause = matchCols.map((col, i) => `"${col}" = $${i + 1}`).join(" AND ");
              const companyIdx = matchCols.length + 1;
              const matchResult = await client.query(
                `SELECT "${masterKeyColumn}" FROM "${masterTable}" WHERE ${whereClause} AND company_code = $${companyIdx} LIMIT 1`,
                [...matchCols.map(k => masterDataWithoutKey[k]), companyCode]
              );
              if (matchResult.rows.length > 0) {
                existingMasterKey = matchResult.rows[0][masterKeyColumn];
                logger.info(`채번 모드: 기존 마스터 발견 → ${masterKeyColumn}=${existingMasterKey} (매칭: ${matchCols.map(c => `${c}=${masterDataWithoutKey[c]}`).join(", ")})`);
              }
            }

            if (existingMasterKey) {
              // 기존 마스터 사용 (UPDATE)
              masterKey = existingMasterKey;
              const updateKeys = matchCols.filter(k => k !== masterKeyColumn);
              if (updateKeys.length > 0) {
                const setClauses = updateKeys.map((k, i) => `"${k}" = $${i + 1}`);
                const setValues = updateKeys.map(k => masterDataWithoutKey[k]);
                const updatedDateClause = masterExistingCols.has("updated_date") ? `, updated_date = NOW()` : "";
                await client.query(
                  `UPDATE "${masterTable}" SET ${setClauses.join(", ")}${updatedDateClause} WHERE "${masterKeyColumn}" = $${setValues.length + 1} AND company_code = $${setValues.length + 2}`,
                  [...setValues, masterKey, companyCode]
                );
              }
              result.masterUpdated++;
            } else {
              // 새 마스터 생성 (채번)
              masterKey = await this.generateNumberWithRule(client, numberingInfo!.numberingRuleId, companyCode);
              logger.info(`채번 생성: ${masterKey}`);
            }
          } else {
            masterKey = groupKey;
          }

          // 마스터 데이터 조립
          const masterData: Record<string, any> = {};
          masterData[masterKeyColumn] = masterKey;
          Object.assign(masterData, masterDataWithoutKey);

          // 회사 코드, 작성자 추가 (테이블에 해당 컬럼이 있을 때만)
          if (masterExistingCols.has("company_code")) {
            masterData.company_code = companyCode;
          }
          if (userId && masterExistingCols.has("writer")) {
            masterData.writer = userId;
          }

          // 마스터 비-키 채번 컬럼 자동 생성 (매핑되지 않은 경우)
          for (const [colName, ruleId] of masterNumberingCols) {
            if (colName === masterKeyColumn) continue;
            if (!masterData[colName] || masterData[colName] === "") {
              const generatedValue = await this.generateNumberWithRule(client, ruleId, companyCode);
              masterData[colName] = generatedValue;
              logger.info(`마스터 채번 생성: ${masterTable}.${colName} = ${generatedValue}`);
            }
          }

          // INSERT SQL 생성 헬퍼 (created_date 존재 시만 추가)
          const buildInsertSQL = (table: string, data: Record<string, any>, existingCols: Set<string>) => {
            const cols = Object.keys(data);
            const hasCreatedDate = existingCols.has("created_date");
            const colList = hasCreatedDate ? [...cols, "created_date"] : cols;
            const placeholders = cols.map((_, i) => `$${i + 1}`);
            const valList = hasCreatedDate ? [...placeholders, "NOW()"] : placeholders;
            const values = cols.map(k => data[k]);
            return {
              sql: `INSERT INTO "${table}" (${colList.map(c => `"${c}"`).join(", ")}) VALUES (${valList.join(", ")})`,
              values,
            };
          };

          if (isAutoNumbering && !existingMasterKey) {
            // 채번 모드 + 새 마스터: INSERT
            const { sql, values } = buildInsertSQL(masterTable, masterData, masterExistingCols);
            await client.query(sql, values);
            result.masterInserted++;
          } else if (!isAutoNumbering) {
            // 일반 모드: UPSERT (있으면 UPDATE, 없으면 INSERT)
            const existingMaster = await client.query(
              `SELECT 1 FROM "${masterTable}" WHERE "${masterKeyColumn}" = $1 AND company_code = $2`,
              [masterKey, companyCode]
            );

            if (existingMaster.rows.length > 0) {
              const updateCols = Object.keys(masterData)
                .filter(k => k !== masterKeyColumn && k !== "id")
                .map((k, i) => `"${k}" = $${i + 1}`);
              const updateValues = Object.keys(masterData)
                .filter(k => k !== masterKeyColumn && k !== "id")
                .map(k => masterData[k]);
              
              if (updateCols.length > 0) {
                const updatedDateClause = masterExistingCols.has("updated_date") ? `, updated_date = NOW()` : "";
                await client.query(
                  `UPDATE "${masterTable}" 
                   SET ${updateCols.join(", ")}${updatedDateClause}
                   WHERE "${masterKeyColumn}" = $${updateValues.length + 1} AND company_code = $${updateValues.length + 2}`,
                  [...updateValues, masterKey, companyCode]
                );
              }
              result.masterUpdated++;
            } else {
              const { sql, values } = buildInsertSQL(masterTable, masterData, masterExistingCols);
              await client.query(sql, values);
              result.masterInserted++;
            }

          }

          // 디테일 개별 행 UPSERT 처리
          for (const row of rows) {
            const detailData: Record<string, any> = {};
            
            // FK 컬럼에 마스터 키 주입
            detailData[detailFkColumn] = masterKey;
            if (detailExistingCols.has("company_code")) {
              detailData.company_code = companyCode;
            }
            if (userId && detailExistingCols.has("writer")) {
              detailData.writer = userId;
            }

            // 디테일 컬럼 데이터 추출 (분할 패널 설정 컬럼 기준)
            for (const col of detailColumns) {
              if (row[col.name] !== undefined) {
                detailData[col.name] = row[col.name];
              }
            }

            // 분할 패널에 없지만 엑셀에서 매핑된 디테일 컬럼도 포함
            // (user_id 등 화면에 표시되지 않지만 NOT NULL인 컬럼 처리)
            const detailColNames = new Set(detailColumns.map(c => c.name));
            const skipCols = new Set([
              detailFkColumn, masterKeyColumn,
              "company_code", "writer", "created_date", "updated_date", "id",
            ]);
            for (const key of Object.keys(row)) {
              if (!detailColNames.has(key) && !skipCols.has(key) && detailExistingCols.has(key) && row[key] !== undefined && row[key] !== null && row[key] !== "") {
                const isMasterCol = masterColumns.some(mc => mc.name === key);
                if (!isMasterCol) {
                  detailData[key] = row[key];
                }
              }
            }

            // 디테일 채번 컬럼 자동 생성 (매핑되지 않은 채번 컬럼에 값 주입)
            for (const [colName, ruleId] of detailNumberingCols) {
              if (!detailData[colName] || detailData[colName] === "") {
                const generatedValue = await this.generateNumberWithRule(client, ruleId, companyCode);
                detailData[colName] = generatedValue;
                logger.info(`디테일 채번 생성: ${detailTable}.${colName} = ${generatedValue}`);
              }
            }

            // 고유 키 기반 UPSERT: 존재하면 UPDATE, 없으면 INSERT
            const hasUniqueKey = detailUniqueKeyCols.length > 0;
            const uniqueKeyValues = hasUniqueKey
              ? detailUniqueKeyCols.map(col => detailData[col])
              : [];
            // 고유 키 값이 모두 있어야 매칭 가능 (채번으로 생성된 값도 포함)
            const canMatch = hasUniqueKey && uniqueKeyValues.every(v => v !== undefined && v !== null && v !== "");

            if (canMatch) {
              // 기존 행 존재 여부 확인
              const whereClause = detailUniqueKeyCols
                .map((col, i) => `"${col}" = $${i + 1}`)
                .join(" AND ");
              const companyParam = detailExistingCols.has("company_code")
                ? ` AND company_code = $${detailUniqueKeyCols.length + 1}`
                : "";
              const checkParams = detailExistingCols.has("company_code")
                ? [...uniqueKeyValues, companyCode]
                : uniqueKeyValues;

              const existingRow = await client.query(
                `SELECT 1 FROM "${detailTable}" WHERE ${whereClause}${companyParam} LIMIT 1`,
                checkParams
              );

              if (existingRow.rows.length > 0) {
                // UPDATE: 고유 키와 시스템 컬럼 제외한 나머지 업데이트
                const updateExclude = new Set([
                  ...detailUniqueKeyCols, "id", "company_code", "created_date",
                ]);
                const updateKeys = Object.keys(detailData).filter(k => !updateExclude.has(k));
                
                if (updateKeys.length > 0) {
                  const setClauses = updateKeys.map((k, i) => `"${k}" = $${i + 1}`);
                  const setValues = updateKeys.map(k => detailData[k]);
                  const updatedDateClause = detailExistingCols.has("updated_date") ? `, updated_date = NOW()` : "";
                  
                  const whereParams = detailUniqueKeyCols.map((col, i) => `"${col}" = $${setValues.length + i + 1}`);
                  const companyWhere = detailExistingCols.has("company_code")
                    ? ` AND company_code = $${setValues.length + detailUniqueKeyCols.length + 1}`
                    : "";
                  const allValues = [
                    ...setValues,
                    ...uniqueKeyValues,
                    ...(detailExistingCols.has("company_code") ? [companyCode] : []),
                  ];

                  await client.query(
                    `UPDATE "${detailTable}" SET ${setClauses.join(", ")}${updatedDateClause} WHERE ${whereParams.join(" AND ")}${companyWhere}`,
                    allValues
                  );
                  result.detailUpdated = (result.detailUpdated || 0) + 1;
                  logger.info(`디테일 UPDATE: ${detailUniqueKeyCols.map((c, i) => `${c}=${uniqueKeyValues[i]}`).join(", ")}`);
                }
              } else {
                // INSERT: 새로운 행
                const { sql, values } = buildInsertSQL(detailTable, detailData, detailExistingCols);
                await client.query(sql, values);
                result.detailInserted++;
                logger.info(`디테일 INSERT: ${detailUniqueKeyCols.map((c, i) => `${c}=${uniqueKeyValues[i]}`).join(", ")}`);
              }
            } else {
              // 고유 키가 없거나 값이 없으면 INSERT 전용
              const { sql, values } = buildInsertSQL(detailTable, detailData, detailExistingCols);
              await client.query(sql, values);
              result.detailInserted++;
            }
          }
        } catch (error: any) {
          result.errors.push(`그룹 처리 실패: ${error.message}`);
          logger.error(`그룹 처리 실패:`, error);
        }
      }

      await client.query("COMMIT");
      result.success = result.errors.length === 0 || result.masterInserted + result.masterUpdated > 0;

      logger.info(`마스터-디테일 업로드 완료:`, {
        masterInserted: result.masterInserted,
        masterUpdated: result.masterUpdated,
        detailInserted: result.detailInserted,
        detailUpdated: result.detailUpdated,
        errors: result.errors.length,
      });

    } catch (error: any) {
      await client.query("ROLLBACK");
      result.errors.push(`트랜잭션 실패: ${error.message}`);
      logger.error(`마스터-디테일 업로드 트랜잭션 실패:`, error);
    } finally {
      client.release();
    }

    return result;
  }

  /**
   * 마스터-디테일 간단 모드 업로드
   * 
   * 마스터 정보는 UI에서 선택하고, 엑셀은 디테일 데이터만 포함
   * 채번 규칙을 통해 마스터 키 자동 생성
   * 
   * @param screenId 화면 ID
   * @param detailData 디테일 데이터 배열
   * @param masterFieldValues UI에서 선택한 마스터 필드 값
   * @param numberingRuleId 채번 규칙 ID (optional)
   * @param companyCode 회사 코드
   * @param userId 사용자 ID
   * @param afterUploadFlowId 업로드 후 실행할 노드 플로우 ID (optional, 하위 호환성)
   * @param afterUploadFlows 업로드 후 실행할 노드 플로우 배열 (optional)
   */
  async uploadSimple(
    screenId: number,
    detailData: Record<string, any>[],
    masterFieldValues: Record<string, any>,
    numberingRuleId: string | undefined,
    companyCode: string,
    userId: string,
    afterUploadFlowId?: string,
    afterUploadFlows?: Array<{ flowId: string; order: number }>
  ): Promise<{
    success: boolean;
    masterInserted: number;
    detailInserted: number;
    generatedKey: string;
    errors: string[];
    controlResult?: any;
  }> {
    const result: {
      success: boolean;
      masterInserted: number;
      detailInserted: number;
      generatedKey: string;
      errors: string[];
      controlResult?: any;
    } = {
      success: false,
      masterInserted: 0,
      detailInserted: 0,
      generatedKey: "",
      errors: [] as string[],
    };

    const pool = getPool();
    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      // 1. 마스터-디테일 관계 정보 조회
      const relation = await this.getMasterDetailRelation(screenId);
      if (!relation) {
        throw new Error("마스터-디테일 관계 정보를 찾을 수 없습니다.");
      }

      const { masterTable, detailTable, masterKeyColumn, detailFkColumn } = relation;

      // 2. 채번 처리
      let generatedKey: string;

      if (numberingRuleId) {
        // 채번 규칙으로 키 생성
        generatedKey = await this.generateNumberWithRule(client, numberingRuleId, companyCode);
      } else {
        // 채번 규칙 없으면 마스터 필드에서 키 값 사용
        generatedKey = masterFieldValues[masterKeyColumn];
        if (!generatedKey) {
          throw new Error(`마스터 키(${masterKeyColumn}) 값이 필요합니다.`);
        }
      }

      result.generatedKey = generatedKey;
      logger.info(`채번 결과: ${generatedKey}`);

      // 3. 마스터 레코드 생성
      const masterData: Record<string, any> = {
        ...masterFieldValues,
        [masterKeyColumn]: generatedKey,
        company_code: companyCode,
        writer: userId,
      };

      // 마스터 컬럼명 목록 구성
      const masterCols = Object.keys(masterData).filter(k => masterData[k] !== undefined);
      const masterPlaceholders = masterCols.map((_, i) => `$${i + 1}`);
      const masterValues = masterCols.map(k => masterData[k]);

      await client.query(
        `INSERT INTO "${masterTable}" (${masterCols.map(c => `"${c}"`).join(", ")}, created_date)
         VALUES (${masterPlaceholders.join(", ")}, NOW())`,
        masterValues
      );
      result.masterInserted = 1;
      logger.info(`마스터 레코드 생성: ${masterTable}, key=${generatedKey}`);

      // 4. 디테일 레코드들 생성 (삽입된 데이터 수집)
      const insertedDetailRows: Record<string, any>[] = [];
      
      for (const row of detailData) {
        try {
          const detailRowData: Record<string, any> = {
            ...row,
            [detailFkColumn]: generatedKey,
            company_code: companyCode,
            writer: userId,
          };

          // 빈 값 필터링 및 id 제외
          const detailCols = Object.keys(detailRowData).filter(k => 
            k !== "id" && 
            detailRowData[k] !== undefined && 
            detailRowData[k] !== null && 
            detailRowData[k] !== ""
          );
          const detailPlaceholders = detailCols.map((_, i) => `$${i + 1}`);
          const detailValues = detailCols.map(k => detailRowData[k]);

          // RETURNING *로 삽입된 데이터 반환받기
          const insertResult = await client.query(
            `INSERT INTO "${detailTable}" (${detailCols.map(c => `"${c}"`).join(", ")}, created_date)
             VALUES (${detailPlaceholders.join(", ")}, NOW())
             RETURNING *`,
            detailValues
          );
          
          if (insertResult.rows && insertResult.rows[0]) {
            insertedDetailRows.push(insertResult.rows[0]);
          }
          
          result.detailInserted++;
        } catch (error: any) {
          result.errors.push(`디테일 행 처리 실패: ${error.message}`);
          logger.error(`디테일 행 처리 실패:`, error);
        }
      }
      
      logger.info(`디테일 레코드 ${insertedDetailRows.length}건 삽입 완료`);

      await client.query("COMMIT");
      result.success = result.errors.length === 0 || result.detailInserted > 0;

      logger.info(`마스터-디테일 간단 모드 업로드 완료:`, {
        masterInserted: result.masterInserted,
        detailInserted: result.detailInserted,
        generatedKey: result.generatedKey,
        errors: result.errors.length,
      });

      // 업로드 후 제어 실행 (단일 또는 다중)
      const flowsToExecute = afterUploadFlows && afterUploadFlows.length > 0
        ? afterUploadFlows // 다중 제어
        : afterUploadFlowId 
          ? [{ flowId: afterUploadFlowId, order: 1 }] // 단일 (하위 호환성)
          : [];
      
      if (flowsToExecute.length > 0 && result.success) {
        try {
          const { NodeFlowExecutionService } = await import("./nodeFlowExecutionService");
          
          // 마스터 데이터 구성
          const masterData = {
            ...masterFieldValues,
            [relation!.masterKeyColumn]: result.generatedKey,
            company_code: companyCode,
          };

          const controlResults: any[] = [];
          
          // 순서대로 제어 실행
          for (const flow of flowsToExecute.sort((a, b) => a.order - b.order)) {
            logger.info(`업로드 후 제어 실행: flowId=${flow.flowId}, order=${flow.order}`);
            logger.info(`   전달 데이터: 마스터 1건, 디테일 ${insertedDetailRows.length}건`);
            
            // 🆕 삽입된 디테일 데이터를 sourceData로 전달 (성능 최적화)
            // - 전체 테이블 조회 대신 방금 INSERT한 데이터만 처리
            // - tableSource 노드가 context-data 모드일 때 이 데이터를 사용
            const controlResult = await NodeFlowExecutionService.executeFlow(
              parseInt(flow.flowId),
              {
                sourceData: insertedDetailRows.length > 0 ? insertedDetailRows : [masterData],
                dataSourceType: "excelUpload", // 엑셀 업로드 데이터임을 명시
                buttonId: "excel-upload-button",
                screenId: screenId,
                userId: userId,
                companyCode: companyCode,
                formData: masterData,
                // 추가 컨텍스트: 마스터/디테일 정보
                masterData: masterData,
                detailData: insertedDetailRows,
                masterTable: relation!.masterTable,
                detailTable: relation!.detailTable,
                masterKeyColumn: relation!.masterKeyColumn,
                detailFkColumn: relation!.detailFkColumn,
              }
            );
            
            controlResults.push({
              flowId: flow.flowId,
              order: flow.order,
              success: controlResult.success,
              message: controlResult.message,
              executedNodes: controlResult.nodes?.length || 0,
            });
          }
          
          result.controlResult = {
            success: controlResults.every(r => r.success),
            executedFlows: controlResults.length,
            results: controlResults,
          };
          
          logger.info(`업로드 후 제어 실행 완료: ${controlResults.length}개 실행`, result.controlResult);
        } catch (controlError: any) {
          logger.error(`업로드 후 제어 실행 실패:`, controlError);
          result.controlResult = {
            success: false,
            message: `제어 실행 실패: ${controlError.message}`,
          };
        }
      }

    } catch (error: any) {
      await client.query("ROLLBACK");
      result.errors.push(`트랜잭션 실패: ${error.message}`);
      logger.error(`마스터-디테일 간단 모드 업로드 실패:`, error);
    } finally {
      client.release();
    }

    return result;
  }

  /**
   * 채번 규칙으로 번호 생성 (기존 numberingRuleService 사용)
   * @param client DB 클라이언트
   * @param ruleId 규칙 ID
   * @param companyCode 회사 코드
   * @param formData 폼 데이터 (날짜 컬럼 기준 생성 시 사용)
   */
  private async generateNumberWithRule(
    client: any, 
    ruleId: string, 
    companyCode: string,
    formData?: Record<string, any>
  ): Promise<string> {
    try {
      // 기존 numberingRuleService를 사용하여 코드 할당
      const { numberingRuleService } = await import("./numberingRuleService");
      const generatedCode = await numberingRuleService.allocateCode(ruleId, companyCode, formData);
      
      logger.info(`채번 생성 (numberingRuleService): rule=${ruleId}, result=${generatedCode}`);
      
      return generatedCode;
    } catch (error: any) {
      logger.error(`채번 생성 실패: rule=${ruleId}, error=${error.message}`);
      throw error;
    }
  }
}

export const masterDetailExcelService = new MasterDetailExcelService();

