/**
 * answerValidator.test.ts
 * 아르고스(테스터) 작성
 *
 * 테스트 대상: answerValidator.ts
 *  - sanitizeAssertions: 단정 표현 자동 치환
 *  - maskPII: 개인정보 마스킹
 *  - validateAnswer: 답변 자동 검증 필터 (기존 + 새 규칙)
 *  - checkSimilarityGate: 유사도 임계값 게이트 (회귀)
 *  - SIMILARITY_THRESHOLDS: 상수 값 불변 보장 (회귀)
 */

import { describe, it, expect } from 'vitest';
import {
    sanitizeAssertions,
    maskPII,
    validateAnswer,
    checkSimilarityGate,
    SIMILARITY_THRESHOLDS,
    appendRiderWarning,
} from './answerValidator';

// ─────────────────────────────────────────────────────────────────────────────
// 1. sanitizeAssertions
// ─────────────────────────────────────────────────────────────────────────────
describe('sanitizeAssertions', () => {
    // 긍정형 치환
    it('"보장됩니다"를 완화 표현으로 치환한다', () => {
        const result = sanitizeAssertions('이 경우 보장됩니다.');
        expect(result).toContain('해당 조항에 따르면 보장되는 것으로 보입니다');
        expect(result).toContain('정확한 보장 여부는 보험사에 확인하세요');
        expect(result).not.toContain('보장됩니다');
    });

    it('"지급됩니다"를 완화 표현으로 치환한다', () => {
        const result = sanitizeAssertions('보험금이 지급됩니다.');
        expect(result).toContain('해당 조항에 따르면 지급되는 것으로 보입니다');
        expect(result).toContain('정확한 지급 여부는 보험사에 확인하세요');
        expect(result).not.toContain('지급됩니다');
    });

    it('"보상됩니다"를 완화 표현으로 치환한다', () => {
        const result = sanitizeAssertions('손해가 보상됩니다.');
        expect(result).toContain('해당 조항에 따르면 보상되는 것으로 보입니다');
        expect(result).toContain('정확한 보상 여부는 보험사에 확인하세요');
        expect(result).not.toContain('보상됩니다');
    });

    it('"해당됩니다"를 완화 표현으로 치환한다', () => {
        const result = sanitizeAssertions('이 조건에 해당됩니다.');
        expect(result).toContain('해당 조항에 따르면 해당되는 것으로 보입니다');
        expect(result).toContain('정확한 해당 여부는 보험사에 확인하세요');
        expect(result).not.toContain('해당됩니다');
    });

    it('"적용됩니다"를 완화 표현으로 치환한다', () => {
        const result = sanitizeAssertions('특약이 적용됩니다.');
        expect(result).toContain('해당 조항에 따르면 적용되는 것으로 보입니다');
        expect(result).toContain('정확한 적용 여부는 보험사에 확인하세요');
        expect(result).not.toContain('적용됩니다');
    });

    // 부정형 치환
    it('"보장되지 않습니다"를 완화 부정 표현으로 치환한다', () => {
        const result = sanitizeAssertions('이 경우 보장되지 않습니다.');
        expect(result).toContain('해당 조항에 따르면 보장되지 않는 것으로 보입니다');
        expect(result).toContain('정확한 보장 여부는 보험사에 확인하세요');
        expect(result).not.toContain('보장되지 않습니다');
    });

    it('"지급되지 않습니다"를 완화 부정 표현으로 치환한다', () => {
        const result = sanitizeAssertions('보험금이 지급되지 않습니다.');
        expect(result).toContain('해당 조항에 따르면 지급되지 않는 것으로 보입니다');
        expect(result).toContain('정확한 지급 여부는 보험사에 확인하세요');
        expect(result).not.toContain('지급되지 않습니다');
    });

    // 부정형 우선 매칭 보장
    it('"보장되지 않습니다"가 "보장됩니다" 패턴에 걸리지 않는다 (부정형 우선 매칭)', () => {
        const input = '이 경우 보장되지 않습니다.';
        const result = sanitizeAssertions(input);
        // 부정형 replacement 가 사용되어야 한다
        expect(result).toContain('보장되지 않는 것으로 보입니다');
        // 긍정형 replacement 문구가 섞이면 안 된다
        expect(result).not.toContain('보장되는 것으로 보입니다');
    });

    // 복수 표현 동시 치환
    it('여러 단정 표현이 동시에 있는 경우 모두 치환한다', () => {
        const input = '치료비가 보장됩니다. 수술비도 지급됩니다.';
        const result = sanitizeAssertions(input);
        expect(result).toContain('보장되는 것으로 보입니다');
        expect(result).toContain('지급되는 것으로 보입니다');
        expect(result).not.toContain('보장됩니다');
        expect(result).not.toContain('지급됩니다');
    });

    // 단정 표현 없는 경우 원문 유지
    it('단정 표현이 없는 텍스트는 변경 없이 반환한다', () => {
        const input = '약관을 검토해 보시기 바랍니다.';
        expect(sanitizeAssertions(input)).toBe(input);
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// 2. maskPII
// ─────────────────────────────────────────────────────────────────────────────
describe('maskPII', () => {
    // 전화번호
    it('010-XXXX-XXXX 형식의 휴대전화 번호를 마스킹한다', () => {
        expect(maskPII('010-1234-5678')).toBe('[MASKED_PHONE]');
    });

    it('02-XXX-XXXX 형식의 지역 전화번호를 마스킹한다', () => {
        expect(maskPII('02-123-4567')).toBe('[MASKED_PHONE]');
    });

    // 주민등록번호
    it('주민등록번호(6자리-7자리)를 마스킹한다', () => {
        expect(maskPII('900101-1234567')).toBe('[MASKED_RRN]');
    });

    // 이름 + 호칭
    it('"김철수님" 형태의 이름+님을 마스킹한다', () => {
        expect(maskPII('김철수님')).toBe('[MASKED_NAME]님');
    });

    it('"홍길동씨" 형태의 이름+씨를 마스킹한다', () => {
        expect(maskPII('홍길동씨')).toBe('[MASKED_NAME]씨');
    });

    // 호칭 없는 이름은 마스킹 금지
    it('호칭 없는 단어("보장")는 마스킹하지 않는다', () => {
        expect(maskPII('보장')).toBe('보장');
    });

    // 보험 약관 일반 용어 오마스킹 방지
    it('보험 약관 용어("계약자님")는 마스킹하지 않는다', () => {
        expect(maskPII('계약자님께서는 다음 내용을 확인하세요.')).toContain('계약자님');
    });

    it('"피보험자씨"와 같은 보험 용어는 마스킹하지 않는다', () => {
        expect(maskPII('피보험자씨에게 적용됩니다.')).toContain('피보험자씨');
    });

    it('"당사 고객" 등 일반 표현은 마스킹하지 않는다', () => {
        expect(maskPII('당사 고객 여러분께')).toBe('당사 고객 여러분께');
    });

    // PII 패턴 없는 경우 원문 유지
    it('PII 패턴이 없는 텍스트는 변경 없이 반환한다', () => {
        const input = '약관 제3조에 따라 처리됩니다.';
        expect(maskPII(input)).toBe(input);
    });

    // 복합 케이스
    it('전화번호와 이름이 함께 있는 경우 모두 마스킹한다', () => {
        const input = '김철수님의 연락처는 010-1234-5678입니다.';
        const result = maskPII(input);
        expect(result).toContain('[MASKED_NAME]님');
        expect(result).toContain('[MASKED_PHONE]');
        expect(result).not.toContain('김철수');
        expect(result).not.toContain('010-1234-5678');
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// 3. validateAnswer — 기존 + 새 규칙
// ─────────────────────────────────────────────────────────────────────────────
describe('validateAnswer', () => {
    // 기존 규칙: 불확실 표현 감지
    it('불확실 표현("아마도")이 있으면 실패한다', () => {
        const result = validateAnswer('아마도 보장될 것입니다. ※ 출처: A상품 약관 5p');
        expect(result.valid).toBe(false);
        expect(result.reason).toContain('불확실 표현 감지');
    });

    // 기존 규칙: 출처 누락
    it('출처 마커가 없으면 실패한다', () => {
        const result = validateAnswer('이 경우 보장 가능합니다.');
        expect(result.valid).toBe(false);
        expect(result.reason).toContain('출처 누락');
    });

    // 새 규칙: 출처 없이 "따라서" 결론만 → 실패
    // 구현상 출처 누락 검사가 먼저 실행되므로 reason은 '출처 누락'이 반환된다.
    // 출처가 있되 출처 마커만 있고 결론 표현이 있는 경우는 "확인할 수 없습니다" 케이스로 별도 검증한다.
    it('출처 없이 "따라서" 결론만 있으면 실패(valid: false)한다', () => {
        const result = validateAnswer('따라서 청구가 가능합니다.');
        expect(result.valid).toBe(false);
        // 출처 누락 검사가 먼저 실행되므로 reason은 '출처 누락'
        expect(result.reason).toBeTruthy();
    });

    // 새 규칙: 출처 없이 "그러므로" 결론만 → 실패
    it('출처 없이 "그러므로" 결론만 있으면 실패(valid: false)한다', () => {
        const result = validateAnswer('그러므로 보험금이 지급됩니다.');
        expect(result.valid).toBe(false);
        expect(result.reason).toBeTruthy();
    });

    // 새 규칙 직접 검증: "확인할 수 없습니다"로 출처 누락 우회 후 "따라서"가 있으면 결론 차단
    it('"확인할 수 없습니다" + "따라서" 조합은 "원문 출처 없이 결론만 있는 답변"으로 차단한다', () => {
        // "확인할 수 없습니다"가 있으면 출처 누락 검사를 통과하지만
        // 출처 마커(출처:, ※)가 없으면 결론 차단 규칙에 걸린다
        const result = validateAnswer('해당 내용을 확인할 수 없습니다. 따라서 청구는 어렵습니다.');
        expect(result.valid).toBe(false);
        expect(result.reason).toContain('원문 출처 없이 결론만 있는 답변');
    });

    // 출처 있는 경우 "따라서" 허용
    it('출처("※ 출처:")가 있으면 "따라서"가 있어도 통과한다', () => {
        const result = validateAnswer(
            '약관 제5조에 따라 처리됩니다. 따라서 청구가 가능합니다.\n※ 출처: A상품 약관 10p',
        );
        expect(result.valid).toBe(true);
    });

    // 정상 답변
    it('출처가 명시되고 불확실 표현이 없으면 통과한다', () => {
        const result = validateAnswer(
            '제3조에 의거하여 보장 대상입니다.\n※ 출처: B상품 약관 3p',
        );
        expect(result.valid).toBe(true);
        expect(result.reason).toBeUndefined();
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// 4. checkSimilarityGate / SIMILARITY_THRESHOLDS — 회귀 테스트
// ─────────────────────────────────────────────────────────────────────────────
describe('checkSimilarityGate (회귀)', () => {
    it('0.90 → "TRUST"', () => {
        expect(checkSimilarityGate(0.90)).toBe('TRUST');
    });

    it('0.75 → "DISAMBIGUATION"', () => {
        expect(checkSimilarityGate(0.75)).toBe('DISAMBIGUATION');
    });

    it('0.50 → "REJECT"', () => {
        expect(checkSimilarityGate(0.50)).toBe('REJECT');
    });

    it('정확히 임계값(0.85) 이상이면 "TRUST"', () => {
        expect(checkSimilarityGate(0.85)).toBe('TRUST');
    });

    it('정확히 임계값(0.70) 이상이면 "DISAMBIGUATION"', () => {
        expect(checkSimilarityGate(0.70)).toBe('DISAMBIGUATION');
    });

    it('임계값(0.70) 미만이면 "REJECT"', () => {
        expect(checkSimilarityGate(0.699)).toBe('REJECT');
    });
});

describe('SIMILARITY_THRESHOLDS (회귀)', () => {
    it('TRUST 값이 0.85로 유지된다', () => {
        expect(SIMILARITY_THRESHOLDS.TRUST).toBe(0.85);
    });

    it('AMBIGUOUS 값이 0.70으로 유지된다', () => {
        expect(SIMILARITY_THRESHOLDS.AMBIGUOUS).toBe(0.70);
    });

    it('REJECT 값이 0.70으로 유지된다', () => {
        expect(SIMILARITY_THRESHOLDS.REJECT).toBe(0.70);
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// 5. appendRiderWarning — CL-10 특약 경고 자동 삽입
// ─────────────────────────────────────────────────────────────────────────────
describe('appendRiderWarning (CL-10)', () => {
    it('"선택특약"이 포함된 답변에 경고 문구를 자동 삽입한다', () => {
        const result = appendRiderWarning('이 선택특약은 보장합니다.\n※ 출처: A상품 약관 5p');
        expect(result).toContain('보험증권을 확인하세요');
    });

    it('"추가특약"이 포함된 답변에 경고 문구를 자동 삽입한다', () => {
        const result = appendRiderWarning('추가특약 가입 시 보장됩니다.\n※ 출처: B상품 약관 10p');
        expect(result).toContain('보험증권을 확인하세요');
    });

    it('"특별약관"이 포함된 답변에 경고 문구를 자동 삽입한다', () => {
        const result = appendRiderWarning('특별약관에 의거합니다.\n※ 출처: C상품 약관 3p');
        expect(result).toContain('보험증권을 확인하세요');
    });

    it('"선택담보"가 포함된 답변에 경고 문구를 자동 삽입한다', () => {
        const result = appendRiderWarning('선택담보를 확인하세요.\n※ 출처: D상품 약관 7p');
        expect(result).toContain('보험증권을 확인하세요');
    });

    it('특약 키워드가 없는 답변은 변경하지 않는다', () => {
        const input = '일반 보장 내용입니다.\n※ 출처: A상품 약관 5p';
        expect(appendRiderWarning(input)).toBe(input);
    });

    it('이미 "보험증권을 확인하세요"가 포함되어 있으면 중복 삽입하지 않는다', () => {
        const input = '이 선택특약은 보장합니다. **이 특약에 가입되어 있는지 보험증권을 확인하세요.**\n※ 출처: A상품 약관 5p';
        const result = appendRiderWarning(input);
        const count = (result.match(/보험증권을 확인하세요/g) || []).length;
        expect(count).toBe(1);
    });
});
