/**
 * auditLog.ts 단위 테스트
 *
 * 테스트 대상:
 *  - writeAuditLog : 감사 로그 Firestore 기록 + Cloud Logging 이중화
 *
 * Firebase 에뮬레이터 없이 vi.mock 기반으로 동작
 * vitest로 실행 (nextapp/vitest.config.ts가 ../functions/src/**를 포함)
 */

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

// ── vi.hoisted: mock 팩토리보다 먼저 초기화되는 변수 ─────────────────────────

const { mockSet, mockLogRefId, mockLoggerInfo, mockTimestampNow } = vi.hoisted(() => {
  const mockSet = vi.fn().mockResolvedValue(undefined);
  const mockLogRefId = 'mock-audit-log-id-123';
  const mockLoggerInfo = vi.fn();
  const mockTimestampNow = vi.fn(() => ({ seconds: 1234567890, nanoseconds: 0 }));
  return { mockSet, mockLogRefId, mockLoggerInfo, mockTimestampNow };
});

// ── firebase-admin mock ──────────────────────────────────────────────────────

vi.mock('firebase-admin', () => {
  const mockLogRef = {
    id: mockLogRefId,
    set: mockSet,
  };

  const mockAuditLogsCollection = {
    doc: vi.fn(() => mockLogRef),
  };

  const mockDocRef = {
    collection: vi.fn((_name: string) => mockAuditLogsCollection),
  };

  const mockDocumentsCollection = {
    doc: vi.fn(() => mockDocRef),
  };

  const mockFirestoreInstance = {
    collection: vi.fn((_name: string) => mockDocumentsCollection),
  };

  const firestoreFn = vi.fn(() => mockFirestoreInstance);
  // admin.firestore.Timestamp 접근을 위해 함수에 Timestamp 프로퍼티 추가
  (firestoreFn as unknown as Record<string, unknown>).Timestamp = {
    now: mockTimestampNow,
  };

  return {
    firestore: firestoreFn,
    apps: [{}],
    initializeApp: vi.fn(),
  };
});

// ── firebase-functions mock ──────────────────────────────────────────────────

vi.mock('firebase-functions', () => ({
  logger: {
    info: mockLoggerInfo,
    error: vi.fn(),
  },
}));

// ── 모듈 import (mock 선언 후) ───────────────────────────────────────────────

import { writeAuditLog } from '../auditLog';

// ── writeAuditLog 테스트 ─────────────────────────────────────────────────────

describe('writeAuditLog', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockSet.mockResolvedValue(undefined);
    mockTimestampNow.mockReturnValue({ seconds: 1234567890, nanoseconds: 0 });
  });

  it('1. 감사 로그 정상 기록 - logRef.id 반환', async () => {
    const logId = await writeAuditLog({
      docId: 'doc-001',
      action: 'status_change',
      actorId: 'user-001',
    });

    expect(logId).toBe(mockLogRefId);
    expect(mockSet).toHaveBeenCalledOnce();
  });

  it('2. 필수 필드 포함 확인 (docId, action, actorId, createdAt)', async () => {
    await writeAuditLog({
      docId: 'doc-002',
      action: 'review_submitted',
      actorId: 'reviewer-001',
      actorName: '홍길동',
    });

    const setCall = mockSet.mock.calls[0][0];
    expect(setCall).toHaveProperty('id', mockLogRefId);
    expect(setCall).toHaveProperty('docId', 'doc-002');
    expect(setCall).toHaveProperty('action', 'review_submitted');
    expect(setCall).toHaveProperty('actorId', 'reviewer-001');
    expect(setCall).toHaveProperty('createdAt');
  });

  it('3. Cloud Logging (functions.logger.info) 호출 확인', async () => {
    await writeAuditLog({
      docId: 'doc-003',
      action: 'status_change',
      actorId: 'admin-001',
      previousStatus: 'in_review',
      newStatus: 'approved',
    });

    expect(mockLoggerInfo).toHaveBeenCalledOnce();
    const [logLabel, logPayload] = mockLoggerInfo.mock.calls[0];
    expect(logLabel).toBe('AUDIT_LOG');
    expect(logPayload).toMatchObject({
      docId: 'doc-003',
      action: 'status_change',
      actorId: 'admin-001',
      previousStatus: 'in_review',
      newStatus: 'approved',
    });
  });

  it('4. metadata 포함 기록', async () => {
    const metadata = { riskLevel: 'high', reviewCount: 2, reason: '법령 개정' };

    await writeAuditLog({
      docId: 'doc-004',
      action: 'risk_level_assessed',
      actorId: 'system',
      metadata,
    });

    const setCall = mockSet.mock.calls[0][0];
    expect(setCall).toHaveProperty('metadata');
    expect(setCall.metadata).toEqual(metadata);
  });
});
