/**
 * 동시성 테스트
 *
 * Firebase Emulator 없이 순수 로직 레벨에서 동시 호출 시나리오를 시뮬레이션합니다.
 *   1. 두 리뷰어가 동시에 같은 문서에 리뷰 → 상태 전이 충돌 없이 정상 처리
 *   2. 낙관적 잠금: 두 편집자가 동시에 같은 문서 업데이트 → version 충돌 감지
 *   3. 리뷰 + 경량 수정 동시 발생 → 상태 전이 우선순위 올바름
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { transitionStatus, canApprove, assessRiskLevel } from '../../reviewStateMachine';
import { isLightweightEditExempt } from '../../lightweightEditExemption';

// ── firebase-admin / firebase-functions mock ─────────────────────────────────
vi.mock('firebase-admin', () => {
  const firestoreFn = vi.fn(() => ({}));
  (firestoreFn as any).Timestamp = { now: vi.fn(() => ({ seconds: 1700000000, nanoseconds: 0 })) };
  (firestoreFn as any).FieldValue = { serverTimestamp: () => 'SERVER_TIMESTAMP' };
  return {
    firestore: firestoreFn,
    apps: [{}],
    initializeApp: vi.fn(),
  };
});

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

// ── 헬퍼: 경량 수정 DB mock ──────────────────────────────────────────────────
function makeExemptionDb(recentCount: number) {
  const mockQuery = {
    where: vi.fn().mockReturnThis(),
    get: vi.fn().mockResolvedValue({ size: recentCount }),
  };
  return {
    collection: vi.fn(() => ({
      doc: vi.fn(() => ({
        collection: vi.fn(() => mockQuery),
      })),
    })),
  } as any;
}

// ════════════════════════════════════════════════════════════════════════════
// 1. 두 리뷰어가 동시에 같은 문서에 리뷰 → 상태 전이 충돌 없이 정상 처리
// ════════════════════════════════════════════════════════════════════════════

describe('동시성 테스트 1: 두 리뷰어 동시 리뷰', () => {
  it('두 reviewer가 같은 in_review 문서에 동시 approve → 두 호출 모두 approved로 전이 성공', async () => {
    // 두 리뷰어가 각자 독립적으로 상태 전이를 시도 (Firestore transaction mock)
    let currentStatus = 'in_review' as const;
    const riskLevel = assessRiskLevel('newsletter');

    // 리뷰어 1의 approve
    const reviewer1Reviews = [{ decision: 'approve', reviewerId: 'reviewer-1', reviewerRole: 'reviewer' }];
    const reviewer1CanApprove = canApprove(riskLevel, reviewer1Reviews);

    // 리뷰어 2의 approve (동시 호출 - 동일 시점의 리뷰 목록으로 판정)
    const reviewer2Reviews = [{ decision: 'approve', reviewerId: 'reviewer-2', reviewerRole: 'reviewer' }];
    const reviewer2CanApprove = canApprove(riskLevel, reviewer2Reviews);

    // 두 리뷰어 모두 저위험이므로 각자 단독으로 canApprove true
    expect(reviewer1CanApprove).toBe(true);
    expect(reviewer2CanApprove).toBe(true);

    // 두 호출을 Promise.all로 동시 시뮬레이션
    const [result1, result2] = await Promise.all([
      Promise.resolve(transitionStatus('in_review', 'approved')),
      Promise.resolve(transitionStatus('in_review', 'approved')),
    ]);

    // 두 결과 모두 approved
    expect(result1).toBe('approved');
    expect(result2).toBe('approved');
  });

  it('두 reviewer가 동시에 리뷰하되 한 명은 reject → reject 전이 성공', async () => {
    const [result1, result2] = await Promise.all([
      Promise.resolve(transitionStatus('in_review', 'approved')),
      Promise.resolve(transitionStatus('in_review', 'rejected')),
    ]);

    expect(result1).toBe('approved');
    expect(result2).toBe('rejected');
  });

  it('고위험 문서: reviewer와 admin이 동시에 approve → 합산 후 canApprove true', async () => {
    const riskLevel = assessRiskLevel('court_ruling');

    // 동시에 각자의 approve가 제출된 후, 최종 합산 리뷰 목록으로 판정
    const allReviews = await Promise.all([
      Promise.resolve({ decision: 'approve', reviewerId: 'reviewer-1', reviewerRole: 'reviewer' }),
      Promise.resolve({ decision: 'approve', reviewerId: 'admin-1', reviewerRole: 'admin' }),
    ]);

    expect(canApprove(riskLevel, allReviews)).toBe(true);

    const finalStatus = transitionStatus('in_review', 'approved');
    expect(finalStatus).toBe('approved');
  });
});

// ════════════════════════════════════════════════════════════════════════════
// 2. 낙관적 잠금: 두 편집자 동시 업데이트 → version 충돌 감지
// ════════════════════════════════════════════════════════════════════════════

describe('동시성 테스트 2: 낙관적 잠금 (version 필드 충돌)', () => {
  // 낙관적 잠금 시뮬레이션 헬퍼
  function simulateOptimisticUpdate(
    currentVersion: number,
    expectedVersion: number,
    newContent: string
  ): { success: boolean; reason?: string } {
    if (currentVersion !== expectedVersion) {
      return { success: false, reason: `version conflict: expected ${expectedVersion}, got ${currentVersion}` };
    }
    return { success: true };
  }

  it('편집자 A: version=1 예상, 현재 version=1 → 업데이트 성공', () => {
    const result = simulateOptimisticUpdate(1, 1, '수정된 내용 A');
    expect(result.success).toBe(true);
  });

  it('편집자 B: version=1 예상, 현재 version=2 (A가 먼저 업데이트) → 충돌 감지', () => {
    // 편집자 A가 version 1→2로 업데이트한 후, B는 version=1로 시도
    const result = simulateOptimisticUpdate(2, 1, '수정된 내용 B');
    expect(result.success).toBe(false);
    expect(result.reason).toContain('version conflict');
  });

  it('두 편집자 동시 시도: 첫 번째만 성공, 두 번째는 충돌', async () => {
    let serverVersion = 1;

    // 편집자 A와 B가 동시에 version=1로 읽어서 업데이트 시도
    const editorA = { expectedVersion: 1, content: '편집자 A의 내용' };
    const editorB = { expectedVersion: 1, content: '편집자 B의 내용' };

    // 시뮬레이션: A가 0ms 지연 없이 먼저 성공
    const resultA = simulateOptimisticUpdate(serverVersion, editorA.expectedVersion, editorA.content);
    if (resultA.success) serverVersion++;  // A 성공 → version 증가

    // B는 이미 version이 변경된 상태에서 시도
    const resultB = simulateOptimisticUpdate(serverVersion, editorB.expectedVersion, editorB.content);

    expect(resultA.success).toBe(true);
    expect(resultB.success).toBe(false);
    expect(serverVersion).toBe(2);
  });
});

// ════════════════════════════════════════════════════════════════════════════
// 3. 리뷰 + 경량 수정 동시 발생 → 상태 전이 우선순위 올바름
// ════════════════════════════════════════════════════════════════════════════

describe('동시성 테스트 3: 리뷰 + 경량 수정 동시 발생', () => {
  it('approved 문서: 리뷰 제출 + 경량 수정 면제 동시 → 면제 시 상태 유지(approved)', async () => {
    const currentStatus = 'approved' as const;

    // 경량 수정 면제 체크 (조건 모두 충족)
    const db = makeExemptionDb(0);
    const exemptionResult = await isLightweightEditExempt({
      oldContent: '보험 안내 문서입니다.',
      newContent: '보험 상세 안내 문서입니다.',
      sourceType: 'newsletter',
      docId: 'doc-concurrent',
      db,
    });

    expect(exemptionResult.exempt).toBe(true);

    // 면제이므로 상태 전이 없이 approved 유지
    // (실제 onDocumentUpdate에서 return early)
    const statusAfterExemption = currentStatus;  // 변경 없음
    expect(statusAfterExemption).toBe('approved');
  });

  it('approved 문서: 경량 수정 면제 불가 → needs_re_review 전이', async () => {
    const currentStatus = 'approved' as const;

    // 경량 수정 면제 불가 (고위험 카테고리)
    const db = makeExemptionDb(0);
    const exemptionResult = await isLightweightEditExempt({
      oldContent: '판례에 따르면 보장합니다.',
      newContent: '판례에 따르면 보장하지 않습니다. 제1조 적용.',
      sourceType: 'court_ruling',
      docId: 'doc-concurrent-2',
      db,
    });

    expect(exemptionResult.exempt).toBe(false);

    // 면제 불가 → needs_re_review 전이 필요
    expect(isValidTransition(currentStatus, 'needs_re_review')).toBe(true);
    const newStatus = transitionStatus(currentStatus, 'needs_re_review');
    expect(newStatus).toBe('needs_re_review');
  });

  it('published 문서: 경량 수정 면제 불가 → needs_re_review 전이', async () => {
    const currentStatus = 'published' as const;

    const db = makeExemptionDb(3); // 횟수 초과
    const exemptionResult = await isLightweightEditExempt({
      oldContent: '보험 안내입니다.',
      newContent: '보험 상세 안내입니다.',
      sourceType: 'newsletter',
      docId: 'doc-concurrent-3',
      db,
    });

    expect(exemptionResult.exempt).toBe(false);
    expect(exemptionResult.failedConditions).toContain('hasLowRecentExemptionCount');

    // published → needs_re_review
    const newStatus = transitionStatus(currentStatus, 'needs_re_review');
    expect(newStatus).toBe('needs_re_review');
  });
});

// isValidTransition import (동시성 테스트 3에서 사용)
import { isValidTransition } from '../../reviewStateMachine';
