import { Request, Response } from "express";
import { Client } from "pg";
import { logger } from "../utils/logger";
import { AuthenticatedRequest } from "../types/auth";
import { ApiResponse } from "../types/common";
import { TableManagementService } from "../services/tableManagementService";
import {
  TableInfo,
  ColumnTypeInfo,
  ColumnSettings,
  TableListResponse,
  ColumnListResponse,
  ColumnSettingsResponse,
} from "../types/tableManagement";
import { query } from "../database/db";
import { auditLogService, getClientIp } from "../services/auditLogService";

/**
 * 테이블 목록 조회
 */
export async function getTableList(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    logger.info("=== 테이블 목록 조회 시작 ===");

    const tableManagementService = new TableManagementService();
    const tableList = await tableManagementService.getTableList();

    logger.info(`테이블 목록 조회 결과: ${tableList.length}개`);

    const response: ApiResponse<TableInfo[]> = {
      success: true,
      message: "테이블 목록을 성공적으로 조회했습니다.",
      data: tableList,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 목록 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 목록 조회 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_LIST_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 컬럼 정보 조회
 */
export async function getColumnList(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { page = 1, size = 50 } = req.query;

    // 🔥 회사 코드 추출 (JWT에서 또는 DB에서 조회)
    let companyCode = req.user?.companyCode;

    if (!companyCode && req.user?.userId) {
      // JWT에 없으면 DB에서 조회
      const { query } = require("../database/db");
      const userResult = await query(
        `SELECT company_code FROM user_info WHERE user_id = $1`,
        [req.user.userId]
      );
      companyCode = userResult[0]?.company_code;
      logger.info(
        `DB에서 회사 코드 조회 (컬럼 목록): ${req.user.userId} → ${companyCode}`
      );
    }

    logger.info(
      `=== 컬럼 정보 조회 시작: ${tableName} (page: ${page}, size: ${size}), company: ${companyCode} ===`
    );

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();

    // 🔥 캐시 버스팅: _t 파라미터가 있으면 캐시 무시
    const bustCache = !!req.query._t;

    const result = await tableManagementService.getColumnList(
      tableName,
      parseInt(page as string),
      parseInt(size as string),
      companyCode, // 🔥 회사 코드 전달
      bustCache // 🔥 캐시 버스팅 옵션
    );

    logger.info(
      `컬럼 정보 조회 결과: ${tableName}, ${result.columns.length}/${result.total}개 (${result.page}/${result.totalPages} 페이지)`
    );

    const response: ApiResponse<typeof result> = {
      success: true,
      message: "컬럼 목록을 성공적으로 조회했습니다.",
      data: result,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("컬럼 정보 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 목록 조회 중 오류가 발생했습니다.",
      error: {
        code: "COLUMN_LIST_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 개별 컬럼 설정 업데이트
 */
export async function updateColumnSettings(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, columnName } = req.params;
    const settings: ColumnSettings = req.body;

    // 🔥 회사 코드 추출 (JWT에서 또는 DB에서 조회)
    let companyCode = req.user?.companyCode;

    if (!companyCode && req.user?.userId) {
      // JWT에 없으면 DB에서 조회
      const { query } = require("../database/db");
      const userResult = await query(
        `SELECT company_code FROM user_info WHERE user_id = $1`,
        [req.user.userId]
      );
      companyCode = userResult[0]?.company_code;
      logger.info(`DB에서 회사 코드 조회: ${req.user.userId} → ${companyCode}`);
    }

    logger.info(
      `=== 컬럼 설정 업데이트 시작: ${tableName}.${columnName}, company: ${companyCode} ===`
    );

    if (!tableName || !columnName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명과 컬럼명이 필요합니다.",
        error: {
          code: "MISSING_PARAMETERS",
          details: "테이블명 또는 컬럼명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!settings) {
      const response: ApiResponse<null> = {
        success: false,
        message: "컬럼 설정 정보가 필요합니다.",
        error: {
          code: "MISSING_SETTINGS",
          details: "요청 본문에 컬럼 설정 정보가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!companyCode) {
      logger.error(`회사 코드 누락: ${tableName}.${columnName}`, {
        user: req.user,
        hasUser: !!req.user,
        userId: req.user?.userId,
        companyCodeFromJWT: req.user?.companyCode,
      });
      const response: ApiResponse<null> = {
        success: false,
        message: "회사 코드를 찾을 수 없습니다.",
        error: {
          code: "MISSING_COMPANY_CODE",
          details:
            "사용자 정보에서 회사 코드를 찾을 수 없습니다. 관리자에게 문의하세요.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    await tableManagementService.updateColumnSettings(
      tableName,
      columnName,
      settings,
      companyCode // 🔥 회사 코드 전달
    );

    logger.info(
      `컬럼 설정 업데이트 완료: ${tableName}.${columnName}, company: ${companyCode}`
    );

    auditLogService.log({
      companyCode: companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "UPDATE",
      resourceType: "TABLE",
      resourceId: `${tableName}.${columnName}`,
      resourceName: settings.columnLabel || columnName,
      tableName: "table_type_columns",
      summary: `테이블 타입관리: ${tableName}.${columnName} 컬럼 설정 변경`,
      changes: {
        after: {
          columnLabel: settings.columnLabel,
          inputType: settings.inputType,
          referenceTable: settings.referenceTable,
          referenceColumn: settings.referenceColumn,
          displayColumn: settings.displayColumn,
          codeCategory: settings.codeCategory,
        },
        fields: ["columnLabel", "inputType", "referenceTable", "referenceColumn", "displayColumn", "codeCategory"],
      },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    const response: ApiResponse<null> = {
      success: true,
      message: "컬럼 설정을 성공적으로 저장했습니다.",
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("컬럼 설정 업데이트 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 설정 저장 중 오류가 발생했습니다.",
      error: {
        code: "COLUMN_SETTINGS_UPDATE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 전체 컬럼 설정 일괄 업데이트
 */
export async function updateAllColumnSettings(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const columnSettings: ColumnSettings[] = req.body;

    // 🔥 회사 코드 추출 (JWT에서 또는 DB에서 조회)
    let companyCode = req.user?.companyCode;

    if (!companyCode && req.user?.userId) {
      // JWT에 없으면 DB에서 조회
      const { query } = require("../database/db");
      const userResult = await query(
        `SELECT company_code FROM user_info WHERE user_id = $1`,
        [req.user.userId]
      );
      companyCode = userResult[0]?.company_code;
      logger.info(`DB에서 회사 코드 조회: ${req.user.userId} → ${companyCode}`);
    }

    // 🔍 디버깅: 사용자 정보 출력
    logger.info(`[DEBUG] req.user:`, JSON.stringify(req.user, null, 2));
    logger.info(`[DEBUG] req.user?.companyCode: ${req.user?.companyCode}`);
    logger.info(`[DEBUG] req.user?.userId: ${req.user?.userId}`);
    logger.info(`[DEBUG] companyCode 최종값: ${companyCode}`);

    logger.info(
      `=== 전체 컬럼 설정 일괄 업데이트 시작: ${tableName}, company: ${companyCode} ===`
    );

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!Array.isArray(columnSettings) || columnSettings.length === 0) {
      const response: ApiResponse<null> = {
        success: false,
        message: "컬럼 설정 목록이 필요합니다.",
        error: {
          code: "MISSING_COLUMN_SETTINGS",
          details: "요청 본문에 컬럼 설정 목록이 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!companyCode) {
      logger.error(`회사 코드 누락 (일괄 업데이트): ${tableName}`, {
        user: req.user,
        hasUser: !!req.user,
        userId: req.user?.userId,
        companyCodeFromJWT: req.user?.companyCode,
        settingsCount: columnSettings.length,
      });
      const response: ApiResponse<null> = {
        success: false,
        message: "회사 코드를 찾을 수 없습니다.",
        error: {
          code: "MISSING_COMPANY_CODE",
          details:
            "사용자 정보에서 회사 코드를 찾을 수 없습니다. 관리자에게 문의하세요.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    await tableManagementService.updateAllColumnSettings(
      tableName,
      columnSettings,
      companyCode // 🔥 회사 코드 전달
    );

    logger.info(
      `전체 컬럼 설정 일괄 업데이트 완료: ${tableName}, ${columnSettings.length}개, company: ${companyCode}`
    );

    const changedColumns = columnSettings
      .filter((c) => c.columnName)
      .map((c) => c.columnName)
      .join(", ");

    auditLogService.log({
      companyCode: companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "BATCH_UPDATE",
      resourceType: "TABLE",
      resourceId: tableName,
      resourceName: tableName,
      tableName: "table_type_columns",
      summary: `테이블 타입관리: ${tableName} 전체 컬럼 설정 일괄 변경 (${columnSettings.length}개)`,
      changes: {
        after: { columns: changedColumns, count: columnSettings.length },
        fields: columnSettings.filter((c) => c.columnName).map((c) => c.columnName!),
      },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    const response: ApiResponse<null> = {
      success: true,
      message: "모든 컬럼 설정을 성공적으로 저장했습니다.",
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("전체 컬럼 설정 일괄 업데이트 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 설정 저장 중 오류가 발생했습니다.",
      error: {
        code: "ALL_COLUMN_SETTINGS_UPDATE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 라벨 정보 조회
 */
export async function getTableLabels(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    logger.info(`=== 테이블 라벨 정보 조회 시작: ${tableName} ===`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const tableLabels = await tableManagementService.getTableLabels(tableName);

    if (!tableLabels) {
      // 라벨이 없으면 빈 객체를 성공으로 반환 (404 에러 대신)
      const response: ApiResponse<{}> = {
        success: true,
        message: "테이블 라벨 정보를 조회했습니다.",
        data: {},
      };
      res.status(200).json(response);
      return;
    }

    logger.info(`테이블 라벨 정보 조회 완료: ${tableName}`);

    const response: ApiResponse<any> = {
      success: true,
      message: "테이블 라벨 정보를 성공적으로 조회했습니다.",
      data: tableLabels,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 라벨 정보 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 라벨 정보 조회 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_LABELS_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 컬럼 라벨 정보 조회
 */
export async function getColumnLabels(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, columnName } = req.params;
    logger.info(`=== 컬럼 라벨 정보 조회 시작: ${tableName}.${columnName} ===`);

    if (!tableName || !columnName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명과 컬럼명이 필요합니다.",
        error: {
          code: "MISSING_PARAMETERS",
          details: "테이블명 또는 컬럼명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const columnLabels = await tableManagementService.getColumnLabels(
      tableName,
      columnName
    );

    if (!columnLabels) {
      // 라벨이 없으면 빈 객체를 성공으로 반환 (404 에러 대신)
      const response: ApiResponse<{}> = {
        success: true,
        message: "컬럼 라벨 정보를 조회했습니다.",
        data: {},
      };
      res.status(200).json(response);
      return;
    }

    logger.info(`컬럼 라벨 정보 조회 완료: ${tableName}.${columnName}`);

    const response: ApiResponse<any> = {
      success: true,
      message: "컬럼 라벨 정보를 성공적으로 조회했습니다.",
      data: columnLabels,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("컬럼 라벨 정보 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 라벨 정보 조회 중 오류가 발생했습니다.",
      error: {
        code: "COLUMN_LABELS_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 라벨 설정
 */
export async function updateTableLabel(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { displayName, description } = req.body;

    logger.info(`=== 테이블 라벨 설정 시작: ${tableName} ===`);
    logger.info(`표시명: ${displayName}, 설명: ${description}`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    await tableManagementService.updateTableLabel(
      tableName,
      displayName,
      description
    );

    logger.info(`테이블 라벨 설정 완료: ${tableName}`);

    const response: ApiResponse<null> = {
      success: true,
      message: "테이블 라벨이 성공적으로 설정되었습니다.",
      data: null,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 라벨 설정 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 라벨 설정 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_LABEL_UPDATE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 컬럼 입력 타입 설정
 */
export async function updateColumnInputType(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, columnName } = req.params;
    let { inputType, detailSettings } = req.body;

    // 🔥 "direct" 또는 "auto"는 프론트엔드의 입력 방식 구분값이므로
    // DB의 input_type(웹타입)으로 저장하면 안 됨 - "text"로 변환
    if (inputType === "direct" || inputType === "auto") {
      logger.warn(
        `잘못된 inputType 값 감지: ${inputType} → 'text'로 변환 (${tableName}.${columnName})`
      );
      inputType = "text";
    }

    // 🔥 회사 코드 추출 (JWT에서 또는 DB에서 조회)
    let companyCode = req.user?.companyCode;

    if (!companyCode && req.user?.userId) {
      // JWT에 없으면 DB에서 조회
      const { query } = require("../database/db");
      const userResult = await query(
        `SELECT company_code FROM user_info WHERE user_id = $1`,
        [req.user.userId]
      );
      companyCode = userResult[0]?.company_code;
      logger.info(`DB에서 회사 코드 조회: ${req.user.userId} → ${companyCode}`);
    }

    logger.info(
      `=== 컬럼 입력 타입 설정 시작: ${tableName}.${columnName} = ${inputType}, company: ${companyCode} ===`
    );

    if (!tableName || !columnName || !inputType) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명, 컬럼명, 입력 타입이 모두 필요합니다.",
        error: {
          code: "MISSING_PARAMETERS",
          details: "필수 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!companyCode) {
      logger.error(`회사 코드 누락 (입력 타입): ${tableName}.${columnName}`, {
        user: req.user,
        hasUser: !!req.user,
        userId: req.user?.userId,
        companyCodeFromJWT: req.user?.companyCode,
        inputType,
      });
      const response: ApiResponse<null> = {
        success: false,
        message: "회사 코드를 찾을 수 없습니다.",
        error: {
          code: "MISSING_COMPANY_CODE",
          details:
            "사용자 정보에서 회사 코드를 찾을 수 없습니다. 관리자에게 문의하세요.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    await tableManagementService.updateColumnInputType(
      tableName,
      columnName,
      inputType,
      companyCode,
      detailSettings
    );

    logger.info(
      `컬럼 입력 타입 설정 완료: ${tableName}.${columnName} = ${inputType}, company: ${companyCode}`
    );

    const response: ApiResponse<null> = {
      success: true,
      message: "컬럼 입력 타입이 성공적으로 설정되었습니다.",
      data: null,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("컬럼 입력 타입 설정 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 입력 타입 설정 중 오류가 발생했습니다.",
      error: {
        code: "INPUT_TYPE_UPDATE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 단일 레코드 조회 (자동 입력용)
 */
export async function getTableRecord(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { filterColumn, filterValue, displayColumn } = req.body;

    logger.info(`=== 단일 레코드 조회 시작: ${tableName} ===`);
    logger.info(`필터: ${filterColumn} = ${filterValue}`);
    logger.info(`표시 컬럼: ${displayColumn}`);

    if (!tableName || !filterColumn || !filterValue) {
      const response: ApiResponse<null> = {
        success: false,
        message: "필수 파라미터가 누락되었습니다.",
        error: {
          code: "MISSING_PARAMETERS",
          details:
            "tableName, filterColumn, filterValue가 필요합니다. displayColumn은 선택적입니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();

    // 단일 레코드 조회 (WHERE filterColumn = filterValue)
    const result = await tableManagementService.getTableData(tableName, {
      page: 1,
      size: 1,
      search: {
        [filterColumn]: filterValue,
      },
    });

    if (!result.data || result.data.length === 0) {
      const response: ApiResponse<null> = {
        success: false,
        message: "데이터를 찾을 수 없습니다.",
        error: {
          code: "NOT_FOUND",
          details: `${filterColumn} = ${filterValue}에 해당하는 데이터가 없습니다.`,
        },
      };
      res.status(404).json(response);
      return;
    }

    const record = result.data[0];
    // displayColumn이 "*"이거나 없으면 전체 레코드 반환
    const displayValue = displayColumn && displayColumn !== "*" 
      ? record[displayColumn] 
      : record;

    logger.info(`레코드 조회 완료: ${displayColumn || "*"} = ${typeof displayValue === 'object' ? '[전체 레코드]' : displayValue}`);

    const response: ApiResponse<{ value: any; record: any }> = {
      success: true,
      message: "레코드를 성공적으로 조회했습니다.",
      data: {
        value: displayValue,
        record: record,
      },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("레코드 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "레코드 조회 중 오류가 발생했습니다.",
      error: {
        code: "RECORD_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 데이터 조회 (페이징 + 검색 + 필터링)
 */
export async function getTableData(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const {
      page = 1,
      size = 10,
      search = {},
      sortBy,
      sortOrder = "asc",
      autoFilter, // 🆕 자동 필터 설정 추가 (컴포넌트에서 직접 전달)
      dataFilter, // 🆕 컬럼 값 기반 데이터 필터링
    } = req.body;

    logger.info(`=== 테이블 데이터 조회 시작: ${tableName} ===`);
    logger.info(`페이징: page=${page}, size=${size}`);
    logger.info(`검색 조건:`, search);
    logger.info(`정렬: ${sortBy} ${sortOrder}`);
    logger.info(`자동 필터:`, autoFilter); // 🆕
    logger.info(`데이터 필터:`, dataFilter); // 🆕

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();

    // 🆕 현재 사용자 필터 적용 (autoFilter가 없거나 enabled가 명시적으로 false가 아니면 기본 적용)
    let enhancedSearch = { ...search };
    const shouldApplyAutoFilter = autoFilter?.enabled !== false; // 기본값: true
    if (shouldApplyAutoFilter && req.user) {
      const filterColumn = autoFilter?.filterColumn || "company_code";
      const userField = autoFilter?.userField || "companyCode";
      const userValue = (req.user as any)[userField];

      // 🆕 프리뷰용 회사 코드 오버라이드 (최고 관리자만 허용)
      let finalCompanyCode = userValue;
      if (autoFilter?.companyCodeOverride && userValue === "*") {
        // 최고 관리자만 다른 회사 코드로 오버라이드 가능
        finalCompanyCode = autoFilter.companyCodeOverride;
        logger.info("🔓 최고 관리자 회사 코드 오버라이드:", {
          originalCompanyCode: userValue,
          overrideCompanyCode: autoFilter.companyCodeOverride,
          tableName,
        });
      }

      if (finalCompanyCode) {
        enhancedSearch[filterColumn] = finalCompanyCode;

        logger.info("🔍 현재 사용자 필터 적용:", {
          filterColumn,
          userField,
          userValue: finalCompanyCode,
          tableName,
        });
      } else {
        logger.warn("⚠️ 사용자 정보 필드 값 없음:", {
          userField,
          user: req.user,
        });
      }
    }

    // 데이터 조회
    const result = await tableManagementService.getTableData(tableName, {
      page: parseInt(page),
      size: parseInt(size),
      search: enhancedSearch, // 🆕 필터가 적용된 search 사용
      sortBy,
      sortOrder,
      dataFilter, // 🆕 데이터 필터 전달
    });

    logger.info(
      `테이블 데이터 조회 완료: ${tableName}, 총 ${result.total}건, 페이지 ${result.page}/${result.totalPages}`
    );

    const response: ApiResponse<any> = {
      success: true,
      message: "테이블 데이터를 성공적으로 조회했습니다.",
      data: result,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 데이터 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 데이터 조회 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_DATA_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 데이터 추가
 */
export async function addTableData(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const data = req.body;

    logger.info(`=== 테이블 데이터 추가 시작: ${tableName} ===`);
    logger.info(`추가할 데이터:`, data);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!data || Object.keys(data).length === 0) {
      const response: ApiResponse<null> = {
        success: false,
        message: "추가할 데이터가 필요합니다.",
        error: {
          code: "MISSING_DATA",
          details: "요청 본문에 데이터가 없습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();

    // 🆕 멀티테넌시: company_code 자동 추가 (테이블에 company_code 컬럼이 있는 경우)
    const companyCode = req.user?.companyCode;
    if (companyCode && !data.company_code) {
      // 테이블에 company_code 컬럼이 있는지 확인
      const hasCompanyCodeColumn = await tableManagementService.hasColumn(tableName, "company_code");
      if (hasCompanyCodeColumn) {
        data.company_code = companyCode;
        logger.info(`멀티테넌시: company_code 자동 추가 - ${companyCode}`);
      }
    }

    // 🆕 writer 컬럼 자동 추가 (테이블에 writer 컬럼이 있고 값이 없는 경우)
    const userId = req.user?.userId;
    if (userId && !data.writer) {
      const hasWriterColumn = await tableManagementService.hasColumn(tableName, "writer");
      if (hasWriterColumn) {
        data.writer = userId;
        logger.info(`writer 자동 추가 - ${userId}`);
      }
    }

    // 회사별 NOT NULL 소프트 제약조건 검증
    const notNullViolations = await tableManagementService.validateNotNullConstraints(
      tableName,
      data,
      companyCode || "*"
    );
    if (notNullViolations.length > 0) {
      res.status(400).json({
        success: false,
        message: `필수 항목이 비어있습니다: ${notNullViolations.join(", ")}`,
        error: {
          code: "NOT_NULL_VIOLATION",
          details: notNullViolations,
        },
      });
      return;
    }

    // 회사별 UNIQUE 소프트 제약조건 검증
    const uniqueViolations = await tableManagementService.validateUniqueConstraints(
      tableName,
      data,
      companyCode || "*"
    );
    if (uniqueViolations.length > 0) {
      res.status(400).json({
        success: false,
        message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
        error: {
          code: "UNIQUE_VIOLATION",
          details: uniqueViolations,
        },
      });
      return;
    }

    // 데이터 추가
    const result = await tableManagementService.addTableData(tableName, data);

    logger.info(`테이블 데이터 추가 완료: ${tableName}, id: ${result.insertedId}`);

    const systemFields = new Set([
      "id", "created_date", "updated_date", "writer", "company_code",
      "createdDate", "updatedDate", "companyCode",
    ]);
    const auditData: Record<string, any> = {};
    for (const [k, v] of Object.entries(data)) {
      if (!systemFields.has(k)) auditData[k] = v;
    }

    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "CREATE",
      resourceType: "DATA",
      resourceId: result.insertedId || "",
      resourceName: tableName,
      tableName,
      summary: `${tableName} 데이터 추가`,
      changes: { after: auditData },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    const response: ApiResponse<{ id: string | null }> = {
      success: true,
      message: "테이블 데이터를 성공적으로 추가했습니다.",
      data: { id: result.insertedId },
    };

    res.status(201).json(response);
  } catch (error) {
    logger.error("테이블 데이터 추가 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 데이터 추가 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_ADD_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 데이터 수정
 */
export async function editTableData(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { originalData, updatedData } = req.body;

    logger.info(`=== 테이블 데이터 수정 시작: ${tableName} ===`);
    logger.info(`원본 데이터:`, originalData);
    logger.info(`수정할 데이터:`, updatedData);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "INVALID_TABLE_NAME",
          details: "테이블명이 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!originalData || !updatedData) {
      const response: ApiResponse<null> = {
        success: false,
        message: "원본 데이터와 수정할 데이터가 모두 필요합니다.",
        error: {
          code: "INVALID_DATA",
          details: "originalData와 updatedData가 모두 제공되어야 합니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (Object.keys(updatedData).length === 0) {
      const response: ApiResponse<null> = {
        success: false,
        message: "수정할 데이터가 없습니다.",
        error: {
          code: "INVALID_DATA",
          details: "수정할 데이터가 비어있습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const companyCode = req.user?.companyCode || "*";

    // 회사별 NOT NULL 소프트 제약조건 검증 (수정 데이터 대상)
    const notNullViolations = await tableManagementService.validateNotNullConstraints(
      tableName,
      updatedData,
      companyCode
    );
    if (notNullViolations.length > 0) {
      res.status(400).json({
        success: false,
        message: `필수 항목이 비어있습니다: ${notNullViolations.join(", ")}`,
        error: {
          code: "NOT_NULL_VIOLATION",
          details: notNullViolations,
        },
      });
      return;
    }

    // 회사별 UNIQUE 소프트 제약조건 검증 (수정 시 자기 자신 제외)
    const excludeId = originalData?.id ? String(originalData.id) : undefined;
    const uniqueViolations = await tableManagementService.validateUniqueConstraints(
      tableName,
      updatedData,
      companyCode,
      excludeId
    );
    if (uniqueViolations.length > 0) {
      res.status(400).json({
        success: false,
        message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
        error: {
          code: "UNIQUE_VIOLATION",
          details: uniqueViolations,
        },
      });
      return;
    }

    const systemFieldsForEdit = new Set([
      "id", "created_date", "updated_date", "writer", "company_code",
      "createdDate", "updatedDate", "companyCode",
    ]);
    const changedBefore: Record<string, any> = {};
    const changedAfter: Record<string, any> = {};
    for (const key of Object.keys(updatedData)) {
      if (systemFieldsForEdit.has(key)) continue;
      if (String(originalData[key] ?? "") !== String(updatedData[key] ?? "")) {
        changedBefore[key] = originalData[key];
        changedAfter[key] = updatedData[key];
      }
    }

    // 데이터 수정
    await tableManagementService.editTableData(
      tableName,
      originalData,
      updatedData
    );

    logger.info(`테이블 데이터 수정 완료: ${tableName}`);

    if (Object.keys(changedAfter).length > 0) {
      auditLogService.log({
        companyCode: req.user?.companyCode || "",
        userId: req.user?.userId || "",
        userName: req.user?.userName || "",
        action: "UPDATE",
        resourceType: "DATA",
        resourceId: originalData.id?.toString() || "",
        resourceName: tableName,
        tableName,
        summary: `${tableName} 데이터 수정`,
        changes: { before: changedBefore, after: changedAfter },
        ipAddress: getClientIp(req),
        requestPath: req.originalUrl,
      });
    }

    const response: ApiResponse<null> = {
      success: true,
      message: "테이블 데이터를 성공적으로 수정했습니다.",
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 데이터 수정 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 데이터 수정 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_EDIT_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 스키마 정보 조회 (컬럼 존재 여부 검증용)
 */
export async function getTableSchema(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    logger.info(`=== 테이블 스키마 정보 조회 시작: ${tableName} ===`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const schema = await tableManagementService.getTableSchema(tableName);

    logger.info(
      `테이블 스키마 정보 조회 완료: ${tableName}, ${schema.length}개 컬럼`
    );

    const response: ApiResponse<ColumnTypeInfo[]> = {
      success: true,
      message: "테이블 스키마 정보를 성공적으로 조회했습니다.",
      data: schema,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 스키마 정보 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 스키마 정보 조회 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_SCHEMA_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 존재 여부 확인
 */
export async function checkTableExists(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    logger.info(`=== 테이블 존재 여부 확인 시작: ${tableName} ===`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const exists = await tableManagementService.checkTableExists(tableName);

    logger.info(`테이블 존재 여부 확인 완료: ${tableName} = ${exists}`);

    const response: ApiResponse<{ exists: boolean }> = {
      success: true,
      message: "테이블 존재 여부를 확인했습니다.",
      data: { exists },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 존재 여부 확인 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 존재 여부 확인 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_EXISTS_CHECK_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 컬럼 웹타입 정보 조회 (화면관리 연동용)
 */
export async function getColumnWebTypes(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;

    // 🔥 회사 코드 추출 (JWT에서 또는 DB에서 조회)
    let companyCode = req.user?.companyCode;

    if (!companyCode && req.user?.userId) {
      // JWT에 없으면 DB에서 조회
      const { query } = require("../database/db");
      const userResult = await query(
        `SELECT company_code FROM user_info WHERE user_id = $1`,
        [req.user.userId]
      );
      companyCode = userResult[0]?.company_code;
      logger.info(
        `DB에서 회사 코드 조회 (조회): ${req.user.userId} → ${companyCode}`
      );
    }

    logger.info(
      `=== 컬럼 웹타입 정보 조회 시작: ${tableName}, company: ${companyCode} ===`
    );

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!companyCode) {
      logger.error(`회사 코드 누락 (조회): ${tableName}`, {
        user: req.user,
        hasUser: !!req.user,
        userId: req.user?.userId,
        companyCodeFromJWT: req.user?.companyCode,
      });
      const response: ApiResponse<null> = {
        success: false,
        message: "회사 코드를 찾을 수 없습니다.",
        error: {
          code: "MISSING_COMPANY_CODE",
          details:
            "사용자 정보에서 회사 코드를 찾을 수 없습니다. 관리자에게 문의하세요.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const inputTypes = await tableManagementService.getColumnInputTypes(
      tableName,
      companyCode
    );

    logger.info(
      `컬럼 입력타입 정보 조회 완료: ${tableName}, company: ${companyCode}, ${inputTypes.length}개 컬럼`
    );

    const response: ApiResponse<ColumnTypeInfo[]> = {
      success: true,
      message: "컬럼 입력타입 정보를 성공적으로 조회했습니다.",
      data: inputTypes,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("컬럼 웹타입 정보 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 웹타입 정보 조회 중 오류가 발생했습니다.",
      error: {
        code: "COLUMN_WEB_TYPES_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 데이터베이스 연결 상태 확인
 */
export async function checkDatabaseConnection(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    logger.info("=== 데이터베이스 연결 상태 확인 시작 ===");

    const tableManagementService = new TableManagementService();
    const connectionStatus =
      await tableManagementService.checkDatabaseConnection();

    logger.info(
      `데이터베이스 연결 상태: ${connectionStatus.connected ? "연결됨" : "연결 안됨"}`
    );

    const response: ApiResponse<{ connected: boolean; message: string }> = {
      success: true,
      message: "데이터베이스 연결 상태를 확인했습니다.",
      data: connectionStatus,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("데이터베이스 연결 상태 확인 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "데이터베이스 연결 상태 확인 중 오류가 발생했습니다.",
      error: {
        code: "DATABASE_CONNECTION_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 테이블 데이터 삭제
 */
export async function deleteTableData(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const data = req.body;

    logger.info(`=== 테이블 데이터 삭제 시작: ${tableName} ===`);
    logger.info(`삭제할 데이터:`, data);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!data || (Array.isArray(data) && data.length === 0)) {
      const response: ApiResponse<null> = {
        success: false,
        message: "삭제할 데이터가 필요합니다.",
        error: {
          code: "MISSING_DATA",
          details: "요청 본문에 삭제할 데이터가 없습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();

    // 데이터 삭제
    const deletedCount = await tableManagementService.deleteTableData(
      tableName,
      data
    );

    logger.info(
      `테이블 데이터 삭제 완료: ${tableName}, ${deletedCount}건 삭제`
    );

    const deleteItems = Array.isArray(data) ? data : [data];
    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: "DELETE",
      resourceType: "DATA",
      resourceId: deleteItems[0]?.id?.toString() || "",
      resourceName: tableName,
      tableName,
      summary: `${tableName} 데이터 삭제 (${deletedCount}건)`,
      changes: { before: { deletedCount, items: deleteItems.length } },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    const response: ApiResponse<{ deletedCount: number }> = {
      success: true,
      message: `테이블 데이터를 성공적으로 삭제했습니다. (${deletedCount}건)`,
      data: { deletedCount },
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 데이터 삭제 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 데이터 삭제 중 오류가 발생했습니다.",
      error: {
        code: "TABLE_DELETE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 컬럼 웹 타입 설정 (레거시 지원)
 * @deprecated updateColumnInputType 사용 권장
 */
export async function updateColumnWebType(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, columnName } = req.params;
    const { webType, detailSettings, inputType } = req.body;

    logger.warn(
      `레거시 API 사용: updateColumnWebType → updateColumnInputType 사용 권장`
    );

    // 🔥 inputType이 "direct" 또는 "auto"이면 무시하고 webType 사용
    // "direct"/"auto"는 프론트엔드의 입력 방식(직접입력/자동입력) 구분값이지
    // DB에 저장할 웹 타입(text, number, date 등)이 아님
    let convertedInputType = webType || "text";
    if (inputType && inputType !== "direct" && inputType !== "auto") {
      convertedInputType = inputType;
    }

    logger.info(
      `웹타입 변환: webType=${webType}, inputType=${inputType} → ${convertedInputType}`
    );

    // 새로운 메서드 호출
    req.body = { inputType: convertedInputType, detailSettings };
    await updateColumnInputType(req, res);
  } catch (error) {
    logger.error("레거시 컬럼 웹 타입 설정 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "컬럼 웹 타입 설정 중 오류가 발생했습니다.",
      error: {
        code: "WEB_TYPE_UPDATE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

// ========================================
// 🎯 테이블 로그 시스템 API
// ========================================

/**
 * 로그 테이블 생성
 */
export async function createLogTable(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { pkColumn } = req.body;
    const userId = req.user?.userId;

    logger.info(`=== 로그 테이블 생성 시작: ${tableName} ===`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (!pkColumn || !pkColumn.columnName || !pkColumn.dataType) {
      const response: ApiResponse<null> = {
        success: false,
        message: "PK 컬럼 정보가 필요합니다.",
        error: {
          code: "MISSING_PK_COLUMN",
          details: "PK 컬럼명과 데이터 타입이 필요합니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    await tableManagementService.createLogTable(tableName, pkColumn, userId);

    logger.info(`로그 테이블 생성 완료: ${tableName}_log`);

    const response: ApiResponse<null> = {
      success: true,
      message: "로그 테이블이 성공적으로 생성되었습니다.",
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("로그 테이블 생성 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "로그 테이블 생성 중 오류가 발생했습니다.",
      error: {
        code: "LOG_TABLE_CREATE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 로그 설정 조회
 */
export async function getLogConfig(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;

    logger.info(`=== 로그 설정 조회: ${tableName} ===`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const logConfig = await tableManagementService.getLogConfig(tableName);

    const response: ApiResponse<typeof logConfig> = {
      success: true,
      message: "로그 설정을 조회했습니다.",
      data: logConfig,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("로그 설정 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "로그 설정 조회 중 오류가 발생했습니다.",
      error: {
        code: "LOG_CONFIG_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 로그 데이터 조회
 */
export async function getLogData(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const {
      page = 1,
      size = 20,
      operationType,
      startDate,
      endDate,
      changedBy,
      originalId,
    } = req.query;

    logger.info(`=== 로그 데이터 조회: ${tableName} ===`);

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    const result = await tableManagementService.getLogData(tableName, {
      page: parseInt(page as string),
      size: parseInt(size as string),
      operationType: operationType as string,
      startDate: startDate as string,
      endDate: endDate as string,
      changedBy: changedBy as string,
      originalId: originalId as string,
    });

    logger.info(`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`);

    const response: ApiResponse<typeof result> = {
      success: true,
      message: "로그 데이터를 조회했습니다.",
      data: result,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("로그 데이터 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "로그 데이터 조회 중 오류가 발생했습니다.",
      error: {
        code: "LOG_DATA_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 로그 테이블 활성화/비활성화
 */
export async function toggleLogTable(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { isActive } = req.body;

    logger.info(
      `=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`
    );

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "테이블명이 필요합니다.",
        error: {
          code: "MISSING_TABLE_NAME",
          details: "테이블명 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    if (isActive === undefined || isActive === null) {
      const response: ApiResponse<null> = {
        success: false,
        message: "isActive 값이 필요합니다.",
        error: {
          code: "MISSING_IS_ACTIVE",
          details: "isActive 파라미터가 누락되었습니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    const tableManagementService = new TableManagementService();
    await tableManagementService.toggleLogTable(
      tableName,
      isActive === "Y" || isActive === true
    );

    logger.info(`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`);

    const response: ApiResponse<null> = {
      success: true,
      message: `로그 기능이 ${isActive ? "활성화" : "비활성화"}되었습니다.`,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("로그 테이블 토글 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "로그 테이블 토글 중 오류가 발생했습니다.",
      error: {
        code: "LOG_TOGGLE_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

/**
 * 회사별 카테고리 컬럼 조회 (메뉴 종속 없음)
 * 
 * @route GET /api/table-management/category-columns
 * @description table_type_columns에서 회사 코드 기준으로 input_type = 'category'인 컬럼을 조회
 */
export async function getCategoryColumnsByCompany(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const companyCode = req.user?.companyCode;

    logger.info("📥 회사별 카테고리 컬럼 조회 요청", { companyCode });

    if (!companyCode) {
      logger.error("❌ 회사 코드가 없습니다", { user: req.user });
      res.status(400).json({
        success: false,
        message: "회사 코드를 확인할 수 없습니다. 다시 로그인해주세요.",
      });
      return;
    }

    const { getPool } = await import("../database/db");
    const pool = getPool();

    let columnsResult;
    
    // 최고 관리자인 경우 company_code = '*'인 카테고리 컬럼 조회
    // category_ref가 설정된 컬럼은 제외 (참조 컬럼은 자체 값 관리 안 함)
    if (companyCode === "*") {
      const columnsQuery = `
        SELECT DISTINCT
          ttc.table_name AS "tableName",
          COALESCE(
            tl.table_label,
            initcap(replace(ttc.table_name, '_', ' '))
          ) AS "tableLabel",
          ttc.column_name AS "columnName",
          COALESCE(
            ttc.column_label,
            initcap(replace(ttc.column_name, '_', ' '))
          ) AS "columnLabel",
          ttc.input_type AS "inputType"
        FROM table_type_columns ttc
        LEFT JOIN table_labels tl
          ON ttc.table_name = tl.table_name
        WHERE ttc.input_type = 'category'
          AND ttc.company_code = '*'
          AND (ttc.category_ref IS NULL OR ttc.category_ref = '')
        ORDER BY ttc.table_name, ttc.column_name
      `;
      
      columnsResult = await pool.query(columnsQuery);
      logger.info("최고 관리자: 전체 카테고리 컬럼 조회 완료 (참조 제외)", { 
        rowCount: columnsResult.rows.length 
      });
    } else {
      const columnsQuery = `
        SELECT DISTINCT
          ttc.table_name AS "tableName",
          COALESCE(
            tl.table_label,
            initcap(replace(ttc.table_name, '_', ' '))
          ) AS "tableLabel",
          ttc.column_name AS "columnName",
          COALESCE(
            ttc.column_label,
            initcap(replace(ttc.column_name, '_', ' '))
          ) AS "columnLabel",
          ttc.input_type AS "inputType"
        FROM table_type_columns ttc
        LEFT JOIN table_labels tl
          ON ttc.table_name = tl.table_name
        WHERE ttc.input_type = 'category'
          AND ttc.company_code = $1
          AND (ttc.category_ref IS NULL OR ttc.category_ref = '')
        ORDER BY ttc.table_name, ttc.column_name
      `;
      
      columnsResult = await pool.query(columnsQuery, [companyCode]);
      logger.info("회사별 카테고리 컬럼 조회 완료 (참조 제외)", { 
        companyCode,
        rowCount: columnsResult.rows.length 
      });
    }

    res.json({
      success: true,
      data: columnsResult.rows,
      message: "카테고리 컬럼 조회 성공",
    });
  } catch (error: any) {
    logger.error("❌ 회사별 카테고리 컬럼 조회 실패", { error: error.message });
    res.status(500).json({
      success: false,
      message: "카테고리 컬럼 조회 중 오류가 발생했습니다.",
      error: error.message,
    });
  }
}

/**
 * 메뉴의 상위 메뉴들이 설정한 모든 카테고리 타입 컬럼 조회 (계층 구조 상속)
 * 
 * @route GET /api/table-management/menu/:menuObjid/category-columns
 * @description 현재 메뉴와 상위 메뉴들에서 설정한 category_column_mapping의 모든 카테고리 컬럼 조회
 * 
 * 예시:
 * - 2레벨 메뉴 "고객사관리"에서 discount_type, rounding_type 설정
 * - 3레벨 메뉴 "고객등록", "고객조회" 등에서도 동일하게 보임 (상속)
 */
export async function getCategoryColumnsByMenu(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { menuObjid } = req.params;
    const companyCode = req.user?.companyCode;

    logger.info("📥 메뉴별 카테고리 컬럼 조회 요청", { menuObjid, companyCode });

    if (!menuObjid) {
      res.status(400).json({
        success: false,
        message: "메뉴 OBJID가 필요합니다.",
      });
      return;
    }

    if (!companyCode) {
      logger.error("❌ 회사 코드가 없습니다", { menuObjid, user: req.user });
      res.status(400).json({
        success: false,
        message: "회사 코드를 확인할 수 없습니다. 다시 로그인해주세요.",
      });
      return;
    }

    const { getPool } = await import("../database/db");
    const pool = getPool();

    // table_type_columns에서 input_type = 'category' 컬럼 조회
    // category_ref가 설정된 컬럼은 제외 (참조 컬럼은 자체 값 관리 안 함)
    let columnsResult;
    
    if (companyCode === "*") {
      const columnsQuery = `
        SELECT DISTINCT
          ttc.table_name AS "tableName",
          COALESCE(
            tl.table_label,
            initcap(replace(ttc.table_name, '_', ' '))
          ) AS "tableLabel",
          ttc.column_name AS "columnName",
          COALESCE(
            ttc.column_label,
            initcap(replace(ttc.column_name, '_', ' '))
          ) AS "columnLabel",
          ttc.input_type AS "inputType"
        FROM table_type_columns ttc
        LEFT JOIN table_labels tl
          ON ttc.table_name = tl.table_name
        WHERE ttc.input_type = 'category'
          AND ttc.company_code = '*'
          AND (ttc.category_ref IS NULL OR ttc.category_ref = '')
        ORDER BY ttc.table_name, ttc.column_name
      `;
      
      columnsResult = await pool.query(columnsQuery);
      logger.info("최고 관리자: 메뉴별 카테고리 컬럼 조회 완료 (참조 제외)", { 
        rowCount: columnsResult.rows.length 
      });
    } else {
      const columnsQuery = `
        SELECT DISTINCT
          ttc.table_name AS "tableName",
          COALESCE(
            tl.table_label,
            initcap(replace(ttc.table_name, '_', ' '))
          ) AS "tableLabel",
          ttc.column_name AS "columnName",
          COALESCE(
            ttc.column_label,
            initcap(replace(ttc.column_name, '_', ' '))
          ) AS "columnLabel",
          ttc.input_type AS "inputType"
        FROM table_type_columns ttc
        LEFT JOIN table_labels tl
          ON ttc.table_name = tl.table_name
        WHERE ttc.input_type = 'category'
          AND ttc.company_code = $1
          AND (ttc.category_ref IS NULL OR ttc.category_ref = '')
        ORDER BY ttc.table_name, ttc.column_name
      `;
      
      columnsResult = await pool.query(columnsQuery, [companyCode]);
      logger.info("회사별 메뉴 카테고리 컬럼 조회 완료 (참조 제외)", { 
        companyCode,
        rowCount: columnsResult.rows.length 
      });
    }
    
    logger.info("✅ 카테고리 컬럼 조회 완료", { 
      columnCount: columnsResult.rows.length 
    });

    res.json({
      success: true,
      data: columnsResult.rows,
      message: "카테고리 컬럼 조회 성공",
    });
  } catch (error: any) {
    logger.error("❌ 메뉴별 카테고리 컬럼 조회 실패");
    logger.error("에러 메시지:", error.message);
    logger.error("에러 스택:", error.stack);
    logger.error("에러 전체:", error);

    res.status(500).json({
      success: false,
      message: "카테고리 컬럼 조회에 실패했습니다.",
      error: error.message,
      stack: error.stack, // 디버깅용
    });
  }
}

/**
 * 범용 다중 테이블 저장 API
 * 
 * 메인 테이블과 서브 테이블(들)에 트랜잭션으로 데이터를 저장합니다.
 * 
 * 요청 본문:
 * {
 *   mainTable: { tableName: string, primaryKeyColumn: string },
 *   mainData: Record<string, any>,
 *   subTables: Array<{
 *     tableName: string,
 *     linkColumn: { mainField: string, subColumn: string },
 *     items: Record<string, any>[],
 *     options?: {
 *       saveMainAsFirst?: boolean,
 *       mainFieldMappings?: Array<{ formField: string, targetColumn: string }>,
 *       mainMarkerColumn?: string,
 *       mainMarkerValue?: any,
 *       subMarkerValue?: any,
 *       deleteExistingBefore?: boolean,
 *     }
 *   }>,
 *   isUpdate?: boolean
 * }
 */
export async function multiTableSave(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  const pool = require("../database/db").getPool();
  const client = await pool.connect();

  try {
    const { mainTable, mainData, subTables, isUpdate } = req.body;
    const companyCode = req.user?.companyCode || "*";

    logger.info("=== 다중 테이블 저장 시작 ===", {
      mainTable,
      mainDataKeys: Object.keys(mainData || {}),
      subTablesCount: subTables?.length || 0,
      isUpdate,
      companyCode,
    });

    // 유효성 검사
    if (!mainTable?.tableName || !mainTable?.primaryKeyColumn) {
      res.status(400).json({
        success: false,
        message: "메인 테이블 설정이 올바르지 않습니다.",
      });
      return;
    }

    if (!mainData || Object.keys(mainData).length === 0) {
      res.status(400).json({
        success: false,
        message: "저장할 메인 데이터가 없습니다.",
      });
      return;
    }

    // UNIQUE 제약조건 검증 (트랜잭션 전에)
    const tmsMulti = new TableManagementService();
    const uniqueViolations = await tmsMulti.validateUniqueConstraints(
      mainTable.tableName,
      mainData,
      companyCode,
      isUpdate ? mainData[mainTable.primaryKeyColumn] : undefined
    );
    if (uniqueViolations.length > 0) {
      client.release();
      res.status(400).json({
        success: false,
        message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
      });
      return;
    }

    await client.query("BEGIN");

    // 1. 메인 테이블 저장
    const mainTableName = mainTable.tableName;
    const pkColumn = mainTable.primaryKeyColumn;
    const pkValue = mainData[pkColumn];

    // company_code 자동 추가 (최고 관리자가 아닌 경우)
    if (companyCode !== "*" && !mainData.company_code) {
      mainData.company_code = companyCode;
    }

    let mainResult: any;
    
    if (isUpdate && pkValue) {
      // UPDATE
      const updateColumns = Object.keys(mainData)
        .filter(col => col !== pkColumn)
        .map((col, idx) => `"${col}" = $${idx + 1}`)
        .join(", ");
      const updateValues = Object.keys(mainData)
        .filter(col => col !== pkColumn)
        .map(col => mainData[col]);
      
      // updated_at 컬럼 존재 여부 확인
      const hasUpdatedAt = await client.query(`
        SELECT 1 FROM information_schema.columns 
        WHERE table_name = $1 AND column_name = 'updated_at'
      `, [mainTableName]);
      const updatedAtClause = hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 ? ", updated_at = NOW()" : "";

      const updateQuery = `
        UPDATE "${mainTableName}"
        SET ${updateColumns}${updatedAtClause}
        WHERE "${pkColumn}" = $${updateValues.length + 1}
        ${companyCode !== "*" ? `AND company_code = $${updateValues.length + 2}` : ""}
        RETURNING *
      `;
      
      const updateParams = companyCode !== "*" 
        ? [...updateValues, pkValue, companyCode]
        : [...updateValues, pkValue];
      
      logger.info("메인 테이블 UPDATE:", { query: updateQuery, paramsCount: updateParams.length });
      mainResult = await client.query(updateQuery, updateParams);
    } else {
      // INSERT
      const columns = Object.keys(mainData).map(col => `"${col}"`).join(", ");
      const placeholders = Object.keys(mainData).map((_, idx) => `$${idx + 1}`).join(", ");
      const values = Object.values(mainData);

      // updated_at 컬럼 존재 여부 확인
      const hasUpdatedAt = await client.query(`
        SELECT 1 FROM information_schema.columns 
        WHERE table_name = $1 AND column_name = 'updated_at'
      `, [mainTableName]);
      const updatedAtClause = hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 ? ", updated_at = NOW()" : "";

      const updateSetClause = Object.keys(mainData)
        .filter(col => col !== pkColumn)
        .map(col => `"${col}" = EXCLUDED."${col}"`)
        .join(", ");

      const insertQuery = `
        INSERT INTO "${mainTableName}" (${columns})
        VALUES (${placeholders})
        ON CONFLICT ("${pkColumn}") DO UPDATE SET
          ${updateSetClause}${updatedAtClause}
        RETURNING *
      `;

      logger.info("메인 테이블 INSERT/UPSERT:", { query: insertQuery, paramsCount: values.length });
      mainResult = await client.query(insertQuery, values);
    }

    if (mainResult.rowCount === 0) {
      throw new Error("메인 테이블 저장 실패");
    }

    const savedMainData = mainResult.rows[0];
    const savedPkValue = savedMainData[pkColumn];
    logger.info("메인 테이블 저장 완료:", { pkColumn, savedPkValue });

    // 2. 서브 테이블 저장
    const subTableResults: any[] = [];

    for (const subTableConfig of subTables || []) {
      const { tableName, linkColumn, items, options } = subTableConfig;

      // saveMainAsFirst가 활성화된 경우, items가 비어있어도 메인 데이터를 서브 테이블에 저장해야 함
      const hasSaveMainAsFirst = options?.saveMainAsFirst && 
                                  options?.mainFieldMappings && 
                                  options.mainFieldMappings.length > 0;
      
      if (!tableName || (!items?.length && !hasSaveMainAsFirst)) {
        logger.info(`서브 테이블 ${tableName} 스킵: 데이터 없음 (saveMainAsFirst: ${hasSaveMainAsFirst})`);
        continue;
      }

      logger.info(`서브 테이블 ${tableName} 저장 시작:`, {
        itemsCount: items?.length || 0,
        linkColumn,
        options,
        hasSaveMainAsFirst,
      });

      // 기존 데이터 삭제 옵션
      if (options?.deleteExistingBefore && linkColumn?.subColumn) {
        const deleteQuery = options?.deleteOnlySubItems && options?.mainMarkerColumn
          ? `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1 AND "${options.mainMarkerColumn}" = $2`
          : `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1`;
        
        const deleteParams = options?.deleteOnlySubItems && options?.mainMarkerColumn
          ? [savedPkValue, options.subMarkerValue ?? false]
          : [savedPkValue];

        logger.info(`서브 테이블 ${tableName} 기존 데이터 삭제:`, { deleteQuery, deleteParams });
        await client.query(deleteQuery, deleteParams);
      }

      // 메인 데이터도 서브 테이블에 저장 (옵션)
      // mainFieldMappings가 비어 있으면 건너뜀 (필수 컬럼 누락 방지)
      logger.info(`saveMainAsFirst 옵션 확인:`, {
        saveMainAsFirst: options?.saveMainAsFirst,
        mainFieldMappings: options?.mainFieldMappings,
        mainFieldMappingsLength: options?.mainFieldMappings?.length,
        linkColumn,
        mainDataKeys: Object.keys(mainData),
      });
      if (options?.saveMainAsFirst && options?.mainFieldMappings && options.mainFieldMappings.length > 0 && linkColumn?.subColumn) {
        const mainSubItem: Record<string, any> = {
          [linkColumn.subColumn]: savedPkValue,
        };

        // 메인 필드 매핑 적용
        for (const mapping of options.mainFieldMappings) {
          if (mapping.formField && mapping.targetColumn) {
            mainSubItem[mapping.targetColumn] = mainData[mapping.formField];
          }
        }

        // 메인 마커 설정
        if (options.mainMarkerColumn) {
          mainSubItem[options.mainMarkerColumn] = options.mainMarkerValue ?? true;
        }

        // company_code 추가
        if (companyCode !== "*") {
          mainSubItem.company_code = companyCode;
        }

        // 먼저 기존 데이터 존재 여부 확인 (user_id + is_primary 조합)
        const checkQuery = `
          SELECT * FROM "${tableName}" 
          WHERE "${linkColumn.subColumn}" = $1 
          ${options.mainMarkerColumn ? `AND "${options.mainMarkerColumn}" = $2` : ""}
          ${companyCode !== "*" ? `AND company_code = $${options.mainMarkerColumn ? 3 : 2}` : ""}
          LIMIT 1
        `;
        const checkParams: any[] = [savedPkValue];
        if (options.mainMarkerColumn) {
          checkParams.push(options.mainMarkerValue ?? true);
        }
        if (companyCode !== "*") {
          checkParams.push(companyCode);
        }
        
        const existingResult = await client.query(checkQuery, checkParams);
        
        if (existingResult.rows.length > 0) {
          // UPDATE
          const updateColumns = Object.keys(mainSubItem)
            .filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code")
            .map((col, idx) => `"${col}" = $${idx + 1}`)
            .join(", ");
          
          const updateValues = Object.keys(mainSubItem)
            .filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code")
            .map(col => mainSubItem[col]);
          
          if (updateColumns) {
            const updateQuery = `
              UPDATE "${tableName}"
              SET ${updateColumns}
              WHERE "${linkColumn.subColumn}" = $${updateValues.length + 1}
              ${options.mainMarkerColumn ? `AND "${options.mainMarkerColumn}" = $${updateValues.length + 2}` : ""}
              ${companyCode !== "*" ? `AND company_code = $${updateValues.length + (options.mainMarkerColumn ? 3 : 2)}` : ""}
              RETURNING *
            `;
            const updateParams = [...updateValues, savedPkValue];
            if (options.mainMarkerColumn) {
              updateParams.push(options.mainMarkerValue ?? true);
            }
            if (companyCode !== "*") {
              updateParams.push(companyCode);
            }

            const updateResult = await client.query(updateQuery, updateParams);
            subTableResults.push({ tableName, type: "main", data: updateResult.rows[0] });
          } else {
            subTableResults.push({ tableName, type: "main", data: existingResult.rows[0] });
          }
        } else {
          // INSERT
          const mainSubColumns = Object.keys(mainSubItem).map(col => `"${col}"`).join(", ");
          const mainSubPlaceholders = Object.keys(mainSubItem).map((_, idx) => `$${idx + 1}`).join(", ");
          const mainSubValues = Object.values(mainSubItem);

          const insertQuery = `
            INSERT INTO "${tableName}" (${mainSubColumns})
            VALUES (${mainSubPlaceholders})
            RETURNING *
          `;

          const insertResult = await client.query(insertQuery, mainSubValues);
          subTableResults.push({ tableName, type: "main", data: insertResult.rows[0] });
        }
      }

      // 서브 아이템들 저장
      for (const item of items) {
        // 연결 컬럼 값 설정
        if (linkColumn?.subColumn) {
          item[linkColumn.subColumn] = savedPkValue;
        }

        // company_code 추가
        if (companyCode !== "*" && !item.company_code) {
          item.company_code = companyCode;
        }

        const subColumns = Object.keys(item).map(col => `"${col}"`).join(", ");
        const subPlaceholders = Object.keys(item).map((_, idx) => `$${idx + 1}`).join(", ");
        const subValues = Object.values(item);

        const subInsertQuery = `
          INSERT INTO "${tableName}" (${subColumns})
          VALUES (${subPlaceholders})
          RETURNING *
        `;

        logger.info(`서브 테이블 ${tableName} 아이템 저장:`, { subInsertQuery, subValuesCount: subValues.length });
        const subResult = await client.query(subInsertQuery, subValues);
        subTableResults.push({ tableName, type: "sub", data: subResult.rows[0] });
      }

      logger.info(`서브 테이블 ${tableName} 저장 완료`);
    }

    await client.query("COMMIT");

    logger.info("=== 다중 테이블 저장 완료 ===", {
      mainTable: mainTableName,
      mainPk: savedPkValue,
      subTableResultsCount: subTableResults.length,
    });

    auditLogService.log({
      companyCode: req.user?.companyCode || "",
      userId: req.user?.userId || "",
      userName: req.user?.userName || "",
      action: isUpdate ? "UPDATE" : "CREATE",
      resourceType: "DATA",
      resourceId: savedPkValue?.toString() || "",
      resourceName: mainTableName,
      tableName: mainTableName,
      summary: `${mainTableName} 데이터 ${isUpdate ? "수정" : "생성"}${subTableResults.length > 0 ? ` (서브 테이블 ${subTableResults.length}건)` : ""}`,
      changes: { after: mainData },
      ipAddress: getClientIp(req),
      requestPath: req.originalUrl,
    });

    res.json({
      success: true,
      message: "다중 테이블 저장이 완료되었습니다.",
      data: {
        main: savedMainData,
        subTables: subTableResults,
      },
    });
  } catch (error: any) {
    await client.query("ROLLBACK");

    logger.error("다중 테이블 저장 실패:", {
      message: error.message,
      stack: error.stack,
    });

    res.status(500).json({
      success: false,
      message: error.message || "다중 테이블 저장에 실패했습니다.",
      error: error.message,
    });
  } finally {
    client.release();
  }
}

/**
 * 두 테이블 간 엔티티 관계 조회
 * table_type_columns의 entity/category 타입 설정을 기반으로 두 테이블 간의 관계를 조회
 */
export async function getTableEntityRelations(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { leftTable, rightTable } = req.query;

    if (!leftTable || !rightTable) {
      res.status(400).json({
        success: false,
        message: "leftTable과 rightTable 파라미터가 필요합니다.",
      });
      return;
    }

    logger.info("=== 테이블 엔티티 관계 조회 ===", { leftTable, rightTable });

    // 두 테이블의 컬럼 라벨 정보 조회
    const columnLabelsQuery = `
      SELECT 
        table_name,
        column_name,
        column_label,
        input_type as web_type,
        detail_settings
      FROM table_type_columns
      WHERE table_name IN ($1, $2)
        AND input_type IN ('entity', 'category')
        AND company_code = '*'
    `;

    const result = await query(columnLabelsQuery, [leftTable, rightTable]);

    // 관계 분석
    const relations: Array<{
      fromTable: string;
      fromColumn: string;
      toTable: string;
      toColumn: string;
      relationType: string;
    }> = [];

    for (const row of result) {
      try {
        const detailSettings = typeof row.detail_settings === "string" 
          ? JSON.parse(row.detail_settings) 
          : row.detail_settings;

        if (detailSettings && detailSettings.referenceTable) {
          const refTable = detailSettings.referenceTable;
          const refColumn = detailSettings.referenceColumn || "id";
          
          // leftTable과 rightTable 간의 관계인지 확인
          if (
            (row.table_name === leftTable && refTable === rightTable) ||
            (row.table_name === rightTable && refTable === leftTable)
          ) {
            relations.push({
              fromTable: row.table_name,
              fromColumn: row.column_name,
              toTable: refTable,
              toColumn: refColumn,
              relationType: row.web_type,
            });
          }
        }
      } catch (parseError) {
        logger.warn("detail_settings 파싱 오류:", { 
          table: row.table_name, 
          column: row.column_name,
          error: parseError 
        });
      }
    }

    logger.info("테이블 엔티티 관계 조회 완료", { 
      leftTable, 
      rightTable, 
      relationsCount: relations.length 
    });

    res.json({
      success: true,
      data: {
        leftTable,
        rightTable,
        relations,
      },
    });
  } catch (error: any) {
    logger.error("테이블 엔티티 관계 조회 실패:", error);
    res.status(500).json({
      success: false,
      message: "테이블 엔티티 관계 조회에 실패했습니다.",
      error: error.message,
    });
  }
}

/**
 * 현재 테이블을 참조(FK로 연결)하는 테이블 목록 조회
 * GET /api/table-management/columns/:tableName/referenced-by
 *
 * table_type_columns에서 reference_table이 현재 테이블인 레코드를 찾아서
 * 해당 테이블과 FK 컬럼 정보를 반환합니다.
 * 
 * 우선순위: 현재 사용자의 company_code > 공통('*')
 */
export async function getReferencedByTables(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    // 현재 사용자의 회사 코드 (없으면 '*' 사용)
    const userCompanyCode = req.user?.companyCode || "*";

    logger.info(
      `=== 테이블 참조 관계 조회 시작: ${tableName} 을 참조하는 테이블 (회사코드: ${userCompanyCode}) ===`
    );

    if (!tableName) {
      const response: ApiResponse<null> = {
        success: false,
        message: "tableName 파라미터가 필요합니다.",
        error: {
          code: "MISSING_PARAMETERS",
          details: "tableName 경로 파라미터가 필요합니다.",
        },
      };
      res.status(400).json(response);
      return;
    }

    // table_type_columns에서 reference_table이 현재 테이블인 레코드 조회
    // input_type이 'entity'인 것만 조회 (실제 FK 관계)
    // 우선순위: 현재 사용자의 company_code > 공통('*')
    // ROW_NUMBER를 사용해서 같은 테이블/컬럼 조합에서 회사코드 우선순위로 하나만 선택
    const sqlQuery = `
      WITH ranked AS (
        SELECT 
          ttc.table_name,
          ttc.column_name,
          ttc.column_label,
          ttc.reference_table,
          ttc.reference_column,
          ttc.display_column,
          ttc.company_code,
          ROW_NUMBER() OVER (
            PARTITION BY ttc.table_name, ttc.column_name 
            ORDER BY CASE WHEN ttc.company_code = $2 THEN 1 ELSE 2 END
          ) as rn
        FROM table_type_columns ttc
        WHERE ttc.reference_table = $1
          AND ttc.input_type = 'entity'
          AND ttc.company_code IN ($2, '*')
      )
      SELECT DISTINCT 
        table_name,
        column_name,
        column_label,
        reference_table,
        reference_column,
        display_column,
        table_name as table_label
      FROM ranked
      WHERE rn = 1
      ORDER BY table_name, column_name
    `;

    const result = await query(sqlQuery, [tableName, userCompanyCode]);

    const referencedByTables = result.map((row: any) => ({
      tableName: row.table_name,
      tableLabel: row.table_label,
      columnName: row.column_name,
      columnLabel: row.column_label,
      referenceTable: row.reference_table,
      referenceColumn: row.reference_column || "id",
      displayColumn: row.display_column,
    }));

    logger.info(
      `테이블 참조 관계 조회 완료: ${referencedByTables.length}개 발견 (회사코드: ${userCompanyCode})`
    );

    const response: ApiResponse<any> = {
      success: true,
      message: `${referencedByTables.length}개의 테이블이 ${tableName}을 참조합니다.`,
      data: referencedByTables,
    };

    res.status(200).json(response);
  } catch (error) {
    logger.error("테이블 참조 관계 조회 중 오류 발생:", error);

    const response: ApiResponse<null> = {
      success: false,
      message: "테이블 참조 관계 조회 중 오류가 발생했습니다.",
      error: {
        code: "REFERENCED_BY_ERROR",
        details: error instanceof Error ? error.message : "Unknown error",
      },
    };

    res.status(500).json(response);
  }
}

// ========================================
// PK / 인덱스 관리 API
// ========================================

/**
 * PK/인덱스 상태 조회
 * GET /api/table-management/tables/:tableName/constraints
 */
export async function getTableConstraints(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;

    if (!tableName) {
      res.status(400).json({ success: false, message: "테이블명이 필요합니다." });
      return;
    }

    // PK 조회
    const pkResult = await query<any>(
      `SELECT tc.conname AS constraint_name,
              array_agg(a.attname ORDER BY x.n) AS columns
       FROM pg_constraint tc
       JOIN pg_class c ON tc.conrelid = c.oid
       JOIN pg_namespace ns ON c.relnamespace = ns.oid
       CROSS JOIN LATERAL unnest(tc.conkey) WITH ORDINALITY AS x(attnum, n)
       JOIN pg_attribute a ON a.attrelid = tc.conrelid AND a.attnum = x.attnum
       WHERE ns.nspname = 'public' AND c.relname = $1 AND tc.contype = 'p'
       GROUP BY tc.conname`,
      [tableName]
    );

    // array_agg 결과가 문자열로 올 수 있으므로 안전하게 배열로 변환
    const parseColumns = (cols: any): string[] => {
      if (Array.isArray(cols)) return cols;
      if (typeof cols === "string") {
        // PostgreSQL 배열 형식: {col1,col2}
        return cols.replace(/[{}]/g, "").split(",").filter(Boolean);
      }
      return [];
    };

    const primaryKey = pkResult.length > 0
      ? { name: pkResult[0].constraint_name, columns: parseColumns(pkResult[0].columns) }
      : { name: "", columns: [] };

    // 인덱스 조회 (PK 인덱스 제외)
    const indexResult = await query<any>(
      `SELECT i.relname AS index_name,
              ix.indisunique AS is_unique,
              array_agg(a.attname ORDER BY x.n) AS columns
       FROM pg_index ix
       JOIN pg_class t ON ix.indrelid = t.oid
       JOIN pg_class i ON ix.indexrelid = i.oid
       JOIN pg_namespace ns ON t.relnamespace = ns.oid
       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 ns.nspname = 'public' AND t.relname = $1
         AND ix.indisprimary = false
       GROUP BY i.relname, ix.indisunique
       ORDER BY i.relname`,
      [tableName]
    );

    const indexes = indexResult.map((row: any) => ({
      name: row.index_name,
      columns: parseColumns(row.columns),
      isUnique: row.is_unique,
    }));

    logger.info(`제약조건 조회: ${tableName} - PK: ${primaryKey.columns.join(",")}, 인덱스: ${indexes.length}개`);

    res.status(200).json({
      success: true,
      data: { primaryKey, indexes },
    });
  } catch (error) {
    logger.error("제약조건 조회 오류:", error);
    res.status(500).json({
      success: false,
      message: "제약조건 조회 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * PK 설정
 * PUT /api/table-management/tables/:tableName/primary-key
 */
export async function setTablePrimaryKey(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { columns } = req.body;

    if (!tableName || !columns || !Array.isArray(columns) || columns.length === 0) {
      res.status(400).json({ success: false, message: "테이블명과 PK 컬럼 배열이 필요합니다." });
      return;
    }

    logger.info(`PK 설정: ${tableName} → [${columns.join(", ")}]`);

    // 기존 PK 제약조건 이름 조회
    const existingPk = await query<any>(
      `SELECT conname FROM pg_constraint tc
       JOIN pg_class c ON tc.conrelid = c.oid
       JOIN pg_namespace ns ON c.relnamespace = ns.oid
       WHERE ns.nspname = 'public' AND c.relname = $1 AND tc.contype = 'p'`,
      [tableName]
    );

    // 기존 PK 삭제
    if (existingPk.length > 0) {
      const dropSql = `ALTER TABLE "public"."${tableName}" DROP CONSTRAINT "${existingPk[0].conname}"`;
      logger.info(`기존 PK 삭제: ${dropSql}`);
      await query(dropSql);
    }

    // 새 PK 추가
    const colList = columns.map((c: string) => `"${c}"`).join(", ");
    const addSql = `ALTER TABLE "public"."${tableName}" ADD PRIMARY KEY (${colList})`;
    logger.info(`새 PK 추가: ${addSql}`);
    await query(addSql);

    res.status(200).json({
      success: true,
      message: `PK가 설정되었습니다: ${columns.join(", ")}`,
    });
  } catch (error) {
    logger.error("PK 설정 오류:", error);
    res.status(500).json({
      success: false,
      message: "PK 설정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 인덱스 토글 (생성/삭제)
 * POST /api/table-management/tables/:tableName/indexes
 */
export async function toggleTableIndex(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName } = req.params;
    const { columnName, indexType, action } = req.body;

    if (!tableName || !columnName || !indexType || !action) {
      res.status(400).json({
        success: false,
        message: "tableName, columnName, indexType(index|unique), action(create|drop)이 필요합니다.",
      });
      return;
    }

    const indexName = `idx_${tableName}_${columnName}${indexType === "unique" ? "_uq" : ""}`;

    logger.info(`인덱스 ${action}: ${indexName} (${indexType})`);

    if (action === "create") {
      let indexColumns = `"${columnName}"`;

      // 유니크 인덱스: company_code 컬럼이 있으면 복합 유니크 (회사별 유니크 보장)
      if (indexType === "unique") {
        const hasCompanyCode = await query(
          `SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1 AND column_name = 'company_code'`,
          [tableName]
        );
        if (hasCompanyCode.length > 0) {
          indexColumns = `"company_code", "${columnName}"`;
          logger.info(`멀티테넌시: company_code + ${columnName} 복합 유니크 인덱스 생성`);
        }
      }

      const uniqueClause = indexType === "unique" ? "UNIQUE " : "";
      const sql = `CREATE ${uniqueClause}INDEX IF NOT EXISTS "${indexName}" ON "public"."${tableName}" (${indexColumns})`;
      logger.info(`인덱스 생성: ${sql}`);
      await query(sql);
    } else if (action === "drop") {
      const sql = `DROP INDEX IF EXISTS "public"."${indexName}"`;
      logger.info(`인덱스 삭제: ${sql}`);
      await query(sql);
    } else {
      res.status(400).json({ success: false, message: "action은 create 또는 drop이어야 합니다." });
      return;
    }

    res.status(200).json({
      success: true,
      message: action === "create"
        ? `인덱스가 생성되었습니다: ${indexName}`
        : `인덱스가 삭제되었습니다: ${indexName}`,
    });
  } catch (error: any) {
    logger.error("인덱스 토글 오류:", error);

    const errMsg = error.message || "";
    let userMessage = "인덱스 설정 중 오류가 발생했습니다.";
    let duplicates: any[] = [];

    // 중복 데이터로 인한 UNIQUE 인덱스 생성 실패
    if (
      errMsg.includes("could not create unique index") ||
      errMsg.includes("duplicate key")
    ) {
      const { columnName, tableName } = { ...req.params, ...req.body };
      try {
        duplicates = await query(
          `SELECT company_code, "${columnName}", COUNT(*) as cnt FROM "${tableName}" GROUP BY company_code, "${columnName}" HAVING COUNT(*) > 1 ORDER BY cnt DESC LIMIT 10`
        );
      } catch {
        try {
          duplicates = await query(
            `SELECT "${columnName}", COUNT(*) as cnt FROM "${tableName}" GROUP BY "${columnName}" HAVING COUNT(*) > 1 ORDER BY cnt DESC LIMIT 10`
          );
        } catch { /* 중복 조회 실패 시 무시 */ }
      }

      const dupDetails = duplicates.length > 0
        ? duplicates.map((d: any) => {
            const company = d.company_code ? `[${d.company_code}] ` : "";
            return `${company}"${d[columnName] ?? 'NULL'}" (${d.cnt}건)`;
          }).join(", ")
        : "";

      userMessage = dupDetails
        ? `[${columnName}] 컬럼에 같은 회사 내 중복 데이터가 있어 유니크 인덱스를 생성할 수 없습니다. 중복 값: ${dupDetails}`
        : `[${columnName}] 컬럼에 중복 데이터가 있어 유니크 인덱스를 생성할 수 없습니다. 중복 데이터를 먼저 정리해주세요.`;
    }

    res.status(500).json({
      success: false,
      message: userMessage,
      error: errMsg,
      duplicates,
    });
  }
}

/**
 * NOT NULL 토글 (회사별 소프트 제약조건)
 * PUT /api/table-management/tables/:tableName/columns/:columnName/nullable
 * 
 * DB 레벨 ALTER TABLE 대신 table_type_columns.is_nullable을 회사별로 관리한다.
 * 멀티테넌시 환경에서 회사 A는 NOT NULL, 회사 B는 NULL 허용이 가능하다.
 */
export async function toggleColumnNullable(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, columnName } = req.params;
    const { nullable } = req.body;
    const companyCode = req.user?.companyCode || "*";

    if (!tableName || !columnName || typeof nullable !== "boolean") {
      res.status(400).json({
        success: false,
        message: "tableName, columnName, nullable(boolean)이 필요합니다.",
      });
      return;
    }

    // is_nullable 값: 'Y' = NULL 허용, 'N' = NOT NULL
    const isNullableValue = nullable ? "Y" : "N";

    if (!nullable) {
      // NOT NULL 설정 전 - 해당 회사의 기존 데이터에 NULL이 있는지 확인
      const hasCompanyCode = await query<{ column_name: string }>(
        `SELECT column_name FROM information_schema.columns 
         WHERE table_name = $1 AND column_name = 'company_code'`,
        [tableName]
      );

      if (hasCompanyCode.length > 0) {
        const nullCheckQuery = companyCode === "*"
          ? `SELECT COUNT(*) as null_count FROM "${tableName}" WHERE "${columnName}" IS NULL`
          : `SELECT COUNT(*) as null_count FROM "${tableName}" WHERE "${columnName}" IS NULL AND company_code = $1`;
        const nullCheckParams = companyCode === "*" ? [] : [companyCode];

        const nullCheckResult = await query<{ null_count: string }>(nullCheckQuery, nullCheckParams);
        const nullCount = parseInt(nullCheckResult[0]?.null_count || "0", 10);

        if (nullCount > 0) {
          logger.warn(`NOT NULL 설정 불가 - 해당 회사에 NULL 데이터 존재: ${tableName}.${columnName}`, {
            companyCode,
            nullCount,
          });

          res.status(400).json({
            success: false,
            message: `현재 회사 데이터에 NULL 값이 ${nullCount}건 존재합니다. NULL 데이터를 먼저 정리해주세요.`,
          });
          return;
        }
      }
    }

    // table_type_columns에 회사별 is_nullable 설정 UPSERT
    await query(
      `INSERT INTO table_type_columns (table_name, column_name, is_nullable, company_code, created_date, updated_date)
       VALUES ($1, $2, $3, $4, NOW(), NOW())
       ON CONFLICT (table_name, column_name, company_code)
       DO UPDATE SET is_nullable = $3, updated_date = NOW()`,
      [tableName, columnName, isNullableValue, companyCode]
    );

    logger.info(`NOT NULL 소프트 제약조건 변경: ${tableName}.${columnName} → is_nullable=${isNullableValue}`, {
      companyCode,
    });

    res.status(200).json({
      success: true,
      message: nullable
        ? `${columnName} 컬럼의 NOT NULL 제약이 해제되었습니다.`
        : `${columnName} 컬럼이 NOT NULL로 설정되었습니다.`,
    });
  } catch (error: any) {
    logger.error("NOT NULL 토글 오류:", error);

    res.status(500).json({
      success: false,
      message: "NOT NULL 설정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * UNIQUE 토글 (회사별 소프트 제약조건)
 * PUT /api/table-management/tables/:tableName/columns/:columnName/unique
 *
 * DB 레벨 인덱스 대신 table_type_columns.is_unique를 회사별로 관리한다.
 * 저장 시 앱 레벨에서 중복 검증을 수행한다.
 */
export async function toggleColumnUnique(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, columnName } = req.params;
    const { unique } = req.body;
    const companyCode = req.user?.companyCode || "*";

    if (!tableName || !columnName || typeof unique !== "boolean") {
      res.status(400).json({
        success: false,
        message: "tableName, columnName, unique(boolean)이 필요합니다.",
      });
      return;
    }

    const isUniqueValue = unique ? "Y" : "N";

    if (unique) {
      // UNIQUE 설정 전 - 해당 회사의 기존 데이터에 중복이 있는지 확인
      const hasCompanyCode = await query<{ column_name: string }>(
        `SELECT column_name FROM information_schema.columns 
         WHERE table_name = $1 AND column_name = 'company_code'`,
        [tableName]
      );

      if (hasCompanyCode.length > 0) {
        const dupQuery = companyCode === "*"
          ? `SELECT "${columnName}", COUNT(*) as cnt FROM "${tableName}" WHERE "${columnName}" IS NOT NULL GROUP BY "${columnName}" HAVING COUNT(*) > 1 LIMIT 10`
          : `SELECT "${columnName}", COUNT(*) as cnt FROM "${tableName}" WHERE "${columnName}" IS NOT NULL AND company_code = $1 GROUP BY "${columnName}" HAVING COUNT(*) > 1 LIMIT 10`;
        const dupParams = companyCode === "*" ? [] : [companyCode];

        const dupResult = await query<any>(dupQuery, dupParams);

        if (dupResult.length > 0) {
          const dupDetails = dupResult
            .map((d: any) => `"${d[columnName]}" (${d.cnt}건)`)
            .join(", ");

          res.status(400).json({
            success: false,
            message: `현재 회사 데이터에 중복 값이 존재합니다. 중복 데이터를 먼저 정리해주세요. 중복 값: ${dupDetails}`,
          });
          return;
        }
      }
    }

    // table_type_columns에 회사별 is_unique 설정 UPSERT
    await query(
      `INSERT INTO table_type_columns (table_name, column_name, is_unique, company_code, created_date, updated_date)
       VALUES ($1, $2, $3, $4, NOW(), NOW())
       ON CONFLICT (table_name, column_name, company_code)
       DO UPDATE SET is_unique = $3, updated_date = NOW()`,
      [tableName, columnName, isUniqueValue, companyCode]
    );

    logger.info(`UNIQUE 소프트 제약조건 변경: ${tableName}.${columnName} → is_unique=${isUniqueValue}`, {
      companyCode,
    });

    res.status(200).json({
      success: true,
      message: unique
        ? `${columnName} 컬럼이 UNIQUE로 설정되었습니다.`
        : `${columnName} 컬럼의 UNIQUE 제약이 해제되었습니다.`,
    });
  } catch (error: any) {
    logger.error("UNIQUE 토글 오류:", error);

    res.status(500).json({
      success: false,
      message: "UNIQUE 설정 중 오류가 발생했습니다.",
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

/**
 * 회사별 채번 타입 컬럼 조회 (카테고리 패턴과 동일)
 *
 * @route GET /api/table-management/numbering-columns
 */
export async function getNumberingColumnsByCompany(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const companyCode = req.user?.companyCode;

    logger.info("회사별 채번 컬럼 조회 요청", { companyCode });

    if (!companyCode) {
      res.status(400).json({
        success: false,
        message: "회사 코드를 확인할 수 없습니다.",
      });
      return;
    }

    const { getPool } = await import("../database/db");
    const pool = getPool();

    const targetCompanyCode = companyCode === "*" ? "*" : companyCode;

    const columnsQuery = `
      SELECT DISTINCT
        ttc.table_name AS "tableName",
        COALESCE(
          tl.table_label,
          initcap(replace(ttc.table_name, '_', ' '))
        ) AS "tableLabel",
        ttc.column_name AS "columnName",
        COALESCE(
          ttc.column_label,
          initcap(replace(ttc.column_name, '_', ' '))
        ) AS "columnLabel",
        ttc.input_type AS "inputType"
      FROM table_type_columns ttc
      LEFT JOIN table_labels tl
        ON ttc.table_name = tl.table_name
      WHERE ttc.input_type = 'numbering'
        AND ttc.company_code = $1
      ORDER BY ttc.table_name, ttc.column_name
    `;

    const columnsResult = await pool.query(columnsQuery, [targetCompanyCode]);

    logger.info("채번 컬럼 조회 완료", {
      companyCode,
      rowCount: columnsResult.rows.length,
    });

    res.json({
      success: true,
      data: columnsResult.rows,
    });
  } catch (error: any) {
    logger.error("채번 컬럼 조회 실패", { error: error.message });
    res.status(500).json({
      success: false,
      message: "채번 컬럼 조회 중 오류가 발생했습니다.",
      error: error.message,
    });
  }
}

/**
 * 엑셀 업로드 전 데이터 검증
 * POST /api/table-management/validate-excel
 * Body: { tableName, data: Record<string,any>[] }
 */
export async function validateExcelData(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const { tableName, data } = req.body as {
      tableName: string;
      data: Record<string, any>[];
    };
    const companyCode = req.user?.companyCode || "*";

    if (!tableName || !Array.isArray(data) || data.length === 0) {
      res.status(400).json({ success: false, message: "tableName과 data 배열이 필요합니다." });
      return;
    }

    const effectiveCompanyCode =
      companyCode === "*" && data[0]?.company_code && data[0].company_code !== "*"
        ? data[0].company_code
        : companyCode;

    let constraintCols = await query<{
      column_name: string;
      column_label: string;
      is_nullable: string;
      is_unique: string;
    }>(
      `SELECT column_name,
              COALESCE(column_label, column_name) as column_label,
              COALESCE(is_nullable, 'Y') as is_nullable,
              COALESCE(is_unique, 'N') as is_unique
       FROM table_type_columns
       WHERE table_name = $1 AND company_code = $2`,
      [tableName, effectiveCompanyCode]
    );

    if (constraintCols.length === 0 && effectiveCompanyCode !== "*") {
      constraintCols = await query(
        `SELECT column_name,
                COALESCE(column_label, column_name) as column_label,
                COALESCE(is_nullable, 'Y') as is_nullable,
                COALESCE(is_unique, 'N') as is_unique
         FROM table_type_columns
         WHERE table_name = $1 AND company_code = '*'`,
        [tableName]
      );
    }

    const autoGenCols = ["id", "created_date", "updated_date", "writer", "company_code"];
    const notNullCols = constraintCols.filter((c) => c.is_nullable === "N" && !autoGenCols.includes(c.column_name));
    const uniqueCols = constraintCols.filter((c) => c.is_unique === "Y" && !autoGenCols.includes(c.column_name));

    const notNullErrors: { row: number; column: string; label: string }[] = [];
    const uniqueInExcelErrors: { rows: number[]; column: string; label: string; value: string }[] = [];
    const uniqueInDbErrors: { row: number; column: string; label: string; value: string }[] = [];

    // NOT NULL 검증
    for (const col of notNullCols) {
      for (let i = 0; i < data.length; i++) {
        const val = data[i][col.column_name];
        if (val === null || val === undefined || String(val).trim() === "") {
          notNullErrors.push({ row: i + 1, column: col.column_name, label: col.column_label });
        }
      }
    }

    // UNIQUE: 엑셀 내부 중복
    for (const col of uniqueCols) {
      const seen = new Map<string, number[]>();
      for (let i = 0; i < data.length; i++) {
        const val = data[i][col.column_name];
        if (val === null || val === undefined || String(val).trim() === "") continue;
        const key = String(val).trim();
        if (!seen.has(key)) seen.set(key, []);
        seen.get(key)!.push(i + 1);
      }
      for (const [value, rows] of seen) {
        if (rows.length > 1) {
          uniqueInExcelErrors.push({ rows, column: col.column_name, label: col.column_label, value });
        }
      }
    }

    // UNIQUE: DB 기존 데이터와 중복
    const hasCompanyCode = await query(
      `SELECT column_name FROM information_schema.columns
       WHERE table_name = $1 AND column_name = 'company_code'`,
      [tableName]
    );

    for (const col of uniqueCols) {
      const values = [...new Set(
        data
          .map((row) => row[col.column_name])
          .filter((v) => v !== null && v !== undefined && String(v).trim() !== "")
          .map((v) => String(v).trim())
      )];
      if (values.length === 0) continue;

      let dupQuery: string;
      let dupParams: any[];
      const targetCompany = data[0]?.company_code || (effectiveCompanyCode !== "*" ? effectiveCompanyCode : null);

      if (hasCompanyCode.length > 0 && targetCompany) {
        dupQuery = `SELECT "${col.column_name}" FROM "${tableName}" WHERE "${col.column_name}" = ANY($1) AND company_code = $2`;
        dupParams = [values, targetCompany];
      } else {
        dupQuery = `SELECT "${col.column_name}" FROM "${tableName}" WHERE "${col.column_name}" = ANY($1)`;
        dupParams = [values];
      }

      const existingRows = await query<Record<string, any>>(dupQuery, dupParams);
      const existingSet = new Set(existingRows.map((r) => String(r[col.column_name]).trim()));

      for (let i = 0; i < data.length; i++) {
        const val = data[i][col.column_name];
        if (val === null || val === undefined || String(val).trim() === "") continue;
        if (existingSet.has(String(val).trim())) {
          uniqueInDbErrors.push({ row: i + 1, column: col.column_name, label: col.column_label, value: String(val) });
        }
      }
    }

    const isValid = notNullErrors.length === 0 && uniqueInExcelErrors.length === 0 && uniqueInDbErrors.length === 0;

    res.json({
      success: true,
      data: {
        isValid,
        notNullErrors,
        uniqueInExcelErrors,
        uniqueInDbErrors,
        summary: {
          notNull: notNullErrors.length,
          uniqueInExcel: uniqueInExcelErrors.length,
          uniqueInDb: uniqueInDbErrors.length,
        },
      },
    });
  } catch (error: any) {
    logger.error("엑셀 데이터 검증 오류:", error);
    res.status(500).json({ success: false, message: "데이터 검증 중 오류가 발생했습니다." });
  }
}
