import * as admin from 'firebase-admin';

// Firebase Admin SDK 초기화 (GOOGLE_APPLICATION_CREDENTIALS 환경변수 사용)
// Usage (dry-run): GOOGLE_APPLICATION_CREDENTIALS=/home/jay/.config/gcloud/service-accounts/insuwiki-j2h-fa603f4f75f5.json ts-node -P scripts/tsconfig.json scripts/fix-unknown-reviewers.ts
// Usage (execute): GOOGLE_APPLICATION_CREDENTIALS=/home/jay/.config/gcloud/service-accounts/insuwiki-j2h-fa603f4f75f5.json ts-node -P scripts/tsconfig.json scripts/fix-unknown-reviewers.ts --execute

if (!admin.apps.length) {
  admin.initializeApp();
}

const db = admin.firestore();
const auth = admin.auth();

// ── 상수 ─────────────────────────────────────────────────────────────────────

const BATCH_SIZE = 10; // Firebase Auth API rate limit 방지

// ── 타입 정의 ─────────────────────────────────────────────────────────────────

interface FixResult {
  total: number;
  fixed: number;
  skipped: number;
  errors: number;
}

type ReviewStatus =
  | 'fixed_from_users_name'
  | 'fixed_from_users_displayName'
  | 'fixed_from_auth'
  | 'skipped_no_reviewer_id'
  | 'skipped_no_name_found'
  | `error: ${string}`;

interface ReviewResult {
  path: string;
  reviewerId?: string;
  resolvedName?: string;
  status: ReviewStatus;
}

// ── 유저 이름 캐시 ────────────────────────────────────────────────────────────

// 반복 조회 방지를 위한 유저 이름 캐시 (null = 조회했으나 이름 없음)
const userNameCache = new Map<string, string | null>();

async function resolveUserName(
  uid: string,
): Promise<{ name: string; source: 'users_name' | 'users_displayName' | 'auth' } | null> {
  // 캐시 확인
  if (userNameCache.has(uid)) {
    const cached = userNameCache.get(uid)!;
    if (cached === null) return null;
    // 캐시된 경우 source는 알 수 없으므로 users_name으로 표기
    return { name: cached, source: 'users_name' };
  }

  // 1. Firestore users/{uid} 조회
  const userDoc = await db.collection('users').doc(uid).get();
  if (userDoc.exists) {
    const data = userDoc.data()!;

    // name 필드 우선
    if (typeof data.name === 'string' && data.name.trim().length > 0) {
      const name = data.name.trim();
      userNameCache.set(uid, name);
      return { name, source: 'users_name' };
    }

    // displayName 필드 차선
    if (typeof data.displayName === 'string' && data.displayName.trim().length > 0) {
      const name = data.displayName.trim();
      userNameCache.set(uid, name);
      return { name, source: 'users_displayName' };
    }
  }

  // 2. Firebase Auth에서 displayName 조회
  try {
    const userRecord = await auth.getUser(uid);
    if (userRecord.displayName && userRecord.displayName.trim().length > 0) {
      const name = userRecord.displayName.trim();
      userNameCache.set(uid, name);
      return { name, source: 'auth' };
    }
  } catch (err: any) {
    if (err.code !== 'auth/user-not-found') {
      throw err;
    }
    // Auth에 유저가 없는 경우는 무시
  }

  // 이름을 찾지 못한 경우 캐시에 null 저장
  userNameCache.set(uid, null);
  return null;
}

// ── 핵심 함수 ─────────────────────────────────────────────────────────────────

