/**
 * 경량 수정 면제 로직
 *
 * 7가지 조건을 모두 충족할 때만 검토 면제(true) 반환.
 * 하나라도 미충족 시 면제 불가(false).
 */

import * as FirebaseFirestore from '@google-cloud/firestore';

// ── 부정어 키워드 목록 ────────────────────────────────────────────────────────

const NEGATION_KEYWORDS = [
  '않',
  '않는',
  '않다',
  '못',
  '못하',
  '불가',
  '불가능',
  '없',
  '없는',
  '없다',
  '아니',
  '아닌',
  '아니다',
  '미',
  '비',
  '안 되',
  '안됨',
  '제외',
  '불포함',
  'not',
  'no',
  'never',
  'cannot',
  'unable',
];

// ── 고위험 카테고리 목록 ──────────────────────────────────────────────────────

const HIGH_RISK_CATEGORIES = ['court_ruling', 'regulation', 'policy_pdf'];

// ── 조건 1: Levenshtein 거리 ≤ 20 ────────────────────────────────────────────

/**
 * bandwidth 제한 Levenshtein distance 계산.
 * maxDistance 초과 시 조기 종료하여 대용량 문서 성능 문제 방지.
 */
export function levenshteinDistance(a: string, b: string, maxDistance = 20): number {
  const m = a.length;
  const n = b.length;

  // 길이 차이만으로 이미 초과하면 조기 종료
  if (Math.abs(m - n) > maxDistance) {
    return maxDistance + 1;
  }

  // 공간 최적화: 1차원 배열 2개만 사용
  let prev = Array.from({ length: n + 1 }, (_, i) => i);
  let curr = new Array<number>(n + 1).fill(0);

  for (let i = 1; i <= m; i++) {
    curr[0] = i;

    let rowMin = curr[0];

    for (let j = 1; j <= n; j++) {
      const cost = a[i - 1] === b[j - 1] ? 0 : 1;
      curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
      if (curr[j] < rowMin) rowMin = curr[j];
    }

    // 행 최솟값이 이미 maxDistance 초과하면 조기 종료
    if (rowMin > maxDistance) {
      return maxDistance + 1;
    }

    [prev, curr] = [curr, prev];
  }

  return prev[n];
}

/**
 * 조건 1: 편집 거리(Levenshtein) ≤ 20자
 */
export function isMinorCharChange(oldContent: string, newContent: string): boolean {
  const dist = levenshteinDistance(oldContent, newContent, 20);
  return dist <= 20;
}

// ── 조건 2: 숫자/금액/날짜/비율 변경 없음 ────────────────────────────────────

const NUMERIC_PATTERN =
  /\d+[\d,.]*%?|₩[\d,]+|\$[\d,.]+|\d{4}[-/.]\d{1,2}[-/.]\d{1,2}/g;

function extractNumericTokens(content: string): string[] {
  return content.match(NUMERIC_PATTERN) ?? [];
}

/**
 * 조건 2: old와 new에서 숫자/금액/날짜/비율 토큰이 순서 포함 동일하면 true.
 * 위치 교환(swap)도 변경으로 감지.
 */
export function hasNoNumericChange(oldContent: string, newContent: string): boolean {
  const oldTokens = extractNumericTokens(oldContent);
  const newTokens = extractNumericTokens(newContent);

  if (oldTokens.length !== newTokens.length) return false;
  return oldTokens.every((token, i) => token === newTokens[i]);
}

// ── 조건 3: 부정어 추가/삭제 없음 ────────────────────────────────────────────

function countNegations(content: string): Map<string, number> {
  const counts = new Map<string, number>();
  for (const keyword of NEGATION_KEYWORDS) {
    let count = 0;
    let pos = 0;
    while ((pos = content.indexOf(keyword, pos)) !== -1) {
      count++;
      pos += keyword.length;
    }
    if (count > 0) {
      counts.set(keyword, count);
    }
  }
  return counts;
}

/**
 * 조건 3: 부정어 빈도가 변경되면 false.
 */
export function hasNoNegationChange(oldContent: string, newContent: string): boolean {
  const oldCounts = countNegations(oldContent);
  const newCounts = countNegations(newContent);

  // 모든 부정어 키워드에 대해 빈도 비교
  const allKeywords = new Set([...oldCounts.keys(), ...newCounts.keys()]);
  for (const keyword of allKeywords) {
    const oldCount = oldCounts.get(keyword) ?? 0;
    const newCount = newCounts.get(keyword) ?? 0;
    if (oldCount !== newCount) return false;
  }
  return true;
}

// ── 조건 4: 링크 URL 변경 없음 ───────────────────────────────────────────────

