/**
 * lightweightEditExemption.ts 단위 테스트
 *
 * 테스트 대상:
 *  - isMinorCharChange       : 조건 1 - 변경 문자 수 ≤ 20자
 *  - hasNoNumericChange      : 조건 2 - 숫자/금액/날짜/비율 변경 없음
 *  - hasNoNegationChange     : 조건 3 - 부정어 추가/삭제 없음
 *  - hasNoUrlChange          : 조건 4 - 링크 URL 변경 없음
 *  - hasNoLegalRefChange     : 조건 5 - 법규/약관 인용 번호 변경 없음
 *  - isNotHighRiskCategory   : 조건 6 - 고위험 카테고리 아님
 *  - hasLowRecentExemptionCount : 조건 7 - 7일 내 경량 수정 누적 < 3회
 *  - isLightweightEditExempt : 통합 함수
 *
 * vitest로 실행 (nextapp/vitest.config.ts가 ../functions/src/**를 포함)
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
  isMinorCharChange,
  hasNoNumericChange,
  hasNoNegationChange,
  hasNoUrlChange,
  hasNoLegalRefChange,
  isNotHighRiskCategory,
  hasLowRecentExemptionCount,
  isLightweightEditExempt,
} from '../lightweightEditExemption';

// ── 조건 1: 문자 수 변경 테스트 ─────────────────────────────────────────────

describe('isMinorCharChange - 변경 문자 수 경계값', () => {
  it('1. 편집 거리 20 → true (경계)', () => {
    const old = 'a'.repeat(100);
    // 마지막 20자를 b로 바꿈 → 편집 거리 20
    const newContent = 'a'.repeat(80) + 'b'.repeat(20);
    expect(isMinorCharChange(old, newContent)).toBe(true);
  });

  it('2. 편집 거리 21 → false (경계 초과)', () => {
    const old = 'a'.repeat(100);
    // 마지막 21자를 b로 바꿈 → 편집 거리 21
    const newContent = 'a'.repeat(79) + 'b'.repeat(21);
    expect(isMinorCharChange(old, newContent)).toBe(false);
  });

  it('3. 동일 내용 (편집 거리 0) → true', () => {
    const text = '보험료 안내 문서입니다.';
    expect(isMinorCharChange(text, text)).toBe(true);
  });

  it('짧은 문자 삽입 (10자) → true', () => {
    const old = '보험료 안내입니다.';
    const newContent = '보험료 상세 안내입니다.';
    expect(isMinorCharChange(old, newContent)).toBe(true);
  });

  it('큰 변경 (100자 이상) → false', () => {
    const old = '보험료 안내입니다.';
    const newContent = old + 'x'.repeat(100);
    expect(isMinorCharChange(old, newContent)).toBe(false);
  });
});

// ── 조건 2: 숫자/금액/날짜/비율 변경 테스트 ──────────────────────────────────

describe('hasNoNumericChange - 숫자/금액/날짜/비율', () => {
  it('4. 숫자 없이 텍스트만 추가 → true', () => {
    const old = '보험료 100만원';
    const newContent = '보험료 100만원입니다';
    expect(hasNoNumericChange(old, newContent)).toBe(true);
  });

  it('5. 비율 변경 "20%" → "2.0%" → false', () => {
    const old = '보장률 20%';
    const newContent = '보장률 2.0%';
    expect(hasNoNumericChange(old, newContent)).toBe(false);
  });

  it('6. 날짜 변경 "2026-01-01" → "2026-01-02" → false', () => {
    const old = '시행일 2026-01-01';
    const newContent = '시행일 2026-01-02';
    expect(hasNoNumericChange(old, newContent)).toBe(false);
  });

  it('금액 변경 ₩100,000 → ₩200,000 → false', () => {
    const old = '납입 보험료 ₩100,000';
    const newContent = '납입 보험료 ₩200,000';
    expect(hasNoNumericChange(old, newContent)).toBe(false);
  });

  it('숫자 위치 교환(swap) → false (보험 금액 역할 변경)', () => {
    const old = '보장 한도 1,000만원, 면책금 500만원';
    const newContent = '보장 한도 500만원, 면책금 1,000만원';
    expect(hasNoNumericChange(old, newContent)).toBe(false);
  });

  it('숫자 없는 순수 텍스트 변경 → true', () => {
    const old = '보장 내용 안내';
    const newContent = '보장 내용 상세 안내';
    expect(hasNoNumericChange(old, newContent)).toBe(true);
  });
});

// ── 조건 3: 부정어 추가/삭제 테스트 ─────────────────────────────────────────

describe('hasNoNegationChange - 부정어 빈도', () => {
  it('7. 부정어 추가 "보장함" → "보장하지 않음" → false', () => {
    const old = '보장함';
    const newContent = '보장하지 않음';
    expect(hasNoNegationChange(old, newContent)).toBe(false);
  });

  it('8. 부정어 없음 → true', () => {
    const old = '보장됨';
    const newContent = '보장됩니다';
    expect(hasNoNegationChange(old, newContent)).toBe(true);
  });

  it('부정어 삭제 → false', () => {
    const old = '보장하지 않습니다';
    const newContent = '보장합니다';
    expect(hasNoNegationChange(old, newContent)).toBe(false);
  });

  it('부정어 동일 개수 유지 → true', () => {
    const old = '불가능한 경우 제외';
    const newContent = '불가능한 사유로 제외됩니다';
    expect(hasNoNegationChange(old, newContent)).toBe(true);
  });

  it('영문 부정어 추가 "not" → false', () => {
    const old = 'This is covered';
    const newContent = 'This is not covered';
    expect(hasNoNegationChange(old, newContent)).toBe(false);
  });
});

// ── 조건 4: URL 변경 테스트 ──────────────────────────────────────────────────

describe('hasNoUrlChange - URL 집합 비교', () => {
  it('9. URL 변경 없음 → true', () => {
    const url = 'https://insuwiki.com/doc/123';
    const old = `자세한 내용은 ${url} 참조`;
    const newContent = `자세한 내용은 ${url} 를 참조하세요`;
    expect(hasNoUrlChange(old, newContent)).toBe(true);
  });

  it('10. URL 변경 있음 → false', () => {
    const old = '참조: https://insuwiki.com/doc/123';
    const newContent = '참조: https://insuwiki.com/doc/456';
    expect(hasNoUrlChange(old, newContent)).toBe(false);
  });

  it('URL 추가 → false', () => {
    const old = '안내 문서입니다.';
    const newContent = '안내 문서입니다. https://example.com';
    expect(hasNoUrlChange(old, newContent)).toBe(false);
  });

  it('URL 삭제 → false', () => {
    const old = '참조: https://example.com';
    const newContent = '참조:';
    expect(hasNoUrlChange(old, newContent)).toBe(false);
  });

  it('URL 없는 텍스트 변경 → true', () => {
    const old = '보험 안내서';
    const newContent = '보험 상세 안내서';
    expect(hasNoUrlChange(old, newContent)).toBe(true);
  });
});

// ── 조건 5: 법규/약관 인용 번호 변경 테스트 ─────────────────────────────────

describe('hasNoLegalRefChange - 법규 인용 집합 비교', () => {
  it('11. 법규 조항 변경 "제3조" → "제4조" → false', () => {
    const old = '약관 제3조에 따라';
    const newContent = '약관 제4조에 따라';
    expect(hasNoLegalRefChange(old, newContent)).toBe(false);
  });

  it('12. 법규 인용 없는 텍스트 변경 → true', () => {
    const old = '보험료 납입 안내입니다.';
    const newContent = '보험료 납입 방법 안내입니다.';
    expect(hasNoLegalRefChange(old, newContent)).toBe(true);
  });

  it('법규 인용 동일 유지 → true', () => {
    const old = '법 제10조 및 시행령 제5조 참조';
    const newContent = '법 제10조 및 시행령 제5조를 참조하시기 바랍니다';
    expect(hasNoLegalRefChange(old, newContent)).toBe(true);
  });

  it('별표 번호 변경 → false', () => {
    const old = '별표 1 참조';
    const newContent = '별표 2 참조';
    expect(hasNoLegalRefChange(old, newContent)).toBe(false);
  });

  it('법규 인용 추가 → false', () => {
    const old = '관련 규정에 따라';
    const newContent = '시행규칙 제7조에 따라';
    expect(hasNoLegalRefChange(old, newContent)).toBe(false);
  });
});

// ── 조건 6: 고위험 카테고리 테스트 ──────────────────────────────────────────

describe('isNotHighRiskCategory - 카테고리 위험도', () => {
  it('13. sourceType=court_ruling → false (고위험)', () => {
    expect(isNotHighRiskCategory('court_ruling')).toBe(false);
  });

  it('14. sourceType=newsletter → true (저위험)', () => {
    expect(isNotHighRiskCategory('newsletter')).toBe(true);
  });

  it('sourceType=regulation → false (고위험)', () => {
    expect(isNotHighRiskCategory('regulation')).toBe(false);
  });

  it('sourceType=policy_pdf → false (고위험)', () => {
    expect(isNotHighRiskCategory('policy_pdf')).toBe(false);
  });

  it('sourceType=undefined → true (기본값 저위험)', () => {
    expect(isNotHighRiskCategory(undefined)).toBe(true);
  });

  it('sourceType=blog → true (저위험)', () => {
    expect(isNotHighRiskCategory('blog')).toBe(true);
  });
});

// ── 조건 7: 최근 면제 횟수 테스트 (Firestore mock 필요) ───────────────────────

describe('hasLowRecentExemptionCount - 7일 내 누적 횟수', () => {
  const makeMockDb = (count: number) => {
    const mockQuery = {
      where: vi.fn().mockReturnThis(),
      get: vi.fn().mockResolvedValue({ size: count }),
    };
    return {
      collection: vi.fn(() => ({
        doc: vi.fn(() => ({
          collection: vi.fn(() => mockQuery),
        })),
      })),
    } as any;
  };

  it('15. 7일 내 2건 면제 → true (3 미만)', async () => {
    const db = makeMockDb(2);
    const result = await hasLowRecentExemptionCount('doc-1', db);
    expect(result).toBe(true);
  });

  it('16. 7일 내 3건 면제 → false (3 이상)', async () => {
    const db = makeMockDb(3);
    const result = await hasLowRecentExemptionCount('doc-1', db);
    expect(result).toBe(false);
  });

  it('7일 내 0건 면제 → true', async () => {
    const db = makeMockDb(0);
    const result = await hasLowRecentExemptionCount('doc-1', db);
    expect(result).toBe(true);
  });

  it('7일 내 4건 면제 → false', async () => {
    const db = makeMockDb(4);
    const result = await hasLowRecentExemptionCount('doc-1', db);
    expect(result).toBe(false);
  });
});

// ── 통합 테스트 ──────────────────────────────────────────────────────────────

describe('isLightweightEditExempt - 통합 함수', () => {
  const makeDb = (count: number) => {
    const mockQuery = {
      where: vi.fn().mockReturnThis(),
      get: vi.fn().mockResolvedValue({ size: count }),
    };
    return {
      collection: vi.fn(() => ({
        doc: vi.fn(() => ({
          collection: vi.fn(() => mockQuery),
        })),
      })),
    } as any;
  };

  it('17. 모든 조건 충족 → exempt true, failedConditions 빈 배열', async () => {
    const db = makeDb(0);
    const result = await isLightweightEditExempt({
      oldContent: '보험 안내 문서입니다.',
      newContent: '보험 상세 안내 문서입니다.',
      sourceType: 'newsletter',
      docId: 'doc-1',
      db,
    });
    expect(result.exempt).toBe(true);
    expect(result.failedConditions).toHaveLength(0);
  });

  it('18. 1개 조건 미충족 (고위험 카테고리) → exempt false + failedConditions 포함', async () => {
    const db = makeDb(0);
    const result = await isLightweightEditExempt({
      oldContent: '보험 안내 문서입니다.',
      newContent: '보험 상세 안내 문서입니다.',
      sourceType: 'court_ruling', // 고위험 카테고리
      docId: 'doc-1',
      db,
    });
    expect(result.exempt).toBe(false);
    expect(result.failedConditions).toContain('isNotHighRiskCategory');
  });

  it('문자 수 초과 → exempt false + 조건1 실패', async () => {
    const db = makeDb(0);
    const result = await isLightweightEditExempt({
      oldContent: '보험 안내입니다.',
      newContent: '보험 안내입니다.' + 'x'.repeat(50),
      sourceType: 'newsletter',
      docId: 'doc-1',
      db,
    });
    expect(result.exempt).toBe(false);
    expect(result.failedConditions).toContain('isMinorCharChange');
  });

  it('누적 면제 횟수 초과 → exempt false + 조건7 실패', async () => {
    const db = makeDb(3); // 3건 → false
    const result = await isLightweightEditExempt({
      oldContent: '보험 안내 문서입니다.',
      newContent: '보험 상세 안내 문서입니다.',
      sourceType: 'newsletter',
      docId: 'doc-1',
      db,
    });
    expect(result.exempt).toBe(false);
    expect(result.failedConditions).toContain('hasLowRecentExemptionCount');
  });

  it('여러 조건 실패 → failedConditions에 모두 포함', async () => {
    const db = makeDb(0);
    const result = await isLightweightEditExempt({
      oldContent: '보장함. 날짜: 2026-01-01',
      newContent: '보장하지 않음. 날짜: 2026-06-15. ' + 'x'.repeat(50),
      sourceType: 'court_ruling',
      docId: 'doc-1',
      db,
    });
    expect(result.exempt).toBe(false);
    expect(result.failedConditions.length).toBeGreaterThan(1);
  });
});
