/**
 * reviewerAssignment.ts 단위 테스트
 *
 * 테스트 대상:
 *  - filterSelfReviewers         : 자기 검토 금지 필터
 *  - selectReviewerRoundRobin    : 라운드 로빈 선택
 *  - validatePoolSize            : 리뷰어 풀 최소 3명 체크
 *  - filterConsecutiveAssignment : 연속 배정 제한
 *  - assignReviewer              : 통합 배정 함수
 *  - getReviewerPool             : 리뷰어 풀 조회
 *
 * vitest로 실행 (nextapp/vitest.config.ts가 ../functions/src/**를 포함)
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
  filterSelfReviewers,
  selectReviewerRoundRobin,
  validatePoolSize,
  filterConsecutiveAssignment,
  getReviewerPool,
  assignReviewer,
} from '../reviewerAssignment';
import type { ReviewerCandidate } from '../reviewerAssignment';

// ── 테스트 픽스처 ──────────────────────────────────────────────────────────

const makeReviewer = (
  uid: string,
  recentAssignmentCount: number,
  role: string = 'reviewer'
): ReviewerCandidate => ({
  uid,
  name: `User ${uid}`,
  role,
  recentAssignmentCount,
});

// ── filterSelfReviewers 테스트 ──────────────────────────────────────────────

describe('filterSelfReviewers - 자기 검토 금지', () => {
  it('1. contributorIds에 포함된 reviewer 제외', () => {
    const pool = [
      makeReviewer('reviewer-1', 0),
      makeReviewer('reviewer-2', 1),
      makeReviewer('reviewer-3', 2),
    ];
    const result = filterSelfReviewers(pool, ['reviewer-1']);
    expect(result).toHaveLength(2);
    expect(result.map((r) => r.uid)).not.toContain('reviewer-1');
  });

  it('contributorIds가 여러 명일 때 모두 제외', () => {
    const pool = [
      makeReviewer('reviewer-1', 0),
      makeReviewer('reviewer-2', 1),
      makeReviewer('reviewer-3', 2),
    ];
    const result = filterSelfReviewers(pool, ['reviewer-1', 'reviewer-2']);
    expect(result).toHaveLength(1);
    expect(result[0].uid).toBe('reviewer-3');
  });

  it('contributorIds가 빈 배열이면 모두 포함', () => {
    const pool = [makeReviewer('reviewer-1', 0), makeReviewer('reviewer-2', 1)];
    const result = filterSelfReviewers(pool, []);
    expect(result).toHaveLength(2);
  });

  it('풀이 빈 배열이면 빈 배열 반환', () => {
    const result = filterSelfReviewers([], ['reviewer-1']);
    expect(result).toHaveLength(0);
  });
});

// ── selectReviewerRoundRobin 테스트 ──────────────────────────────────────────

describe('selectReviewerRoundRobin - 라운드 로빈 선택', () => {
  it('2. 배정 횟수 적은 순으로 선택', () => {
    const pool = [
      makeReviewer('reviewer-1', 5),
      makeReviewer('reviewer-2', 2),
      makeReviewer('reviewer-3', 8),
    ];
    const result = selectReviewerRoundRobin(pool);
    expect(result?.uid).toBe('reviewer-2');
  });

  it('배정 횟수 0인 reviewer 선택', () => {
    const pool = [
      makeReviewer('reviewer-1', 3),
      makeReviewer('reviewer-2', 0),
      makeReviewer('reviewer-3', 1),
    ];
    const result = selectReviewerRoundRobin(pool);
    expect(result?.uid).toBe('reviewer-2');
  });

  it('6. 빈 풀 → null 반환', () => {
    const result = selectReviewerRoundRobin([]);
    expect(result).toBeNull();
  });

  it('모두 동일 횟수 → 하나 반환 (null 아님)', () => {
    const pool = [
      makeReviewer('reviewer-1', 3),
      makeReviewer('reviewer-2', 3),
      makeReviewer('reviewer-3', 3),
    ];
    const result = selectReviewerRoundRobin(pool);
    expect(result).not.toBeNull();
    expect(['reviewer-1', 'reviewer-2', 'reviewer-3']).toContain(result?.uid);
  });
});

// ── validatePoolSize 테스트 ──────────────────────────────────────────────────

describe('validatePoolSize - 최소 3명 체크', () => {
  it('3. pool 2명 → valid false, poolWarning true', () => {
    const pool = [makeReviewer('reviewer-1', 0), makeReviewer('reviewer-2', 1)];
    const result = validatePoolSize(pool);
    expect(result.valid).toBe(false);
    expect(result.count).toBe(2);
  });

  it('4. pool 3명 → valid true', () => {
    const pool = [
      makeReviewer('reviewer-1', 0),
      makeReviewer('reviewer-2', 1),
      makeReviewer('reviewer-3', 2),
    ];
    const result = validatePoolSize(pool);
    expect(result.valid).toBe(true);
    expect(result.count).toBe(3);
  });

  it('pool 5명 → valid true', () => {
    const pool = Array.from({ length: 5 }, (_, i) => makeReviewer(`reviewer-${i}`, i));
    const result = validatePoolSize(pool);
    expect(result.valid).toBe(true);
    expect(result.count).toBe(5);
  });

  it('pool 0명 → valid false', () => {
    const result = validatePoolSize([]);
    expect(result.valid).toBe(false);
    expect(result.count).toBe(0);
  });

  it('pool 1명 → valid false', () => {
    const pool = [makeReviewer('reviewer-1', 0)];
    const result = validatePoolSize(pool);
    expect(result.valid).toBe(false);
  });
});

// ── filterConsecutiveAssignment 테스트 ───────────────────────────────────────

describe('filterConsecutiveAssignment - 연속 배정 제한', () => {
  it('5. 최근 리뷰어 ID 제외', () => {
    const pool = [
      makeReviewer('reviewer-1', 0),
      makeReviewer('reviewer-2', 1),
      makeReviewer('reviewer-3', 2),
    ];
    const result = filterConsecutiveAssignment(pool, ['reviewer-1', 'reviewer-2']);
    expect(result).toHaveLength(1);
    expect(result[0].uid).toBe('reviewer-3');
  });

  it('recentReviewerIds가 빈 배열이면 모두 포함', () => {
    const pool = [makeReviewer('reviewer-1', 0), makeReviewer('reviewer-2', 1)];
    const result = filterConsecutiveAssignment(pool, []);
    expect(result).toHaveLength(2);
  });

  it('최근 리뷰어가 풀에 없으면 변화 없음', () => {
    const pool = [makeReviewer('reviewer-1', 0), makeReviewer('reviewer-2', 1)];
    const result = filterConsecutiveAssignment(pool, ['reviewer-99']);
    expect(result).toHaveLength(2);
  });

  it('최근 리뷰어 1명만 제외', () => {
    const pool = [
      makeReviewer('reviewer-1', 0),
      makeReviewer('reviewer-2', 1),
      makeReviewer('reviewer-3', 2),
    ];
    const result = filterConsecutiveAssignment(pool, ['reviewer-1']);
    expect(result).toHaveLength(2);
    expect(result.map((r) => r.uid)).not.toContain('reviewer-1');
  });
});

// ── getReviewerPool 테스트 ────────────────────────────────────────────────────

describe('getReviewerPool - Firestore 리뷰어 풀 조회', () => {
  const makeMockDb = (
    users: Array<{ uid: string; name: string; role: string }>,
    recentAssignmentCount: number = 0
  ) => {
    const mockAuditQuery = {
      where: vi.fn().mockReturnThis(),
      get: vi.fn().mockResolvedValue({ size: recentAssignmentCount }),
    };

    const mockUserDocs = users.map((u) => ({
      id: u.uid,
      data: () => ({ name: u.name, role: u.role }),
      ref: {
        collection: vi.fn(() => mockAuditQuery),
      },
    }));

    const mockUsersCollection = {
      where: vi.fn().mockReturnThis(),
      get: vi.fn().mockResolvedValue({ docs: mockUserDocs }),
    };

    return {
      collection: vi.fn(() => mockUsersCollection),
    } as any;
  };

  it('reviewer와 admin 역할만 반환', async () => {
    const db = makeMockDb([
      { uid: 'user-1', name: 'Alice', role: 'reviewer' },
      { uid: 'user-2', name: 'Bob', role: 'admin' },
    ]);
    const pool = await getReviewerPool(db);
    expect(pool).toHaveLength(2);
    expect(pool.map((p) => p.uid)).toContain('user-1');
    expect(pool.map((p) => p.uid)).toContain('user-2');
  });

  it('최근 배정 횟수가 0이면 recentAssignmentCount=0', async () => {
    const db = makeMockDb([{ uid: 'user-1', name: 'Alice', role: 'reviewer' }], 0);
    const pool = await getReviewerPool(db);
    expect(pool[0].recentAssignmentCount).toBe(0);
  });
});

// ── assignReviewer 통합 테스트 ────────────────────────────────────────────────

describe('assignReviewer - 통합 배정 함수', () => {
  const makeDb = (
    users: Array<{ uid: string; name: string; role: string }>,
    recentCount: number = 0
  ) => {
    const mockAuditQuery = {
      where: vi.fn().mockReturnThis(),
      get: vi.fn().mockResolvedValue({ size: recentCount }),
    };

    const mockUserDocs = users.map((u) => ({
      id: u.uid,
      data: () => ({ name: u.name, role: u.role }),
      ref: {
        collection: vi.fn(() => mockAuditQuery),
      },
    }));

    return {
      collection: vi.fn((name: string) => {
        if (name === 'users') {
          return {
            where: vi.fn().mockReturnThis(),
            get: vi.fn().mockResolvedValue({ docs: mockUserDocs }),
          };
        }
        // documents 컬렉션 (auditLogs 조회용)
        return {
          doc: vi.fn(() => ({
            collection: vi.fn(() => mockAuditQuery),
          })),
        };
      }),
    } as any;
  };

  it('정상 배정: reviewer 반환 + poolWarning false', async () => {
    const db = makeDb([
      { uid: 'reviewer-1', name: 'Alice', role: 'reviewer' },
      { uid: 'reviewer-2', name: 'Bob', role: 'reviewer' },
      { uid: 'reviewer-3', name: 'Carol', role: 'admin' },
    ]);
    const result = await assignReviewer({
      docId: 'doc-1',
      contributorIds: [],
      db,
    });
    expect(result.reviewer).not.toBeNull();
    expect(result.poolSize).toBe(3);
    expect(result.poolWarning).toBe(false);
  });

  it('자기 검토 필터 후 2명만 남으면 poolWarning true', async () => {
    const db = makeDb([
      { uid: 'contributor-1', name: 'Alice', role: 'reviewer' },
      { uid: 'reviewer-2', name: 'Bob', role: 'reviewer' },
      { uid: 'reviewer-3', name: 'Carol', role: 'admin' },
    ]);
    const result = await assignReviewer({
      docId: 'doc-1',
      contributorIds: ['contributor-1'],
      db,
    });
    expect(result.poolWarning).toBe(true);
    expect(result.poolSize).toBe(2);
  });

  it('풀이 비어있으면 reviewer null + poolWarning true', async () => {
    const db = makeDb([]);
    const result = await assignReviewer({
      docId: 'doc-1',
      contributorIds: [],
      db,
    });
    expect(result.reviewer).toBeNull();
    expect(result.poolWarning).toBe(true);
  });

  it('contributor가 전체 풀이면 reviewer null', async () => {
    const db = makeDb([
      { uid: 'reviewer-1', name: 'Alice', role: 'reviewer' },
      { uid: 'reviewer-2', name: 'Bob', role: 'reviewer' },
    ]);
    const result = await assignReviewer({
      docId: 'doc-1',
      contributorIds: ['reviewer-1', 'reviewer-2'],
      db,
    });
    expect(result.reviewer).toBeNull();
  });
});