const URL_PATTERN = /https?:\/\/[^\s<>"{}|\\^`[\]]+/g;

function extractUrls(content: string): string[] {
  return content.match(URL_PATTERN) ?? [];
}

/**
 * 조건 4: URL 집합이 동일하면 true.
 */
export function hasNoUrlChange(oldContent: string, newContent: string): boolean {
  const oldUrls = [...new Set(extractUrls(oldContent))].sort();
  const newUrls = [...new Set(extractUrls(newContent))].sort();

  if (oldUrls.length !== newUrls.length) return false;
  return oldUrls.every((url, i) => url === newUrls[i]);
}

// ── 조건 5: 법규/약관 인용 번호 변경 없음 ────────────────────────────────────

const LEGAL_REF_PATTERN =
  /제\d+조|제\d+항|제\d+호|별표\s*\d+|약관\s*제?\d+|법\s*제?\d+|시행령\s*제?\d+|시행규칙\s*제?\d+/g;

function extractLegalRefs(content: string): string[] {
  return (content.match(LEGAL_REF_PATTERN) ?? []).map((ref) =>
    // 공백 정규화
    ref.replace(/\s+/g, ' ').trim()
  );
}

/**
 * 조건 5: 법규/약관 인용 번호 집합이 동일하면 true.
 */
export function hasNoLegalRefChange(oldContent: string, newContent: string): boolean {
  const oldRefs = [...new Set(extractLegalRefs(oldContent))].sort();
  const newRefs = [...new Set(extractLegalRefs(newContent))].sort();

  if (oldRefs.length !== newRefs.length) return false;
  return oldRefs.every((ref, i) => ref === newRefs[i]);
}

// ── 조건 6: 고위험 카테고리 아님 ─────────────────────────────────────────────

/**
 * 조건 6: 고위험 카테고리(court_ruling, regulation, policy_pdf)이면 false.
 */
export function isNotHighRiskCategory(sourceType?: string): boolean {
  if (!sourceType) return true;
  return !HIGH_RISK_CATEGORIES.includes(sourceType);
}

// ── 조건 7: 7일 내 경량 수정 누적 < 3회 ─────────────────────────────────────

/**
 * 조건 7: documents/{docId}/auditLogs에서 7일 내 lightweight_edit_exempted 횟수 < 3.
 */
export async function hasLowRecentExemptionCount(
  docId: string,
  db: FirebaseFirestore.Firestore
): Promise<boolean> {
  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

  const snapshot = await db
    .collection('documents')
    .doc(docId)
    .collection('auditLogs')
    .where('action', '==', 'lightweight_edit_exempted')
    .where('createdAt', '>', sevenDaysAgo)
    .get();

  return snapshot.size < 3;
}

// ── 통합 함수 ─────────────────────────────────────────────────────────────────

interface ExemptionParams {
  oldContent: string;
  newContent: string;
  sourceType?: string;
  docId: string;
  db: FirebaseFirestore.Firestore;
}

interface ExemptionResult {
  exempt: boolean;
  failedConditions: string[];
}

/**
 * 7개 조건을 모두 체크하여 면제 여부와 실패 조건 목록을 반환.
 * 감사 로그 기록을 위해 failedConditions도 함께 제공.
 */
export async function isLightweightEditExempt(
  params: ExemptionParams
): Promise<ExemptionResult> {
  const { oldContent, newContent, sourceType, docId, db } = params;
  const failedConditions: string[] = [];

  // 조건 1: 변경 문자 수 ≤ 20자
  if (!isMinorCharChange(oldContent, newContent)) {
    failedConditions.push('isMinorCharChange');
  }

  // 조건 2: 숫자/금액/날짜/비율 변경 없음
  if (!hasNoNumericChange(oldContent, newContent)) {
    failedConditions.push('hasNoNumericChange');
  }

  // 조건 3: 부정어 추가/삭제 없음
  if (!hasNoNegationChange(oldContent, newContent)) {
    failedConditions.push('hasNoNegationChange');
  }

  // 조건 4: 링크 URL 변경 없음
  if (!hasNoUrlChange(oldContent, newContent)) {
    failedConditions.push('hasNoUrlChange');
  }

  // 조건 5: 법규/약관 인용 번호 변경 없음
  if (!hasNoLegalRefChange(oldContent, newContent)) {
    failedConditions.push('hasNoLegalRefChange');
  }

  // 조건 6: 고위험 카테고리 아님
  if (!isNotHighRiskCategory(sourceType)) {
    failedConditions.push('isNotHighRiskCategory');
  }

  // 조건 7: 7일 내 경량 수정 누적 < 3회 (async, Firestore 조회)
  const lowCount = await hasLowRecentExemptionCount(docId, db);
  if (!lowCount) {
    failedConditions.push('hasLowRecentExemptionCount');
  }

  return {
    exempt: failedConditions.length === 0,
    failedConditions,
  };
}
