/**
 * reliabilityScoring.ts 단위 테스트
 *
 * vitest 기반 순수 함수 테스트 (Firebase 의존 없음)
 */

import { describe, it, expect } from 'vitest';
import {
  calculateFreshnessScore,
  calculateVerificationScore,
  calculateAuthorityScore,
  calculateSourceScore,
  calculateReviewScore,
  calculateSourceRefScore,
  applyKnockout,
  computeCompositeReliabilityScore,
  loadWeightsConfig,
} from '../reliabilityScoring';

// ── freshness 테스트 ─────────────────────────────────────────────────────────

describe('calculateFreshnessScore', () => {
  const config = loadWeightsConfig();
  const intervals = config.freshnessIntervals;

  function makeDate(daysAgo: number): { updatedAt: Date; now: Date } {
    const now = new Date('2026-04-11T00:00:00Z');
    const updatedAt = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000);
    return { updatedAt, now };
  }

  it('0일 → 1.0', () => {
    const { updatedAt, now } = makeDate(0);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(1.0);
  });

  it('30일 → 1.0 (경계값 포함)', () => {
    const { updatedAt, now } = makeDate(30);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(1.0);
  });

  it('31일 → 0.8', () => {
    const { updatedAt, now } = makeDate(31);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.8);
  });

  it('90일 → 0.8 (경계값 포함)', () => {
    const { updatedAt, now } = makeDate(90);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.8);
  });

  it('91일 → 0.5', () => {
    const { updatedAt, now } = makeDate(91);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.5);
  });

  it('180일 → 0.5 (경계값 포함)', () => {
    const { updatedAt, now } = makeDate(180);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.5);
  });

  it('181일 → 0.3', () => {
    const { updatedAt, now } = makeDate(181);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.3);
  });

  it('365일 → 0.3 (경계값 포함)', () => {
    const { updatedAt, now } = makeDate(365);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.3);
  });

  it('366일 → 0.1 (최대 구간 초과)', () => {
    const { updatedAt, now } = makeDate(366);
    expect(calculateFreshnessScore(updatedAt, now, intervals)).toBe(0.1);
  });
});

// ── verification 테스트 ──────────────────────────────────────────────────────

describe('calculateVerificationScore', () => {
  it('expert_verified → 1.0', () => {
    expect(calculateVerificationScore('expert_verified')).toBe(1.0);
  });

  it('auto_passed → 0.6', () => {
    expect(calculateVerificationScore('auto_passed')).toBe(0.6);
  });

  it('unverified → 0.2', () => {
    expect(calculateVerificationScore('unverified')).toBe(0.2);
  });

  it('undefined → 0.2', () => {
    expect(calculateVerificationScore(undefined)).toBe(0.2);
  });

  it('expert_verified + approved → 1.0 (최대 1.0 제한)', () => {
    expect(calculateVerificationScore('expert_verified', 'approved')).toBe(1.0);
  });

  it('auto_passed + approved → 0.7 (+0.1 보너스)', () => {
    expect(calculateVerificationScore('auto_passed', 'approved')).toBe(0.7);
  });

  it('unverified + approved → 0.3 (+0.1 보너스)', () => {
    expect(calculateVerificationScore('unverified', 'approved')).toBeCloseTo(0.3, 5);
  });

  it('undefined + approved → 0.3 (+0.1 보너스)', () => {
    expect(calculateVerificationScore(undefined, 'approved')).toBeCloseTo(0.3, 5);
  });
});

// ── authority 테스트 ─────────────────────────────────────────────────────────

describe('calculateAuthorityScore', () => {
  it('tier 1 → 1.0', () => {
    expect(calculateAuthorityScore(1)).toBe(1.0);
  });

  it('tier 1.5 → 0.9', () => {
    expect(calculateAuthorityScore(1.5)).toBe(0.9);
  });

  it('tier 2 → 0.7', () => {
    expect(calculateAuthorityScore(2)).toBe(0.7);
  });

  it('tier 3 → 0.5', () => {
    expect(calculateAuthorityScore(3)).toBe(0.5);
  });

  it('tier 3.5 → 0.3', () => {
    expect(calculateAuthorityScore(3.5)).toBe(0.3);
  });

  it('tier 4 → 0.2', () => {
    expect(calculateAuthorityScore(4)).toBe(0.2);
  });

  it('undefined → 0.1', () => {
    expect(calculateAuthorityScore(undefined)).toBe(0.1);
  });

  it('매핑 외 값(2.5) → 선형 보간: tier 2(0.7)~tier 3(0.5) 중간', () => {
    const score = calculateAuthorityScore(2.5);
    // 2~3 사이 중간점: 0.7 + (0.5 - 0.7) * 0.5 = 0.6
    expect(score).toBeCloseTo(0.6, 5);
  });
});

