/**
 * autocomplete/route.ts GET 엔드포인트 단위 테스트
 * 아르고스(테스터) 작성
 *
 * 테스트 대상: GET /api/ai/autocomplete?q=...
 * 프레임워크: vitest
 *
 * 모킹 전략:
 *  - server-only          : 빈 모듈로 대체 (Next.js 서버 전용 가드 우회)
 *  - @/lib/firebase-admin : adminDb.collection('insurance_metadata').where().get() 모킹
 *  - @/lib/auth-middleware : verifyMember 모킹
 *  - @/lib/utils/hangul   : getChoseong 실제 구현 사용 (순수 함수, 모킹 불필요)
 *  - firebase-admin, firebase-admin/firestore, firebase-admin/auth : 초기화 방지
 *
 * vi.hoisted를 사용하여 vi.mock 팩토리 함수 내에서 외부 mock 변수를 안전하게
 * 참조할 수 있도록 처리한다.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { NextRequest, NextResponse } from 'next/server';

// ── vi.hoisted: vi.mock 팩토리보다 먼저 실행되는 변수 선언 ─────────────────

const mocks = vi.hoisted(() => {
    // insurance_metadata 컬렉션 스냅샷 모킹
    const mockMetadataGet = vi.fn();

    const mockWhere = vi.fn(() => ({
        get: mockMetadataGet,
    }));

    const mockCollection = vi.fn((name: string) => {
        if (name === 'insurance_metadata') {
            return { where: mockWhere };
        }
        return {};
    });

    const mockVerifyAuth = vi.fn();

    return {
        mockMetadataGet,
        mockWhere,
        mockCollection,
        mockVerifyAuth,
    };
});

// ── vi.mock 선언 ──────────────────────────────────────────────────────────

// server-only 가드 우회
vi.mock('server-only', () => ({}));

// firebase-admin/firestore 모킹
vi.mock('firebase-admin/firestore', () => ({
    FieldValue: {
        serverTimestamp: () => ({ _serverTimestamp: true }),
    },
}));

// firebase-admin 전체 모킹
vi.mock('firebase-admin', () => ({
    default: {
        apps: [],
        initializeApp: vi.fn(),
        credential: { cert: vi.fn() },
        app: vi.fn(),
    },
    apps: [],
    initializeApp: vi.fn(),
    credential: { cert: vi.fn() },
    app: vi.fn(),
}));

// firebase-admin/auth 모킹
vi.mock('firebase-admin/auth', () => ({
    getAuth: vi.fn(() => ({
        verifyIdToken: vi.fn(),
    })),
}));

// adminDb 모킹
vi.mock('@/lib/firebase-admin', () => ({
    adminDb: {
        collection: mocks.mockCollection,
    },
}));

// verifyMember 모킹
vi.mock('@/lib/auth-middleware', () => ({
    verifyMember: (...args: any[]) => mocks.mockVerifyAuth(...args),
}));

// ── import (모킹 이후) ─────────────────────────────────────────────────────

import { GET } from '../route';

// ── 헬퍼 함수 ─────────────────────────────────────────────────────────────

function makeRequest(searchParams: Record<string, string> = {}, headers: Record<string, string> = {}): NextRequest {
    const url = new URL('http://localhost/api/ai/autocomplete');
    for (const [key, value] of Object.entries(searchParams)) {
        url.searchParams.set(key, value);
    }
    return new NextRequest(url.toString(), {
        method: 'GET',
        headers: {
            ...headers,
        },
    });
}

function makeAuthHeader(token = 'valid-token'): Record<string, string> {
    return { Authorization: `Bearer ${token}` };
}

/** Firestore 스냅샷 형태의 mock 반환값 생성 */
function makeSnapshot(docs: Array<{ productId: string; productName: string; companyName: string; generationType?: string; isActive?: boolean }>) {
    return {
        docs: docs.map(d => ({
            data: () => ({
                productId: d.productId,
                productName: d.productName,
                companyName: d.companyName,
                generationType: d.generationType ?? 'standard',
                isActive: d.isActive ?? true,
            }),
        })),
    };
}

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