async function processBatch(
  docs: FirebaseFirestore.QueryDocumentSnapshot[],
  dryRun: boolean,
): Promise<{ fixed: number; skipped: number; errors: number; details: ReviewResult[] }> {
  let fixed = 0;
  let skipped = 0;
  let errors = 0;
  const details: ReviewResult[] = [];

  await Promise.all(
    docs.map(async (reviewDoc) => {
      const path = reviewDoc.ref.path;
      const data = reviewDoc.data();
      const reviewerId: string | undefined = data.reviewerId;

      if (!reviewerId) {
        skipped++;
        details.push({ path, status: 'skipped_no_reviewer_id' });
        console.warn(`  [WARN] ${path} → reviewerId 필드 없음, 스킵`);
        return;
      }

      try {
        const resolved = await resolveUserName(reviewerId);

        if (!resolved) {
          skipped++;
          details.push({ path, reviewerId, status: 'skipped_no_name_found' });
          console.warn(`  [WARN] ${path} → reviewerId=${reviewerId} 이름 조회 실패, 스킵`);
          return;
        }

        const { name, source } = resolved;
        const statusLabel: ReviewStatus =
          source === 'users_name'
            ? 'fixed_from_users_name'
            : source === 'users_displayName'
              ? 'fixed_from_users_displayName'
              : 'fixed_from_auth';

        if (dryRun) {
          console.log(
            `  [DRY-RUN] ${path} → reviewerName="${name}" (출처: ${source}, reviewerId=${reviewerId})`,
          );
        } else {
          await reviewDoc.ref.update({ reviewerName: name });
          console.log(
            `  [UPDATE] ${path} → reviewerName="${name}" (출처: ${source}, reviewerId=${reviewerId})`,
          );
        }

        fixed++;
        details.push({ path, reviewerId, resolvedName: name, status: statusLabel });
      } catch (err: any) {
        errors++;
        const msg = err.message ?? String(err);
        details.push({ path, reviewerId, status: `error: ${msg}` });
        console.error(`  [ERROR] ${path} → ${msg}`);
      }
    }),
  );

  return { fixed, skipped, errors, details };
}

async function fixUnknownReviewers(dryRun: boolean): Promise<FixResult> {
  console.log(`[INFO] Unknown 리뷰어 이름 수정 시작 (dryRun=${dryRun})`);

  const result: FixResult = {
    total: 0,
    fixed: 0,
    skipped: 0,
    errors: 0,
  };

  // collectionGroup 쿼리로 모든 reviews 서브컬렉션에서 reviewerName === 'Unknown' 조회
  console.log(`[INFO] collectionGroup('reviews')에서 reviewerName='Unknown' 검색 중...`);
  const snapshot = await db
    .collectionGroup('reviews')
    .where('reviewerName', '==', 'Unknown')
    .get();

  result.total = snapshot.docs.length;
  console.log(`[INFO] 대상 리뷰 레코드 수: ${result.total}\n`);

  if (result.total === 0) {
    console.log('[INFO] 수정 대상이 없습니다.');
    return result;
  }

  const totalBatches = Math.ceil(snapshot.docs.length / BATCH_SIZE);

  for (let i = 0; i < snapshot.docs.length; i += BATCH_SIZE) {
    const batchDocs = snapshot.docs.slice(i, i + BATCH_SIZE);
    const batchNum = Math.floor(i / BATCH_SIZE) + 1;

    console.log(`[INFO] 배치 ${batchNum}/${totalBatches} 처리 중 (${batchDocs.length}건)...`);

    const batchResult = await processBatch(batchDocs, dryRun);

    result.fixed += batchResult.fixed;
    result.skipped += batchResult.skipped;
    result.errors += batchResult.errors;
  }

  console.log(`\n[INFO] 유저 이름 캐시 히트 수: ${userNameCache.size}개 uid 캐시됨`);

  return result;
}

// ── 엔트리포인트 ─────────────────────────────────────────────────────────────

async function main() {
  const dryRun = !process.argv.includes('--execute');

  if (dryRun) {
    console.log('[DRY-RUN] --execute 플래그 없이 실행 중입니다. Firestore 실제 쓰기는 발생하지 않습니다.');
    console.log(
      '[DRY-RUN] 실제 실행: GOOGLE_APPLICATION_CREDENTIALS=... ts-node -P scripts/tsconfig.json scripts/fix-unknown-reviewers.ts --execute\n',
    );
  } else {
    console.log('[EXECUTE] reviews 레코드의 reviewerName 실제 쓰기가 실행됩니다.\n');
  }

  const result = await fixUnknownReviewers(dryRun);

  console.log('\n========== Unknown 리뷰어 수정 완료 ==========');
  console.log(`전체 대상 수  : ${result.total}`);
  console.log(`수정 완료     : ${result.fixed}${dryRun ? ' (dry-run, 실제 쓰기 없음)' : ''}`);
  console.log(`스킵 수       : ${result.skipped} (reviewerId 없음 또는 이름 조회 실패)`);
  console.log(`오류 수       : ${result.errors}`);
  console.log('===============================================');

  if (result.errors > 0) {
    process.exit(1);
  }
}

main().catch((err) => {
  console.error('[FATAL]', err);
  process.exit(1);
});
