/**
 * reviewOnCreate Cloud Function 단위 테스트
 *
 * 테스트 대상: onReviewCreate (documents/{docId}/reviews/{reviewId} onCreate 트리거)
 *
 * Firebase 에뮬레이터 없이 vi.mock 기반으로 동작
 */

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

// ── mock 상태 (테스트 간 공유, beforeEach에서 초기화) ────────────────────────
// vi.mock 팩토리 내에서는 외부 let 변수에 직접 접근 불가 (hoisting 이슈)
// → 전역 객체(global.__reviewTest)를 통해 동적으로 주입

declare global {
  // eslint-disable-next-line no-var
  var __reviewTest: {
    docData: Record<string, any>;
    reviewsData: Array<{ id: string; data: () => Record<string, any> }>;
    reviewerRole: string;
    docUpdate: ReturnType<typeof vi.fn>;
    logSet: ReturnType<typeof vi.fn>;
    writeAuditLogMock: ReturnType<typeof vi.fn>;
    canApproveMock: ReturnType<typeof vi.fn>;
  };
}

globalThis.__reviewTest = {
  docData: {},
  reviewsData: [],
  reviewerRole: 'reviewer',
  docUpdate: vi.fn().mockResolvedValue(undefined),
  logSet: vi.fn().mockResolvedValue(undefined),
  writeAuditLogMock: vi.fn().mockResolvedValue('audit-id'),
  canApproveMock: vi.fn().mockReturnValue(true),
};

// ── firebase-admin mock ──────────────────────────────────────────────────────
vi.mock('firebase-admin', () => {
  const firestoreFn = (colName: string) => ({
    collection: (name: string) => ({
      doc: (_id: string) => ({
        get: async () => {
          if (name === 'documents') {
            const d = globalThis.__reviewTest.docData;
            return {
              exists: d.__exists !== false,
              data: () => (d.__exists !== false ? d : undefined),
            };
          }
          if (name === 'users') {
            return {
              exists: true,
              data: () => ({ role: globalThis.__reviewTest.reviewerRole }),
            };
          }
          return { exists: false, data: () => undefined };
        },
        update: (...args: any[]) => globalThis.__reviewTest.docUpdate(...args),
        collection: (_sub: string) => ({
          get: async () => ({ docs: globalThis.__reviewTest.reviewsData }),
          doc: () => ({ set: globalThis.__reviewTest.logSet, id: 'audit-log-id' }),
        }),
      }),
    }),
  });

  // admin.firestore()는 Firestore 인스턴스를 반환하는 함수이면서,
  // admin.firestore.FieldValue 같은 정적 프로퍼티도 갖는다
  const firestoreCallable = () => ({
    collection: (name: string) => ({
      doc: (_id: string) => ({
        get: async () => {
          const d = globalThis.__reviewTest.docData;
          if (name === 'documents') {
            return {
              exists: d.__exists !== false,
              data: () => (d.__exists !== false ? d : undefined),
            };
          }
          if (name === 'users') {
            return {
              exists: true,
              data: () => ({ role: globalThis.__reviewTest.reviewerRole }),
            };
          }
          return { exists: false, data: () => undefined };
        },
        update: (...args: any[]) => globalThis.__reviewTest.docUpdate(...args),
        collection: (_sub: string) => ({
          get: async () => ({ docs: globalThis.__reviewTest.reviewsData }),
          doc: () => ({ set: globalThis.__reviewTest.logSet, id: 'audit-log-id' }),
        }),
      }),
    }),
  });

  firestoreCallable.FieldValue = {
    serverTimestamp: () => 'SERVER_TIMESTAMP',
  };

  return {
    firestore: firestoreCallable,
    apps: [{}],
    initializeApp: () => {},
  };
});

// ── firebase-functions mock ───────────────────────────────────────────────────
vi.mock('firebase-functions', () => ({
  logger: {
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  },
  firestore: {
    document: () => ({
      onCreate: (handler: any) => handler,
    }),
  },
}));