describe('GET /api/ai/autocomplete', () => {

    const { mockVerifyAuth, mockMetadataGet } = mocks;

    beforeEach(() => {
        vi.clearAllMocks();

        // 기본값: 인증 성공
        mockVerifyAuth.mockResolvedValue({ uid: 'test-user-001', email: 'test@example.com' });

        // 기본값: 빈 스냅샷
        mockMetadataGet.mockResolvedValue(makeSnapshot([]));
    });

    // ----------------------------------------------------------
    // 인증 실패 → 401
    // ----------------------------------------------------------
    describe('인증 실패 → 401', () => {
        it('Authorization 헤더 없이 요청 → verifyMember가 NextResponse(401)를 반환하면 → 401', async () => {
            mockVerifyAuth.mockResolvedValue(
                NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
            );

            const req = makeRequest({ q: '삼성' });
            const res = await GET(req);

            expect(res.status).toBe(401);
        });

        it('만료된 토큰 → verifyMember가 NextResponse(401)를 반환하면 → 401', async () => {
            mockVerifyAuth.mockResolvedValue(
                NextResponse.json({ error: 'Invalid or expired token' }, { status: 401 })
            );

            const req = makeRequest({ q: '삼성' }, makeAuthHeader('expired-token'));
            const res = await GET(req);

            expect(res.status).toBe(401);
        });

        it('guest 역할 사용자 → verifyMember가 NextResponse(403)를 반환하면 → 403', async () => {
            mockVerifyAuth.mockResolvedValue(
                NextResponse.json({ error: 'Forbidden: Member access required' }, { status: 403 })
            );

            const req = makeRequest({ q: '삼성' }, makeAuthHeader());
            const res = await GET(req);

            expect(res.status).toBe(403);
        });
    });

    // ----------------------------------------------------------
    // q 파라미터 누락 또는 빈 값 → 200 + 빈 배열
    // ----------------------------------------------------------
    describe('q 파라미터 누락 또는 빈 값 → 200 + 빈 배열', () => {
        it('q 파라미터 누락 → 200 + 빈 배열 반환', async () => {
            const req = makeRequest({}, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(Array.isArray(body)).toBe(true);
            expect(body).toHaveLength(0);
        });

        it('q가 빈 문자열(공백만) → 200 + 빈 배열 반환', async () => {
            const req = makeRequest({ q: '   ' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(Array.isArray(body)).toBe(true);
            expect(body).toHaveLength(0);
        });
    });

    // ----------------------------------------------------------
    // 정상 검색 → 200 + results 배열
    // ----------------------------------------------------------
    describe('정상 검색 → 200 + results 배열', () => {
        it('productName에 매칭되는 상품 → 200 + results 배열 반환', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([
                { productId: 'P001', productName: '삼성생명 종신보험', companyName: '삼성생명', generationType: 'standard' },
                { productId: 'P002', productName: '한화생명 건강보험', companyName: '한화생명', generationType: 'standard' },
            ]));

            const req = makeRequest({ q: '삼성' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(Array.isArray(body)).toBe(true);
            expect(body.length).toBeGreaterThan(0);
            expect(body[0]).toHaveProperty('productId');
            expect(body[0]).toHaveProperty('productName');
            expect(body[0]).toHaveProperty('companyName');
            expect(body[0]).toHaveProperty('generationType');
        });

        it('companyName에 매칭되는 상품 → 200 + results 배열 반환', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([
                { productId: 'P001', productName: '종신보험A', companyName: '현대해상', generationType: 'standard' },
                { productId: 'P002', productName: '건강보험B', companyName: '동부화재', generationType: 'standard' },
            ]));

            const req = makeRequest({ q: '현대' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(body.some((r: any) => r.companyName === '현대해상')).toBe(true);
        });

        it('응답 결과에 productId, productName, companyName, generationType 필드가 포함된다', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([
                { productId: 'P-TEST-001', productName: '테스트보험', companyName: '테스트생명', generationType: 'special' },
            ]));

            const req = makeRequest({ q: '테스트' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(body[0].productId).toBe('P-TEST-001');
            expect(body[0].productName).toBe('테스트보험');
            expect(body[0].companyName).toBe('테스트생명');
            expect(body[0].generationType).toBe('special');
        });

        it('isActive=true 필터링을 위해 where 조건이 올바르게 호출된다', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([]));

            const req = makeRequest({ q: '삼성' }, makeAuthHeader());
            await GET(req);

            expect(mocks.mockWhere).toHaveBeenCalledWith('isActive', '==', true);
        });
    });

    // ----------------------------------------------------------
    // 최대 10건 반환
    // ----------------------------------------------------------
    describe('최대 10건 반환', () => {
        it('매칭 결과가 10건 이상이면 최대 10건만 반환한다', async () => {
            const manyDocs = Array.from({ length: 20 }, (_, i) => ({
                productId: `P${String(i).padStart(3, '0')}`,
                productName: `보험상품${i}`,
                companyName: '대형보험사',
                generationType: 'standard',
            }));
            mockMetadataGet.mockResolvedValue(makeSnapshot(manyDocs));

            const req = makeRequest({ q: '보험' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(body.length).toBeLessThanOrEqual(10);
        });
    });

    // ----------------------------------------------------------
    // 매칭 없음 → 200 + 빈 배열
    // ----------------------------------------------------------
    describe('매칭 없음 → 200 + 빈 배열', () => {
        it('검색어와 매칭되는 상품이 없으면 200 + 빈 배열 반환', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([
                { productId: 'P001', productName: '삼성생명 종신보험', companyName: '삼성생명' },
            ]));

            const req = makeRequest({ q: '존재하지않는검색어XYZ' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(Array.isArray(body)).toBe(true);
            expect(body).toHaveLength(0);
        });

        it('DB에 상품이 전혀 없을 때 → 200 + 빈 배열 반환', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([]));

            const req = makeRequest({ q: '삼성' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            expect(body).toHaveLength(0);
        });
    });

    // ----------------------------------------------------------
    // 초성 검색
    // ----------------------------------------------------------
    describe('초성 검색', () => {
        it('초성("ㅅㅅ")으로 "삼성생명"을 검색할 수 있다', async () => {
            mockMetadataGet.mockResolvedValue(makeSnapshot([
                { productId: 'P001', productName: '종신보험', companyName: '삼성생명' },
                { productId: 'P002', productName: '건강보험', companyName: '한화생명' },
            ]));

            const req = makeRequest({ q: 'ㅅㅅ' }, makeAuthHeader());
            const res = await GET(req);
            const body = await res.json();

            expect(res.status).toBe(200);
            // 삼성생명의 초성: ㅅㅅㅅㅁ → ㅅㅅ 포함
            expect(body.some((r: any) => r.companyName === '삼성생명')).toBe(true);
        });
    });
});