// ── source 테스트 ────────────────────────────────────────────────────────────

describe('calculateSourceScore', () => {
  it('regulation → 1.0', () => {
    expect(calculateSourceScore('regulation')).toBe(1.0);
  });

  it('policy_pdf → 0.9', () => {
    expect(calculateSourceScore('policy_pdf')).toBe(0.9);
  });

  it('court_ruling → 0.8', () => {
    expect(calculateSourceScore('court_ruling')).toBe(0.8);
  });

  it('newsletter → 0.7', () => {
    expect(calculateSourceScore('newsletter')).toBe(0.7);
  });

  it('wiki_editorial → 0.6', () => {
    expect(calculateSourceScore('wiki_editorial')).toBe(0.6);
  });

  it('kakao_expert → 0.5', () => {
    expect(calculateSourceScore('kakao_expert')).toBe(0.5);
  });

  it('youtube → 0.3', () => {
    expect(calculateSourceScore('youtube')).toBe(0.3);
  });

  it('kakao_community → 0.3', () => {
    expect(calculateSourceScore('kakao_community')).toBe(0.3);
  });

  it('user_submitted → 0.3', () => {
    expect(calculateSourceScore('user_submitted')).toBe(0.3);
  });

  it('undefined → 0.2', () => {
    expect(calculateSourceScore(undefined)).toBe(0.2);
  });
});

// ── review 테스트 ────────────────────────────────────────────────────────────

describe('calculateReviewScore', () => {
  it('approval=true + 2건 → 0.8 + 0.2 = 1.0', () => {
    expect(calculateReviewScore(2, true)).toBe(1.0);
  });

  it('approval=true + 1건 → 0.8 + 0.1 = 0.9', () => {
    expect(calculateReviewScore(1, true)).toBe(0.9);
  });

  it('approval=true + 3건 → 최대 1.0 (0.8 + 0.3 > 1.0)', () => {
    expect(calculateReviewScore(3, true)).toBe(1.0);
  });

  it('approval=false + 1건 → 0.2', () => {
    expect(calculateReviewScore(1, false)).toBe(0.2);
  });

  it('approval=false + 3건 → 최대 0.5 (0.6 > 0.5)', () => {
    expect(calculateReviewScore(3, false)).toBe(0.5);
  });

  it('approval=false + 0건 → 0.0', () => {
    expect(calculateReviewScore(0, false)).toBe(0.0);
  });

  it('approval=true + 0건 → 0.0 (리뷰 없음 최우선)', () => {
    expect(calculateReviewScore(0, true)).toBe(0.0);
  });
});

// ── sourceRef 테스트 ─────────────────────────────────────────────────────────

describe('calculateSourceRefScore', () => {
  it('hasSourceRef=false → 0.0', () => {
    expect(calculateSourceRefScore(false)).toBe(0.0);
  });

  it('hasSourceRef=true, quality=high → 1.0', () => {
    expect(calculateSourceRefScore(true, 'high')).toBe(1.0);
  });

  it('hasSourceRef=true, quality=medium → 0.7', () => {
    expect(calculateSourceRefScore(true, 'medium')).toBe(0.7);
  });

  it('hasSourceRef=true, quality=low → 0.4', () => {
    expect(calculateSourceRefScore(true, 'low')).toBe(0.4);
  });

  it('hasSourceRef=true, quality=undefined → 0.4 (기본값)', () => {
    expect(calculateSourceRefScore(true, undefined)).toBe(0.4);
  });
});

// ── knockout 테스트 ──────────────────────────────────────────────────────────

