/**
 * queryUtils.ts
 *
 * SQL 쿼리 관련 순수 유틸 함수 모음.
 *
 * [사용처]
 * - QueryManager.tsx : 쿼리 탭에서 안전성 검증 + 파라미터 자동 감지
 * - modals/QuerySettingsTab.tsx : 인캔버스 설정 모달의 데이터 연결 탭
 *
 * Phase 5-A에서 QueryManager의 인라인 함수를 이곳으로 추출.
 * 두 소비처가 동일 로직을 공유할 수 있도록 단일 소스로 유지한다.
 */

/** SQL 안전성 검증 결과 */
export interface QueryValidationResult {
  isValid: boolean;
  errorMessage: string | null;
}

/**
 * SELECT 전용 SQL 안전성 검증.
 * DML/DDL 키워드를 포함하거나 다중 쿼리를 감지하면 오류를 반환한다.
 */
export const validateQuerySafety = (sql: string): QueryValidationResult => {
  if (!sql || sql.trim() === "") {
    return { isValid: false, errorMessage: "쿼리를 입력해주세요." };
  }

  const dangerousKeywords = [
    "DELETE",
    "DROP",
    "TRUNCATE",
    "INSERT",
    "UPDATE",
    "ALTER",
    "CREATE",
    "REPLACE",
    "MERGE",
    "GRANT",
    "REVOKE",
    "EXECUTE",
    "EXEC",
    "CALL",
  ];

  const upperSql = sql.toUpperCase().trim();

  for (const keyword of dangerousKeywords) {
    const regex = new RegExp(`\\b${keyword}\\b`, "i");
    if (regex.test(upperSql)) {
      return {
        isValid: false,
        errorMessage: `보안상의 이유로 ${keyword} 명령어는 사용할 수 없습니다. SELECT 쿼리만 허용됩니다.`,
      };
    }
  }

  if (!upperSql.startsWith("SELECT") && !upperSql.startsWith("WITH")) {
    return {
      isValid: false,
      errorMessage: "SELECT 쿼리만 허용됩니다. 데이터 조회 용도로만 사용할 수 있습니다.",
    };
  }

  const semicolonCount = (sql.match(/;/g) || []).length;
  if (semicolonCount > 1 || (semicolonCount === 1 && !sql.trim().endsWith(";"))) {
    return {
      isValid: false,
      errorMessage: "보안상의 이유로 여러 개의 쿼리를 동시에 실행할 수 없습니다.",
    };
  }

  return { isValid: true, errorMessage: null };
};

/**
 * SQL에서 파라미터 플레이스홀더($1, $2 …) 추출.
 * 문자열 리터럴(단일따옴표) 내부는 제외하며, 등장 순서를 유지한다.
 */
export const detectParameters = (sql: string): string[] => {
  const withoutStrings = sql.replace(/'[^']*'/g, "");
  const matches = withoutStrings.match(/\$\d+/g);
  if (!matches) return [];

  const seen = new Set<string>();
  const result: string[] = [];
  for (const match of matches) {
    if (!seen.has(match)) {
      seen.add(match);
      result.push(match);
    }
  }
  return result;
};
