// ─────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────

export interface InjectionCheckResult {
  safe: boolean;
  reason?: string;
  severity?: 'low' | 'medium' | 'high';
}

// ─────────────────────────────────────────────
// Detection patterns
// ─────────────────────────────────────────────

/** 영문 인젝션 패턴 */
const EN_INJECTION_PATTERNS: RegExp[] = [
  /ignore\s+(previous|all|above)\s+instructions/i,
  /you\s+are\s+now\b/i,
  /system\s+prompt/i,
  /\bact\s+as\b/i,
  /pretend\s+to\s+be/i,
  /reveal\s+your\s+(instructions|prompt|system)/i,
  /\bdisregard\b/i,
  /\boverride\b/i,
];

/** 한국어 인젝션 패턴 */
const KO_INJECTION_PATTERNS: RegExp[] = [
  /이전\s*지시를?\s*무시/,
  /시스템\s*프롬프트/,
  /너는\s*이제/,
  /역할을?\s*바꿔/,
  /지시를?\s*무시/,
  /프롬프트를?\s*알려/,
  /관리자\s*모드/,
];

const ALL_INJECTION_PATTERNS: RegExp[] = [
  ...EN_INJECTION_PATTERNS,
  ...KO_INJECTION_PATTERNS,
];

// ─────────────────────────────────────────────
// Whitelist patterns
//
// 보험 도메인에서 정상적으로 사용되는 단어/맥락으로 인해
// 인젝션 패턴이 오탐될 수 있는 경우를 걸러냅니다.
//
// 규칙: 아래 컨텍스트 키워드 중 하나 이상이 함께 존재하면
//       해당 입력을 안전(safe)으로 판정합니다.
// ─────────────────────────────────────────────

/** 보험 도메인 컨텍스트 키워드 */
const INSURANCE_CONTEXT_KEYWORDS: RegExp[] = [
  /보험/,
  /약관/,
  /담보/,
  /보장/,
  /청구/,
  /보험료/,
  /피보험자/,
  /보험금/,
  /특약/,
  /면책/,
];

/**
 * 인젝션 패턴이 보험 도메인 컨텍스트와 함께 등장하는지 검사합니다.
 * 보험 컨텍스트가 확인되면 오탐으로 간주하여 true(안전) 를 반환합니다.
 */
function isInsuranceContext(input: string): boolean {
  return INSURANCE_CONTEXT_KEYWORDS.some((kw) => kw.test(input));
}

// ─────────────────────────────────────────────
// Public API
// ─────────────────────────────────────────────

/**
 * AI 쿼리 인젝션 여부를 검사합니다.
 *
 * 처리 순서:
 * 1. 입력 길이 200자 초과 → severity 'low' 경고 플래그 (차단하지 않음)
 * 2. 인젝션 패턴 매칭 확인
 *    2-a. 패턴 매칭 && 보험 컨텍스트 → 화이트리스트로 통과 (safe)
 *    2-b. 패턴 매칭 && 보험 컨텍스트 없음 → unsafe 반환
 * 3. 모든 체크 통과 → safe
 *
 * @param input  사용자 원본 입력 문자열
 */
export function checkInjection(input: string): InjectionCheckResult {
  // ── 1. 길이 경고 (차단하지 않음) ──────────────────
  if (input.length > 200) {
    // 이후 패턴 검사는 계속 진행하되, 결과에 severity 'low' 를 부착할 수 있도록
    // 아래에서 패턴을 검사한 뒤 최종 반환 시 병합합니다.
    const patternResult = _checkPatterns(input);
    if (!patternResult.safe) {
      return patternResult; // 패턴까지 걸리면 그쪽 severity 우선
    }
    return {
      safe: true,
      severity: 'low',
      reason: '입력이 200자를 초과했습니다. 지시문 삽입 가능성을 모니터링합니다.',
    };
  }

  // ── 2 & 3. 패턴 + 화이트리스트 검사 ──────────────
  return _checkPatterns(input);
}

/** 인젝션 패턴 검사 내부 함수 */
function _checkPatterns(input: string): InjectionCheckResult {
  for (const pattern of ALL_INJECTION_PATTERNS) {
    if (pattern.test(input)) {
      // 보험 컨텍스트 화이트리스트 확인
      if (isInsuranceContext(input)) {
        return { safe: true };
      }

      // 패턴 종류에 따라 심각도 분류
      const severity = _classifySeverity(pattern);
      return {
        safe: false,
        severity,
        reason: `인젝션 패턴이 감지되었습니다: ${pattern.source}`,
      };
    }
  }

  return { safe: true };
}

/**
 * 패턴의 위험도를 분류합니다.
 *
 * - high  : 시스템 프롬프트 탈취, 지시 무시처럼 직접적인 공격 패턴
 * - medium: 역할 변경, 관리자 모드 요청 등 권한 상승 시도
 * - low   : 맥락에 따라 무해할 수 있는 경계 패턴
 */
function _classifySeverity(pattern: RegExp): 'low' | 'medium' | 'high' {
  const HIGH_RISK: RegExp[] = [
    /ignore\s+(previous|all|above)\s+instructions/i,
    /reveal\s+your\s+(instructions|prompt|system)/i,
    /system\s+prompt/i,
    /이전\s*지시를?\s*무시/,
    /지시를?\s*무시/,
    /프롬프트를?\s*알려/,
    /시스템\s*프롬프트/,
  ];

  const MEDIUM_RISK: RegExp[] = [
    /you\s+are\s+now\b/i,
    /pretend\s+to\s+be/i,
    /역할을?\s*바꿔/,
    /너는\s*이제/,
    /관리자\s*모드/,
  ];

  const src = pattern.source;

  if (HIGH_RISK.some((r) => r.source === src)) return 'high';
  if (MEDIUM_RISK.some((r) => r.source === src)) return 'medium';
  return 'low';
}

/**
 * 사용자 입력을 정제합니다.
 *
 * - HTML 태그 제거
 * - 연속 공백 정규화 (다중 공백 → 단일 공백)
 * - 최대 2000자 제한
 *
 * @param input  원본 입력 문자열
 * @returns      정제된 문자열
 */
export function sanitizeInput(input: string): string {
  // 1. HTML 태그 제거
  let sanitized = input.replace(/<[^>]*>/g, '');

  // 2. 연속 공백(스페이스, 탭, 개행 포함) 정규화
  sanitized = sanitized.replace(/\s{2,}/g, ' ').trim();

  // 3. 최대 2000자 제한
  if (sanitized.length > 2000) {
    sanitized = sanitized.slice(0, 2000);
  }

  return sanitized;
}
