/**
 * backfill-authority-tier.ts 단위 테스트
 *
 * firebase-admin을 mock하여 Firestore 의존성 없이 핵심 로직을 검증합니다.
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

// ============================================================
// TIER_MAP (canonical source: nextapp/src/lib/constants.ts)
// ============================================================

const TIER_MAP: Record<string, number> = {
  policy_pdf: 1,
  regulation: 1.5,
  newsletter: 2,
  court_ruling: 2,
  wiki_editorial: 3,
  kakao_expert: 3.5,
  kakao_community: 4,
  youtube: 4,
  user_submitted: 4,
};

// ============================================================
// 순수 함수: getTierForSourceType
// ============================================================

function getTierForSourceType(sourceType: string | null | undefined): number {
  if (!sourceType) return 6;
  return TIER_MAP[sourceType] ?? 6;
}

// ============================================================
// mock 타입 정의
// ============================================================

interface MockDocData {
  sourceType?: string | null;
  authorityTier?: number;
  title?: string;
}

interface MockDoc {
  id: string;
  data: () => MockDocData;
  ref: {
    update: ReturnType<typeof vi.fn>;
  };
}

function makeDoc(id: string, data: MockDocData): MockDoc {
  return {
    id,
    data: () => data,
    ref: { update: vi.fn().mockResolvedValue(undefined) },
  };
}

// ============================================================
// BackfillResult 타입
// ============================================================

interface BackfillResult {
  total: number;
  updated: number;
  skipped: number;
  nullSourceType: number;
  errors: number;
}

// ============================================================
// backfillBatch 로직 (스크립트 핵심 로직 추출 후 테스트)
// ============================================================

async function backfillBatch(
  docs: MockDoc[],
  dryRun: boolean,
  commitFn: () => Promise<void>,
  setBatchFn: (ref: MockDoc['ref'], data: object) => void,
): Promise<Omit<BackfillResult, 'total' | 'errors'> & { nullDocs: { id: string; title: string }[] }> {
  let updated = 0;
  let skipped = 0;
  let nullSourceType = 0;
  const nullDocs: { id: string; title: string }[] = [];

  for (const doc of docs) {
    const data = doc.data();

    // 이미 authorityTier가 있으면 스킵
    if (data.authorityTier !== undefined && data.authorityTier !== null) {
      skipped++;
      continue;
    }

    const sourceType = data.sourceType ?? null;
    const tier = getTierForSourceType(sourceType);

    if (!sourceType) {
      nullSourceType++;
      nullDocs.push({ id: doc.id, title: data.title ?? '' });
    }

    setBatchFn(doc.ref, { authorityTier: tier });
    updated++;
  }

  if (!dryRun && updated > 0) {
    await commitFn();
  }

  return { updated, skipped, nullSourceType, nullDocs };
}

// ============================================================
// 테스트
// ============================================================

describe('TIER_MAP 매핑', () => {
  it('policy_pdf → tier 1', () => {
    expect(getTierForSourceType('policy_pdf')).toBe(1);
  });

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

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

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

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

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

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

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

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

  it('알 수 없는 sourceType → tier 6', () => {
    expect(getTierForSourceType('unknown_type')).toBe(6);
  });
});

describe('sourceType null/undefined → tier 6', () => {
  it('null → 6', () => {
    expect(getTierForSourceType(null)).toBe(6);
  });

  it('undefined → 6', () => {
    expect(getTierForSourceType(undefined)).toBe(6);
  });

  it('빈 문자열 → 6', () => {
    expect(getTierForSourceType('')).toBe(6);
  });
});

describe('이미 authorityTier가 있는 문서는 스킵', () => {
  it('authorityTier가 이미 있으면 updated 카운트에 포함되지 않고 skipped 증가', async () => {
    const commitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn = vi.fn();
    const docs = [
      makeDoc('doc1', { sourceType: 'youtube', authorityTier: 4 }),
      makeDoc('doc2', { sourceType: 'policy_pdf' }), // authorityTier 없음
    ];

    const result = await backfillBatch(docs, false, commitFn, setBatchFn);

    expect(result.skipped).toBe(1);
    expect(result.updated).toBe(1);
    // doc1은 setBatchFn이 호출되지 않아야 함
    expect(setBatchFn).toHaveBeenCalledTimes(1);
  });

  it('모든 문서에 authorityTier가 있으면 commit도 호출되지 않음', async () => {
    const commitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn = vi.fn();
    const docs = [
      makeDoc('doc1', { sourceType: 'youtube', authorityTier: 4 }),
      makeDoc('doc2', { sourceType: 'policy_pdf', authorityTier: 1 }),
    ];

    const result = await backfillBatch(docs, false, commitFn, setBatchFn);

    expect(result.skipped).toBe(2);
    expect(result.updated).toBe(0);
    expect(commitFn).not.toHaveBeenCalled();
  });
});

describe('dry-run 시 WriteBatch.commit() 호출되지 않음', () => {
  it('dryRun=true이면 업데이트가 있어도 commit 미호출', async () => {
    const commitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn = vi.fn();
    const docs = [
      makeDoc('doc1', { sourceType: 'youtube' }),
      makeDoc('doc2', { sourceType: 'policy_pdf' }),
    ];

    const result = await backfillBatch(docs, true, commitFn, setBatchFn);

    expect(result.updated).toBe(2);
    expect(commitFn).not.toHaveBeenCalled();
  });

  it('dryRun=false이면 업데이트 후 commit 호출', async () => {
    const commitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn = vi.fn();
    const docs = [makeDoc('doc1', { sourceType: 'youtube' })];

    await backfillBatch(docs, false, commitFn, setBatchFn);

    expect(commitFn).toHaveBeenCalledTimes(1);
  });
});

describe('500건 배치 경계 처리', () => {
  it('501건은 배치 2번으로 분리해야 함', () => {
    // 배치 크기 상수 테스트
    const BATCH_SIZE = 500;
    const totalDocs = 501;
    const expectedBatches = Math.ceil(totalDocs / BATCH_SIZE);
    expect(expectedBatches).toBe(2);
  });

  it('500건은 배치 1번', () => {
    const BATCH_SIZE = 500;
    const totalDocs = 500;
    const expectedBatches = Math.ceil(totalDocs / BATCH_SIZE);
    expect(expectedBatches).toBe(1);
  });

  it('499건은 배치 1번', () => {
    const BATCH_SIZE = 500;
    const totalDocs = 499;
    const expectedBatches = Math.ceil(totalDocs / BATCH_SIZE);
    expect(expectedBatches).toBe(1);
  });

  it('1000건은 배치 2번', () => {
    const BATCH_SIZE = 500;
    const totalDocs = 1000;
    const expectedBatches = Math.ceil(totalDocs / BATCH_SIZE);
    expect(expectedBatches).toBe(2);
  });

  it('501건 배치 처리 시 각 배치별 commit이 호출됨', async () => {
    // 첫 번째 배치: 500건, 두 번째 배치: 1건
    const batch1CommitFn = vi.fn().mockResolvedValue(undefined);
    const batch2CommitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn1 = vi.fn();
    const setBatchFn2 = vi.fn();

    // 500건짜리 배치
    const docs500 = Array.from({ length: 500 }, (_, i) =>
      makeDoc(`doc${i}`, { sourceType: 'youtube' })
    );
    // 1건짜리 배치
    const docs1 = [makeDoc('doc500', { sourceType: 'policy_pdf' })];

    const result1 = await backfillBatch(docs500, false, batch1CommitFn, setBatchFn1);
    const result2 = await backfillBatch(docs1, false, batch2CommitFn, setBatchFn2);

    expect(result1.updated).toBe(500);
    expect(result2.updated).toBe(1);
    expect(batch1CommitFn).toHaveBeenCalledTimes(1);
    expect(batch2CommitFn).toHaveBeenCalledTimes(1);
    expect(setBatchFn1).toHaveBeenCalledTimes(500);
    expect(setBatchFn2).toHaveBeenCalledTimes(1);
  });
});

describe('결과 통계 정확성', () => {
  it('혼합 데이터에서 통계가 정확하게 집계됨', async () => {
    const commitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn = vi.fn();
    const docs = [
      makeDoc('doc1', { sourceType: 'youtube' }),           // updated
      makeDoc('doc2', { sourceType: 'policy_pdf', authorityTier: 1 }), // skipped
      makeDoc('doc3', { sourceType: null }),                  // updated + nullSourceType
      makeDoc('doc4', { sourceType: 'regulation' }),          // updated
      makeDoc('doc5', {}),                                    // updated + nullSourceType (sourceType undefined)
    ];

    const result = await backfillBatch(docs, false, commitFn, setBatchFn);

    expect(result.updated).toBe(4);        // doc1, doc3, doc4, doc5
    expect(result.skipped).toBe(1);        // doc2
    expect(result.nullSourceType).toBe(2); // doc3, doc5
    expect(result.nullDocs).toHaveLength(2);
  });

  it('nullSourceType 문서에는 tier 6이 설정됨', async () => {
    const commitFn = vi.fn().mockResolvedValue(undefined);
    const setBatchFn = vi.fn();
    const docs = [makeDoc('doc1', { sourceType: null, title: '테스트 문서' })];

    const result = await backfillBatch(docs, false, commitFn, setBatchFn);

    expect(result.nullSourceType).toBe(1);
    expect(result.nullDocs[0]).toEqual({ id: 'doc1', title: '테스트 문서' });
    expect(setBatchFn).toHaveBeenCalledWith(
      expect.anything(),
      { authorityTier: 6 }
    );
  });
});