describe('applyKnockout', () => {
  const knockoutConfig = { enabled: true, floorScore: 0.1 };
  const noKnockoutConfig = { enabled: false, floorScore: 0.1 };

  it('knockout 활성화, 0인 차원 있음 → knockedOut=true, score=0.1', () => {
    const dimensions = { a: 0.8, b: 0.0, c: 0.5 };
    const result = applyKnockout(dimensions, knockoutConfig);
    expect(result.knockedOut).toBe(true);
    expect(result.score).toBe(0.1);
  });

  it('knockout 활성화, 0인 차원 없음 → knockedOut=false, 평균값 반환', () => {
    const dimensions = { a: 0.9, b: 0.6, c: 0.3 };
    const result = applyKnockout(dimensions, knockoutConfig);
    expect(result.knockedOut).toBe(false);
    expect(result.score).toBeCloseTo(0.6, 5);
  });

  it('knockout 비활성화, 0인 차원 있어도 → knockedOut=false, 평균값 반환', () => {
    const dimensions = { a: 0.9, b: 0.0, c: 0.3 };
    const result = applyKnockout(dimensions, noKnockoutConfig);
    expect(result.knockedOut).toBe(false);
    expect(result.score).toBeCloseTo(0.4, 5);
  });
});

// ── computeCompositeReliabilityScore 테스트 ──────────────────────────────────

describe('computeCompositeReliabilityScore', () => {
  function makeDate(daysAgo: number): Date {
    const now = new Date('2026-04-11T00:00:00Z');
    return new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000);
  }

  const fixedNow = new Date('2026-04-11T00:00:00Z');

  it('knockout: review=0 → knockedOut=true, compositeScore=0.1', () => {
    const result = computeCompositeReliabilityScore({
      verificationStatus: 'expert_verified',
      authorityTier: 1,
      sourceType: 'regulation',
      reviewCount: 0,     // 0 → review 점수 0 → knockout
      hasApproval: false,
      hasSourceRef: true,
      sourceRefQuality: 'high',
      updatedAt: makeDate(10),
      now: fixedNow,
    });
    expect(result.knockedOut).toBe(true);
    expect(result.compositeScore).toBe(0.1);
    expect(result.dimensions.review).toBe(0.0);
  });

  it('knockout: hasSourceRef=false → knockedOut=true, compositeScore=0.1', () => {
    const result = computeCompositeReliabilityScore({
      verificationStatus: 'expert_verified',
      authorityTier: 1,
      sourceType: 'regulation',
      reviewCount: 2,
      hasApproval: true,
      hasSourceRef: false,  // 0 → knockout
      updatedAt: makeDate(10),
      now: fixedNow,
    });
    expect(result.knockedOut).toBe(true);
    expect(result.compositeScore).toBe(0.1);
    expect(result.dimensions.sourceRef).toBe(0.0);
  });

  it('최고 신뢰도 입력 → 높은 compositeScore (knockout 없음)', () => {
    // verification=1.0, authority=1.0, source=1.0, review=1.0, freshness=1.0, sourceRef=1.0
    // 가중합: 0.30*1+0.20*1+0.15*1+0.15*1+0.10*1+0.10*1 = 1.0
    const result = computeCompositeReliabilityScore({
      verificationStatus: 'expert_verified',
      authorityTier: 1,
      sourceType: 'regulation',
      reviewCount: 2,
      hasApproval: true,
      hasSourceRef: true,
      sourceRefQuality: 'high',
      updatedAt: makeDate(5),
      now: fixedNow,
    });
    expect(result.knockedOut).toBe(false);
    expect(result.compositeScore).toBeCloseTo(1.0, 5);
    expect(result.dimensions.verification).toBe(1.0);
    expect(result.dimensions.authority).toBe(1.0);
    expect(result.dimensions.source).toBe(1.0);
    expect(result.dimensions.review).toBe(1.0);
    expect(result.dimensions.freshness).toBe(1.0);
    expect(result.dimensions.sourceRef).toBe(1.0);
  });

  it('수동 계산 비교: 알려진 입력', () => {
    // verification: auto_passed=0.6, authority: tier 2=0.7, source: newsletter=0.7
    // review: no approval, 1건=0.2, freshness: 90일=0.8, sourceRef: medium=0.7
    // 가중합: 0.30*0.6 + 0.20*0.7 + 0.15*0.7 + 0.15*0.2 + 0.10*0.8 + 0.10*0.7
    //       = 0.18 + 0.14 + 0.105 + 0.03 + 0.08 + 0.07
    //       = 0.605
    const result = computeCompositeReliabilityScore({
      verificationStatus: 'auto_passed',
      authorityTier: 2,
      sourceType: 'newsletter',
      reviewCount: 1,
      hasApproval: false,
      hasSourceRef: true,
      sourceRefQuality: 'medium',
      updatedAt: makeDate(90),
      now: fixedNow,
    });
    expect(result.knockedOut).toBe(false);
    expect(result.compositeScore).toBeCloseTo(0.605, 5);
  });

  it('dimensions 객체가 6개 키를 모두 포함', () => {
    const result = computeCompositeReliabilityScore({
      reviewCount: 1,
      hasApproval: true,
      hasSourceRef: true,
      updatedAt: makeDate(20),
      now: fixedNow,
    });
    const keys = Object.keys(result.dimensions);
    expect(keys).toContain('verification');
    expect(keys).toContain('authority');
    expect(keys).toContain('source');
    expect(keys).toContain('review');
    expect(keys).toContain('freshness');
    expect(keys).toContain('sourceRef');
  });
});