// ── 선행 모듈 mock ────────────────────────────────────────────────────────────
vi.mock('../reviewStateMachine', () => ({
  transitionStatus: (from: string, to: string) => {
    const VALID: Record<string, string[]> = {
      in_review: ['approved', 'rejected', 'revision_requested'],
      approved: ['needs_re_review'],
      published: ['needs_re_review'],
      draft: ['in_review'],
      rejected: ['draft'],
      revision_requested: ['draft', 'in_review'],
      needs_re_review: ['in_review'],
    };
    if (VALID[from]?.includes(to)) return to;
    throw new Error(`Invalid transition: ${from} → ${to}`);
  },
  assessRiskLevel: (sourceType?: string) => {
    const HIGH = ['court_ruling', 'regulation', 'policy_pdf'];
    return sourceType && HIGH.includes(sourceType) ? 'high' : 'low';
  },
  canApprove: (...args: any[]) => globalThis.__reviewTest.canApproveMock(...args),
}));

vi.mock('../auditLog', () => ({
  writeAuditLog: (...args: any[]) => globalThis.__reviewTest.writeAuditLogMock(...args),
}));

// ── 모듈 import ───────────────────────────────────────────────────────────────
import * as functions from 'firebase-functions';
import { onReviewCreate } from '../reviewOnCreate';
import { writeAuditLog } from '../auditLog';

// ── 헬퍼 ─────────────────────────────────────────────────────────────────────
function makeSnap(reviewData: Record<string, any>) {
  return { data: () => reviewData };
}

function makeContext(docId = 'doc-1', reviewId = 'review-1') {
  return { params: { docId, reviewId } };
}

