/**
 * onDocumentUpdateReview Cloud Function 단위 테스트
 *
 * 테스트 대상: onDocumentUpdateReview (documents/{docId} onUpdate 트리거)
 * 목적: 승인/published 상태 문서 content 변경 시 경량 면제 판단 → needs_re_review 전환 또는 면제
 *
 * Firebase 에뮬레이터 없이 vi.mock 기반으로 동작
 */

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

// ── mock 상태 (global로 공유) ─────────────────────────────────────────────────
declare global {
  // eslint-disable-next-line no-var
  var __docUpdateTest: {
    docUpdate: ReturnType<typeof vi.fn>;
    writeAuditLogMock: ReturnType<typeof vi.fn>;
    isLightweightExemptMock: ReturnType<typeof vi.fn>;
  };
}

globalThis.__docUpdateTest = {
  docUpdate: vi.fn().mockResolvedValue(undefined),
  writeAuditLogMock: vi.fn().mockResolvedValue('audit-id'),
  isLightweightExemptMock: vi.fn().mockResolvedValue({ exempt: false, failedConditions: ['isMinorCharChange'] }),
};

// ── firebase-admin mock ───────────────────────────────────────────────────────
vi.mock('firebase-admin', () => {
  const firestoreCallable = () => ({
    collection: (_name: string) => ({
      doc: (_id: string) => ({
        update: (...args: any[]) => globalThis.__docUpdateTest.docUpdate(...args),
        collection: (_sub: string) => ({
          where: (_field: string) => ({
            where: (_f2: string) => ({
              get: async () => ({ size: 0 }),
            }),
          }),
          doc: () => ({ set: vi.fn(), 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: () => ({
      onUpdate: (handler: any) => handler,
    }),
  },
}));

// ── 선행 모듈 mock ────────────────────────────────────────────────────────────
vi.mock('../reviewStateMachine', () => ({
  isValidTransition: (from: string, to: string) => {
    const VALID: Record<string, string[]> = {
      approved: ['needs_re_review'],
      published: ['needs_re_review'],
      in_review: ['approved', 'rejected', 'revision_requested'],
      draft: ['in_review'],
    };
    return VALID[from]?.includes(to) ?? false;
  },
}));

vi.mock('../regulationChangeDetector', () => ({
  detectRegulationChange: (beforeData: Record<string, any>, afterData: Record<string, any>) => {
    const regulationTypes = ['regulation', 'policy_pdf'];
    if (!regulationTypes.includes(afterData.sourceType)) {
      return { shouldReReview: false, reason: 'not_regulation' };
    }
    const fields = ['sourceMeta', 'sourceUrl', 'regulationId', 'effectiveDate'];
    for (const field of fields) {
      if (JSON.stringify(beforeData[field] ?? null) !== JSON.stringify(afterData[field] ?? null)) {
        return { shouldReReview: true, reason: `규정 메타데이터 변경: ${field}` };
      }
    }
    return { shouldReReview: false, reason: 'no_regulation_change' };
  },
}));

vi.mock('../lightweightEditExemption', () => ({
  isLightweightEditExempt: (...args: any[]) =>
    globalThis.__docUpdateTest.isLightweightExemptMock(...args),
}));

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

// ── 모듈 import ───────────────────────────────────────────────────────────────
import { onDocumentUpdateReview } from '../onDocumentUpdate';

// ── 헬퍼 ─────────────────────────────────────────────────────────────────────
function makeChange(beforeData: Record<string, any>, afterData: Record<string, any>) {
  return {
    before: { data: () => beforeData },
    after: { data: () => afterData },
  };
}

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

// ── 테스트 ────────────────────────────────────────────────────────────────────
describe('onDocumentUpdateReview', () => {
  beforeEach(() => {
    globalThis.__docUpdateTest.docUpdate.mockClear();
    globalThis.__docUpdateTest.writeAuditLogMock.mockClear();
    globalThis.__docUpdateTest.isLightweightExemptMock.mockClear();
    // 기본: 면제 불가
    globalThis.__docUpdateTest.isLightweightExemptMock.mockResolvedValue({
      exempt: false,
      failedConditions: ['isMinorCharChange'],
    });
  });

  // ── 케이스 1: approved 문서 content 변경, 경량 면제 통과 → status 유지 ────────
  it('approved 문서 content 변경, 경량 면제 통과 → status 유지, 감사 로그 기록', async () => {
    globalThis.__docUpdateTest.isLightweightExemptMock.mockResolvedValue({
      exempt: true,
      failedConditions: [],
    });

    const change = makeChange(
      { content: '오탈자 수정 전', status: 'approved', sourceType: 'wiki', authorId: 'author-1' },
      { content: '오탈자 수정 후', status: 'approved', sourceType: 'wiki', authorId: 'author-1', editingBy: 'editor-1' }
    );

    await (onDocumentUpdateReview as any)(change, makeContext());

    // 상태 업데이트 없음
    expect(globalThis.__docUpdateTest.docUpdate).not.toHaveBeenCalled();
    // 면제 감사 로그 기록
    expect(globalThis.__docUpdateTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({ action: 'lightweight_edit_exempted', actorId: 'editor-1' })
    );
  });

  // ── 케이스 2: approved 문서 content 변경, 경량 면제 미통과 → needs_re_review ──
  it('approved 문서 content 변경, 경량 면제 미통과 → status: needs_re_review', async () => {
    globalThis.__docUpdateTest.isLightweightExemptMock.mockResolvedValue({
      exempt: false,
      failedConditions: ['isMinorCharChange', 'hasNoNumericChange'],
    });

    const change = makeChange(
      { content: '기존 내용 (상세한 설명)', status: 'approved', sourceType: 'wiki', authorId: 'author-1' },
      { content: '완전히 다른 내용 (수정 후)', status: 'approved', sourceType: 'wiki', authorId: 'author-1', editingBy: 'editor-1' }
    );

    await (onDocumentUpdateReview as any)(change, makeContext());

    // needs_re_review로 상태 전환
    expect(globalThis.__docUpdateTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'needs_re_review' })
    );
    // 상태 변경 감사 로그
    expect(globalThis.__docUpdateTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({
        action: 'status_change',
        newStatus: 'needs_re_review',
        previousStatus: 'approved',
        metadata: expect.objectContaining({
          reason: 'content_modified_after_approval',
          failedExemptionConditions: ['isMinorCharChange', 'hasNoNumericChange'],
        }),
      })
    );
  });

  // ── 케이스 3: draft 문서 content 변경 → 스킵 ─────────────────────────────────
  it('draft 문서 content 변경 → approved/published 아님 → 스킵', async () => {
    const change = makeChange(
      { content: '드래프트 내용 원본', status: 'draft', authorId: 'author-1' },
      { content: '드래프트 내용 수정', status: 'draft', authorId: 'author-1' }
    );

    await (onDocumentUpdateReview as any)(change, makeContext());

    expect(globalThis.__docUpdateTest.docUpdate).not.toHaveBeenCalled();
    expect(globalThis.__docUpdateTest.isLightweightExemptMock).not.toHaveBeenCalled();
    expect(globalThis.__docUpdateTest.writeAuditLogMock).not.toHaveBeenCalled();
  });

  // ── 케이스 4: approved 문서 status만 변경 (content 동일) → 스킵 ───────────────
  it('approved 문서 status만 변경 (content 동일) → 무한 루프 방지 → 스킵', async () => {
    const sameContent = '동일한 콘텐츠';
    const change = makeChange(
      { content: sameContent, status: 'approved', authorId: 'author-1' },
      { content: sameContent, status: 'needs_re_review', authorId: 'author-1' }
    );

    await (onDocumentUpdateReview as any)(change, makeContext());

    expect(globalThis.__docUpdateTest.docUpdate).not.toHaveBeenCalled();
    expect(globalThis.__docUpdateTest.isLightweightExemptMock).not.toHaveBeenCalled();
    expect(globalThis.__docUpdateTest.writeAuditLogMock).not.toHaveBeenCalled();
  });

  // ── 케이스 5: published 문서 content 변경, 면제 미통과 → needs_re_review ───────
  it('published 문서 content 변경, 면제 미통과 → needs_re_review 전환', async () => {
    globalThis.__docUpdateTest.isLightweightExemptMock.mockResolvedValue({
      exempt: false,
      failedConditions: ['hasNoNumericChange'],
    });

    const change = makeChange(
      { content: '보험료 100만원', status: 'published', sourceType: 'wiki', authorId: 'author-1' },
      { content: '보험료 200만원', status: 'published', sourceType: 'wiki', authorId: 'author-1' }
    );

    await (onDocumentUpdateReview as any)(change, makeContext());

    expect(globalThis.__docUpdateTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'needs_re_review' })
    );
  });

  // ── 케이스 6: editingBy 없을 때 authorId를 actorId로 사용 ────────────────────
  it('editingBy 없을 때 authorId를 actorId로 fallback', async () => {
    globalThis.__docUpdateTest.isLightweightExemptMock.mockResolvedValue({
      exempt: true,
      failedConditions: [],
    });

    const change = makeChange(
      { content: '원본', status: 'approved', sourceType: 'wiki', authorId: 'author-1' },
      { content: '수정', status: 'approved', sourceType: 'wiki', authorId: 'author-1' }
      // editingBy 없음
    );

    await (onDocumentUpdateReview as any)(change, makeContext());

    expect(globalThis.__docUpdateTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({ actorId: 'author-1' })
    );
  });

  // ── 케이스 7: regulation 문서 sourceMeta 변경 → needs_re_review 전환 ──────────
  it('regulation 문서의 sourceMeta 변경 → needs_re_review 전환 (경량 면제 무관)', async () => {
    const change = makeChange(
      {
        content: '규정 내용',
        status: 'approved',
        sourceType: 'regulation',
        sourceMeta: { version: 1 },
        authorId: 'author-1',
        editingBy: 'editor-1',
      },
      {
        content: '규정 내용',
        status: 'approved',
        sourceType: 'regulation',
        sourceMeta: { version: 2 },
        authorId: 'author-1',
        editingBy: 'editor-1',
      }
    );

    await (onDocumentUpdateReview as any)(change, makeContext('doc-reg-1'));

    // needs_re_review로 상태 전환
    expect(globalThis.__docUpdateTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'needs_re_review' })
    );
    // 상태 변경 감사 로그
    expect(globalThis.__docUpdateTest.writeAuditLogMock).toHaveBeenCalledWith(
      expect.objectContaining({
        action: 'status_change',
        newStatus: 'needs_re_review',
        previousStatus: 'approved',
        metadata: expect.objectContaining({ reason: expect.stringContaining('sourceMeta') }),
      })
    );
    // 경량 면제 체크 호출 안 됨 (규정 변경으로 이미 처리)
    expect(globalThis.__docUpdateTest.isLightweightExemptMock).not.toHaveBeenCalled();
  });

  // ── 케이스 8: non-regulation 문서 sourceMeta 변경 → 기존 content 체크 로직 ────
  it('비규정(wiki) 문서 sourceMeta 변경 + content 동일 → 스킵 (규정 변경 아님)', async () => {
    const sameContent = '동일한 콘텐츠';
    const change = makeChange(
      {
        content: sameContent,
        status: 'approved',
        sourceType: 'wiki',
        sourceMeta: { version: 1 },
        authorId: 'author-1',
      },
      {
        content: sameContent,
        status: 'approved',
        sourceType: 'wiki',
        sourceMeta: { version: 2 },
        authorId: 'author-1',
      }
    );

    await (onDocumentUpdateReview as any)(change, makeContext('doc-wiki-1'));

    // 규정 변경 아니므로 content 체크로 이동, content 동일하므로 스킵
    expect(globalThis.__docUpdateTest.docUpdate).not.toHaveBeenCalled();
    expect(globalThis.__docUpdateTest.writeAuditLogMock).not.toHaveBeenCalled();
    expect(globalThis.__docUpdateTest.isLightweightExemptMock).not.toHaveBeenCalled();
  });

  // ── 케이스 9: non-regulation 문서 content 변경 → 기존 경량 면제 로직 동작 ──────
  it('비규정(wiki) 문서 content 변경 → 기존 경량 면제 체크 수행', async () => {
    globalThis.__docUpdateTest.isLightweightExemptMock.mockResolvedValue({
      exempt: false,
      failedConditions: ['isMinorCharChange'],
    });

    const change = makeChange(
      {
        content: '기존 내용',
        status: 'approved',
        sourceType: 'wiki',
        sourceMeta: { version: 1 },
        authorId: 'author-1',
      },
      {
        content: '변경된 내용 (더 길어진)',
        status: 'approved',
        sourceType: 'wiki',
        sourceMeta: { version: 1 },
        authorId: 'author-1',
      }
    );

    await (onDocumentUpdateReview as any)(change, makeContext('doc-wiki-2'));

    // 경량 면제 체크 호출됨
    expect(globalThis.__docUpdateTest.isLightweightExemptMock).toHaveBeenCalled();
    // 면제 불가 → needs_re_review 전환
    expect(globalThis.__docUpdateTest.docUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'needs_re_review' })
    );
  });
});