// ── 10건 이상 테스트 시나리오 ────────────────────────────────────────────────

describe('10건 이상 테스트 문서 시나리오', () => {
  function makeDate(daysAgo: number): Date {
    const now = new Date('2026-04-11T00:00:00Z');
    return new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000);
  }

  const fixedNow = new Date('2026-04-11T00:00:00Z');

  const scenarios = [
    {
      name: '시나리오 1: 규정 문서, 전문가 검증, 최신',
      input: {
        verificationStatus: 'expert_verified',
        authorityTier: 1,
        sourceType: 'regulation',
        reviewCount: 2,
        hasApproval: true,
        hasSourceRef: true,
        sourceRefQuality: 'high' as const,
        updatedAt: makeDate(15),
        now: fixedNow,
      },
      expected: { knockedOut: false, minScore: 0.9 },
    },
    {
      name: '시나리오 2: 커뮤니티 게시글, 미검증, 오래됨',
      input: {
        verificationStatus: 'unverified',
        authorityTier: 4,
        sourceType: 'kakao_community',
        reviewCount: 0,
        hasApproval: false,
        hasSourceRef: false,
        updatedAt: makeDate(400),
        now: fixedNow,
      },
      expected: { knockedOut: true, exactScore: 0.1 },
    },
    {
      name: '시나리오 3: 법원 판결문, 자동 통과',
      input: {
        verificationStatus: 'auto_passed',
        authorityTier: 1.5,
        sourceType: 'court_ruling',
        reviewCount: 1,
        hasApproval: true,
        hasSourceRef: true,
        sourceRefQuality: 'medium' as const,
        updatedAt: makeDate(60),
        now: fixedNow,
      },
      expected: { knockedOut: false, minScore: 0.7 },
    },
    {
      name: '시나리오 4: 유튜브 채널, 미검증, 리뷰 없음',
      input: {
        verificationStatus: undefined,
        authorityTier: undefined,
        sourceType: 'youtube',
        reviewCount: 0,
        hasApproval: false,
        hasSourceRef: false,
        updatedAt: makeDate(200),
        now: fixedNow,
      },
      expected: { knockedOut: true, exactScore: 0.1 },
    },
    {
      name: '시나리오 5: 정책 PDF, 전문가 검증, 출처 참조 있음',
      input: {
        verificationStatus: 'expert_verified',
        documentStatus: 'approved',
        authorityTier: 2,
        sourceType: 'policy_pdf',
        reviewCount: 1,
        hasApproval: true,
        hasSourceRef: true,
        sourceRefQuality: 'high' as const,
        updatedAt: makeDate(45),
        now: fixedNow,
      },
      // verification: min(1.0+0.1, 1.0)=1.0, authority: 0.7, source: 0.9
      // review: 0.9, freshness: 0.8 (45일), sourceRef: 1.0
      // 0.30*1.0 + 0.20*0.7 + 0.15*0.9 + 0.15*0.9 + 0.10*0.8 + 0.10*1.0
      // = 0.30 + 0.14 + 0.135 + 0.135 + 0.08 + 0.10 = 0.89
      expected: { knockedOut: false, approxScore: 0.89 },
    },
    {
      name: '시나리오 6: 뉴스레터, 자동 통과, 소스 참조 없음 (knockout)',
      input: {
        verificationStatus: 'auto_passed',
        authorityTier: 3,
        sourceType: 'newsletter',
        reviewCount: 2,
        hasApproval: false,
        hasSourceRef: false,
        updatedAt: makeDate(100),
        now: fixedNow,
      },
      expected: { knockedOut: true, exactScore: 0.1 },
    },
    {
      name: '시나리오 7: 위키 편집, 검토 중',
      input: {
        verificationStatus: 'unverified',
        documentStatus: 'approved',
        authorityTier: 3,
        sourceType: 'wiki_editorial',
        reviewCount: 1,
        hasApproval: false,
        hasSourceRef: true,
        sourceRefQuality: 'low' as const,
        updatedAt: makeDate(30),
        now: fixedNow,
      },
      expected: { knockedOut: false, minScore: 0.3 },
    },
    {
      name: '시나리오 8: 카카오 전문가 답변, 1년 이상 경과',
      input: {
        verificationStatus: 'auto_passed',
        authorityTier: 2,
        sourceType: 'kakao_expert',
        reviewCount: 1,
        hasApproval: true,
        hasSourceRef: true,
        sourceRefQuality: 'medium' as const,
        updatedAt: makeDate(500),
        now: fixedNow,
      },
      // freshness: 500일 → 0.1
      expected: { knockedOut: false, maxScore: 0.7 },
    },
    {
      name: '시나리오 9: 사용자 제출, 다수 리뷰 (승인 없음)',
      input: {
        verificationStatus: undefined,
        authorityTier: 4,
        sourceType: 'user_submitted',
        reviewCount: 3,
        hasApproval: false,
        hasSourceRef: true,
        sourceRefQuality: 'low' as const,
        updatedAt: makeDate(150),
        now: fixedNow,
      },
      expected: { knockedOut: false, maxScore: 0.5 },
    },
    {
      name: '시나리오 10: 전문가 검증 + 가장 최신 문서',
      input: {
        verificationStatus: 'expert_verified',
        authorityTier: 1,
        sourceType: 'regulation',
        reviewCount: 2,
        hasApproval: true,
        hasSourceRef: true,
        sourceRefQuality: 'high' as const,
        updatedAt: makeDate(1),
        now: fixedNow,
      },
      expected: { knockedOut: false, minScore: 0.95 },
    },
    {
      name: '시나리오 11: 중간 품질 모든 항목',
      input: {
        verificationStatus: 'auto_passed',
        authorityTier: 2,
        sourceType: 'wiki_editorial',
        reviewCount: 1,
        hasApproval: true,
        hasSourceRef: true,
        sourceRefQuality: 'medium' as const,
        updatedAt: makeDate(60),
        now: fixedNow,
      },
      // verification: 0.6, authority: 0.7, source: 0.6
      // review: 0.8+0.1=0.9, freshness: 0.8 (60일), sourceRef: 0.7
      // 0.30*0.6 + 0.20*0.7 + 0.15*0.6 + 0.15*0.9 + 0.10*0.8 + 0.10*0.7
      // = 0.18 + 0.14 + 0.09 + 0.135 + 0.08 + 0.07 = 0.695
      expected: { knockedOut: false, approxScore: 0.695 },
    },
    {
      name: '시나리오 12: 티어 없음, 소스 없음, 최소 리뷰',
      input: {
        verificationStatus: 'auto_passed',
        authorityTier: undefined,
        sourceType: undefined,
        reviewCount: 1,
        hasApproval: false,
        hasSourceRef: true,
        sourceRefQuality: 'low' as const,
        updatedAt: makeDate(10),
        now: fixedNow,
      },
      expected: { knockedOut: false, maxScore: 0.5 },
    },
  ];

  for (const scenario of scenarios) {
    it(scenario.name, () => {
      const result = computeCompositeReliabilityScore(scenario.input);

      if (scenario.expected.knockedOut !== undefined) {
        expect(result.knockedOut).toBe(scenario.expected.knockedOut);
      }

      if ('exactScore' in scenario.expected && scenario.expected.exactScore !== undefined) {
        expect(result.compositeScore).toBe(scenario.expected.exactScore);
      }

      if ('approxScore' in scenario.expected && scenario.expected.approxScore !== undefined) {
        expect(result.compositeScore).toBeCloseTo(scenario.expected.approxScore, 3);
      }

      if ('minScore' in scenario.expected && scenario.expected.minScore !== undefined) {
        expect(result.compositeScore).toBeGreaterThanOrEqual(scenario.expected.minScore);
      }

      if ('maxScore' in scenario.expected && scenario.expected.maxScore !== undefined) {
        expect(result.compositeScore).toBeLessThanOrEqual(scenario.expected.maxScore);
      }
    });
  }
});