// ── 테스트 ───────────────────────────────────────────────────────────────────
describe('onReviewCreate', () => {
  beforeEach(() => {
    globalThis.__reviewTest.docUpdate.mockClear();
    globalThis.__reviewTest.logSet.mockClear();
    globalThis.__reviewTest.writeAuditLogMock.mockClear();
    globalThis.__reviewTest.canApproveMock.mockClear();
    globalThis.__reviewTest.canApproveMock.mockReturnValue(true);
    vi.mocked(functions.logger.info).mockClear();
    vi.mocked(functions.logger.warn).mockClear();
    vi.mocked(functions.logger.error).mockClear();

    // 기본 문서 데이터: in_review, low risk, riskLevel 이미 설정됨
    globalThis.__reviewTest.docData = {
      status: 'in_review',
      sourceType: 'wiki',
      authorId: 'author-1',
      riskLevel: 'low',
    };
    globalThis.__reviewTest.reviewsData = [];
    globalThis.__reviewTest.reviewerRole = 'reviewer';
  });

  // ── 케이스 1: reviewer approve → low risk → status: approved ────────────────
  it('reviewer approve → low risk 문서 → status: approved', async () => {
    globalThis.__reviewTest.reviewsData = [
      { id: 'review-1', data: () => ({ decision: 'approve', reviewerId: 'reviewer-1' }) },
    ];
    globalThis.__reviewTest.canApproveMock.mockReturnValue(true);

    const snap = makeSnap({ decision: 'approve', reviewerId: 'reviewer-1', reviewerName: '홍길동' });
    await (onReviewCreate as any)(snap, makeContext());

    expect(globalThis.__reviewTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'approved' })
    );
    expect(globalThis.__reviewTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({ action: 'review_submitted', newStatus: 'approved' })
    );
  });

  // ── 케이스 2: reviewer approve → high risk → status 유지 (admin 승인 대기) ──
  it('reviewer approve → high risk 문서 → status 유지 (admin 승인 대기)', async () => {
    globalThis.__reviewTest.docData = {
      status: 'in_review',
      sourceType: 'court_ruling',
      riskLevel: 'high',
    };
    globalThis.__reviewTest.reviewsData = [
      { id: 'review-1', data: () => ({ decision: 'approve', reviewerId: 'reviewer-1' }) },
    ];
    globalThis.__reviewTest.canApproveMock.mockReturnValue(false);

    const snap = makeSnap({ decision: 'approve', reviewerId: 'reviewer-1', reviewerName: '홍길동' });
    await (onReviewCreate as any)(snap, makeContext());

    // 상태 업데이트 없음 (riskLevel은 이미 docData에 있으므로 step 4도 skip)
    expect(globalThis.__reviewTest.docUpdate).not.toHaveBeenCalled();
    // 추가 승인 대기 로그
    expect(functions.logger.info).toHaveBeenCalledWith(
      expect.stringContaining('waiting for additional approvals')
    );
  });

  // ── 케이스 3: reviewer reject → status: rejected ─────────────────────────────
  it('reviewer reject → status: rejected', async () => {
    const snap = makeSnap({
      decision: 'reject',
      reviewerId: 'reviewer-1',
      reviewerName: '홍길동',
      comment: '부정확한 내용이 포함되어 있습니다.',
    });
    await (onReviewCreate as any)(snap, makeContext());

    expect(globalThis.__reviewTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'rejected' })
    );
    expect(globalThis.__reviewTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({ action: 'review_submitted', newStatus: 'rejected' })
    );
  });

  // ── 케이스 4: reviewer request_revision → status: revision_requested ─────────
  it('reviewer request_revision → status: revision_requested', async () => {
    const snap = makeSnap({
      decision: 'request_revision',
      reviewerId: 'reviewer-1',
      reviewerName: '홍길동',
      comment: '수정이 필요합니다.',
    });
    await (onReviewCreate as any)(snap, makeContext());

    expect(globalThis.__reviewTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'revision_requested' })
    );
    expect(globalThis.__reviewTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({ action: 'review_submitted', newStatus: 'revision_requested' })
    );
  });

  // ── 케이스 5: 문서 미존재 → 에러 로깅 후 return ─────────────────────────────
  it('문서 미존재 → 에러 로깅 후 return', async () => {
    globalThis.__reviewTest.docData = { __exists: false };

    const snap = makeSnap({ decision: 'approve', reviewerId: 'reviewer-1', reviewerName: '홍길동' });
    await (onReviewCreate as any)(snap, makeContext('nonexistent-doc'));

    expect(functions.logger.error).toHaveBeenCalledWith(
      expect.stringContaining('not found')
    );
    expect(globalThis.__reviewTest.docUpdate).not.toHaveBeenCalled();
    expect(globalThis.__reviewTest.writeAuditLogMock).not.toHaveBeenCalled();
  });

  // ── 케이스 6: riskLevel 미설정 문서 → 자동 판정 + 감사 로그 기록 ─────────────
  it('riskLevel 미설정 문서 → riskLevel 자동 판정 + auditLog 기록', async () => {
    globalThis.__reviewTest.docData = {
      status: 'in_review',
      sourceType: 'regulation',
      // riskLevel 없음
    };
    globalThis.__reviewTest.reviewsData = [
      { id: 'review-1', data: () => ({ decision: 'approve', reviewerId: 'reviewer-admin' }) },
    ];
    globalThis.__reviewTest.reviewerRole = 'admin';
    globalThis.__reviewTest.canApproveMock.mockReturnValue(true);

    const snap = makeSnap({ decision: 'approve', reviewerId: 'reviewer-admin', reviewerName: '관리자' });
    await (onReviewCreate as any)(snap, makeContext());

    // step 3: 상태 업데이트 (approved), riskLevel: high 포함
    expect(globalThis.__reviewTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ riskLevel: 'high' })
    );
    // step 4: risk_level_assessed 감사 로그
    expect(globalThis.__reviewTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({
        action: 'risk_level_assessed',
        metadata: expect.objectContaining({ riskLevel: 'high' }),
      })
    );
  });

  // ── 케이스 7: 유효하지 않은 상태 전이 → warn 후 return ───────────────────────
  it('유효하지 않은 상태 전이 → warn 로깅 후 return', async () => {
    globalThis.__reviewTest.docData = { status: 'draft', sourceType: 'wiki', riskLevel: 'low' };

    const snap = makeSnap({ decision: 'reject', reviewerId: 'reviewer-1', reviewerName: '홍길동' });
    await (onReviewCreate as any)(snap, makeContext());

    expect(functions.logger.warn).toHaveBeenCalledWith(
      expect.stringContaining('Cannot transition')
    );
    expect(globalThis.__reviewTest.docUpdate).not.toHaveBeenCalled();
  });
});
