/**
 * YouTube + Whisper STT 파이프라인 유닛 테스트
 *
 * task-207.1 Phase 1~2 구현 검증:
 * - chunkTranscript 분할 로직 (crawlYoutubeChannels.ts 내부 로직 추출 검증)
 * - whisperTranscribe 함수 시그니처 및 import 검증
 * - buildTranscriptContent 마크다운 포맷 검증
 * - 하이브리드 자막 분기 로직 검증
 *
 * task-1094.1 업데이트:
 * - buildMarkdownContent 메타데이터 강화 테스트 (transcriptionSource, captionLanguage, captionType)
 * - TranscriptResult 반환값 구조 검증
 * - VideoProcessingLog 처리 로그 구조 검증
 * - buildProcessingLogMarkdown 처리 로그 마크다운 검증
 */

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

// ── chunkTranscript 로직 인라인 테스트 ────────────────────────────────────
// crawlYoutubeChannels.ts의 chunkTranscript 동일 로직 (순수 함수 검증)
function chunkTranscript(text: string, chunkSize = 500, overlap = 50): string[] {
    const chunks: string[] = [];
    let start = 0;
    while (start < text.length) {
        const end = Math.min(start + chunkSize, text.length);
        chunks.push(text.slice(start, end));
        if (end === text.length) break;
        start += chunkSize - overlap;
    }
    return chunks;
}

describe('chunkTranscript 분할 로직', () => {
    test('빈 문자열 → 빈 배열', () => {
        const result = chunkTranscript('');
        expect(result).toHaveLength(0);
    });

    test('청크 크기보다 짧은 텍스트 → 청크 1개', () => {
        const text = '짧은 텍스트입니다.';
        const result = chunkTranscript(text, 500, 50);
        expect(result).toHaveLength(1);
        expect(result[0]).toBe(text);
    });

    test('정확히 chunkSize 길이의 텍스트 → 청크 1개', () => {
        const text = 'a'.repeat(500);
        const result = chunkTranscript(text, 500, 50);
        expect(result).toHaveLength(1);
        expect(result[0]).toHaveLength(500);
    });

    test('chunkSize + 1 길이 → 청크 2개', () => {
        const text = 'a'.repeat(501);
        const result = chunkTranscript(text, 500, 50);
        // 두 번째 청크는 start=450에서 시작 → 501-450=51자
        expect(result).toHaveLength(2);
        expect(result[0]).toHaveLength(500);
        expect(result[1]).toHaveLength(51);
    });

    test('오버랩: 두 번째 청크는 첫 번째 청크의 마지막 50자를 포함', () => {
        // text = 'a'*490 + 'b'*110 (총 600자)
        // chunk1: 0..500 = 'a'*490 + 'b'*10
        // chunk2: start=450, text[450..600) = 'a'*40 + 'b'*110
        const text = 'a'.repeat(490) + 'b'.repeat(110);
        const result = chunkTranscript(text, 500, 50);
        expect(result).toHaveLength(2);
        // 두 번째 청크 시작(450): 여전히 'a' 구간 (490까지 'a')
        expect(result[1].startsWith('a')).toBe(true); // 오버랩 확인
        expect(result[1]).toContain('b'); // 그리고 'b'도 포함
    });

    test('청크 합산 길이 ≥ 원본 텍스트 길이 (오버랩으로 인해 더 클 수 있음)', () => {
        const text = 'x'.repeat(1200);
        const result = chunkTranscript(text, 500, 50);
        const totalChunkLength = result.reduce((sum, c) => sum + c.length, 0);
        expect(totalChunkLength).toBeGreaterThanOrEqual(text.length);
    });

    test('최대 청크 수 확인 (1000자 텍스트, 500자 청크, 50 오버랩)', () => {
        const text = 'k'.repeat(1000);
        const result = chunkTranscript(text, 500, 50);
        // 청크1: 0~499, 청크2: 450~949, 청크3: 900~999
        expect(result).toHaveLength(3);
    });
});

// ── whisperTranscribe 모듈 import 검증 ────────────────────────────────────
describe('whisperTranscribe 모듈', () => {
    let whisperModule: typeof import('../whisperStt');

    beforeEach(async () => {
        whisperModule = await import('../whisperStt');
    });

    test('whisperTranscribe 함수가 export 되어야 함', () => {
        expect(typeof whisperModule.whisperTranscribe).toBe('function');
    });

    test('GPU Whisper 서비스 미실행 시 null 반환', { timeout: 10000 }, async () => {
        // 모듈 캐시 초기화 후 존재하지 않는 포트로 재import → ECONNREFUSED → null
        vi.resetModules();
        process.env.LOCAL_WHISPER_URL = 'http://localhost:19999';
        const freshModule = await import('../whisperStt');

        const result = await freshModule.whisperTranscribe('dQw4w9WgXcQ');
        expect(result).toBeNull();

        delete process.env.LOCAL_WHISPER_URL;
    });
});

// ── 하이브리드 자막 분기 로직 검증 ────────────────────────────────────────
describe('하이브리드 자막 분기 로직', () => {
    // transcriptionSource 타입 열거값 검증
    type TranscriptionSource = 'youtube_caption' | 'whisper_stt' | 'title_description';

    function determineTranscriptionSource(
        hasYoutubeCaption: boolean,
        hasWhisperResult: boolean
    ): TranscriptionSource {
        if (hasYoutubeCaption) return 'youtube_caption';
        if (hasWhisperResult) return 'whisper_stt';
        return 'title_description';
    }

    test('유튜브 자막 있음 → youtube_caption', () => {
        expect(determineTranscriptionSource(true, false)).toBe('youtube_caption');
    });

    test('유튜브 자막 없음 + Whisper 성공 → whisper_stt', () => {
        expect(determineTranscriptionSource(false, true)).toBe('whisper_stt');
    });

    test('유튜브 자막 없음 + Whisper 실패 → title_description', () => {
        expect(determineTranscriptionSource(false, false)).toBe('title_description');
    });
});

// ── buildTranscriptContent 마크다운 포맷 검증 ─────────────────────────────
describe('전문 마크다운 포맷 검증', () => {
    // crawlYoutubeChannels.ts의 buildTranscriptContent 동일 로직
    function buildTranscriptContent(
        transcript: string,
        videoTitle: string,
        channelName: string,
        videoId: string,
        publishedAt: string,
        duration: string,
        transcriptionSource: string
    ): string {
        const sourceLabel = transcriptionSource === 'whisper_stt' ? 'Whisper STT 전사' : '유튜브 자막 추출';
        return `# ${videoTitle} — 전문 (Full Transcript)

- **채널**: ${channelName}
- **업로드일**: ${publishedAt.slice(0, 10)}
- **영상 길이**: ${duration}
- **원본 URL**: https://www.youtube.com/watch?v=${videoId}
- **전사 방식**: ${sourceLabel}
- **처리일**: ${new Date().toISOString().slice(0, 19)}+09:00

---

${transcript}

---
*자동 전사 | ${sourceLabel} | 원본 음성과 다를 수 있습니다*
`;
    }

    test('whisper_stt 소스: "Whisper STT 전사" 레이블 사용', () => {
        const content = buildTranscriptContent(
            '전사된 텍스트',
            '보험 영상',
            '보험명의정닥터',
            'abc123',
            '2026-03-04T00:00:00Z',
            '30분',
            'whisper_stt'
        );
        expect(content).toContain('Whisper STT 전사');
        expect(content).not.toContain('유튜브 자막 추출');
    });

    test('youtube_caption 소스: "유튜브 자막 추출" 레이블 사용', () => {
        const content = buildTranscriptContent(
            '자막 텍스트',
            '보험 영상',
            '인스킹',
            'xyz789',
            '2026-03-04T00:00:00Z',
            '20분',
            'youtube_caption'
        );
        expect(content).toContain('유튜브 자막 추출');
    });

    test('마크다운에 비디오 URL 포함', () => {
        const content = buildTranscriptContent(
            '텍스트',
            '테스트 영상',
            '채널명',
            'VIDEO123',
            '2026-03-04T00:00:00Z',
            '10분',
            'youtube_caption'
        );
        expect(content).toContain('https://www.youtube.com/watch?v=VIDEO123');
    });

    test('마크다운에 전문 텍스트 포함', () => {
        const transcript = '보험 약관 설명 전문 텍스트입니다.';
        const content = buildTranscriptContent(
            transcript,
            '테스트',
            '채널',
            'vid1',
            '2026-03-04T00:00:00Z',
            '5분',
            'youtube_caption'
        );
        expect(content).toContain(transcript);
    });
});

// ── buildMarkdownContent 메타데이터 강화 테스트 ───────────────────────────
describe('buildMarkdownContent 메타데이터 강화', () => {
    // buildMarkdownContent의 새 매개변수를 반영한 인라인 함수 정의
    function buildMarkdownContent(
        summary: string, videoTitle: string, channelName: string,
        videoId: string, publishedAt: string, duration: string,
        hasTranscript: boolean,
        transcriptionSource?: string, transcriptLength?: number,
        captionLanguage?: string, captionType?: string
    ): string {
        let transcriptionLine: string;
        if (transcriptionSource === 'youtube_caption') {
            const langLabel = captionLanguage || 'ko';
            const typeLabel = captionType || '자동생성';
            transcriptionLine = `✅ YouTube 자막 (${langLabel}, ${typeLabel})`;
        } else if (transcriptionSource === 'whisper_stt') {
            const lenLabel = transcriptLength ? `${transcriptLength.toLocaleString()}자` : '';
            transcriptionLine = `🎤 Whisper STT (GPU${lenLabel ? `, ${lenLabel}` : ''})`;
        } else {
            transcriptionLine = '⚠️ 제목+설명만';
        }
        const transcriptLengthLine = transcriptLength
            ? `- **전사 텍스트 길이**: ${transcriptLength.toLocaleString()}자`
            : '';
        return `# ${videoTitle}\n- **전사 방식**: ${transcriptionLine}\n${transcriptLengthLine}`;
    }

    test('YouTube 자막 소스: 전사 방식에 언어/타입 표시', () => {
        const content = buildMarkdownContent(
            '요약', '테스트', '채널', 'vid1', '2026-03-26T00:00:00Z', '10분', true,
            'youtube_caption', 5000, 'ko', 'auto-generated'
        );
        expect(content).toContain('✅ YouTube 자막 (ko, auto-generated)');
        expect(content).toContain('5,000자');
    });

    test('Whisper STT 소스: GPU 및 글자수 표시', () => {
        const content = buildMarkdownContent(
            '요약', '테스트', '채널', 'vid1', '2026-03-26T00:00:00Z', '10분', true,
            'whisper_stt', 12450
        );
        expect(content).toContain('🎤 Whisper STT (GPU, 12,450자)');
    });

    test('제목+설명 소스: 경고 표시', () => {
        const content = buildMarkdownContent(
            '요약', '테스트', '채널', 'vid1', '2026-03-26T00:00:00Z', '10분', false,
            'title_description'
        );
        expect(content).toContain('⚠️ 제목+설명만');
    });

    test('transcriptionSource 미지정 시 기본 동작 (하위호환)', () => {
        const content = buildMarkdownContent(
            '요약', '테스트', '채널', 'vid1', '2026-03-26T00:00:00Z', '10분', false
        );
        expect(content).toContain('⚠️ 제목+설명만');
    });
});

// ── TranscriptResult 반환값 검증 테스트 ──────────────────────────────────
describe('TranscriptResult 구조 검증', () => {
    interface TranscriptResult {
        text: string;
        captionLanguage: string;
        captionType: 'manual' | 'auto-generated';
    }

    test('수동 자막 결과 구조', () => {
        const result: TranscriptResult = {
            text: '자막 텍스트',
            captionLanguage: 'ko',
            captionType: 'manual',
        };
        expect(result.captionType).toBe('manual');
        expect(result.captionLanguage).toBe('ko');
    });

    test('자동 생성 자막 결과 구조', () => {
        const result: TranscriptResult = {
            text: '자동 생성 자막',
            captionLanguage: 'ko',
            captionType: 'auto-generated',
        };
        expect(result.captionType).toBe('auto-generated');
    });
});

// ── VideoProcessingLog 처리 로그 테스트 ──────────────────────────────────
describe('VideoProcessingLog 처리 로그', () => {
    interface VideoProcessingLog {
        index: number;
        videoId: string;
        title: string;
        processingMethod: 'youtube_caption' | 'whisper_stt' | 'title_description';
        transcriptLength: number;
        summaryLength: number;
        status: 'completed' | 'quality_warning' | 'failed';
        processingTimeMs: number;
        captionLanguage?: string;
        captionType?: string;
    }

    test('YouTube 자막 처리 로그 구조', () => {
        const log: VideoProcessingLog = {
            index: 1,
            videoId: 'abc123def45',
            title: '보험 영상',
            processingMethod: 'youtube_caption',
            transcriptLength: 8230,
            summaryLength: 3500,
            status: 'completed',
            processingTimeMs: 45000,
            captionLanguage: 'ko',
            captionType: 'auto-generated',
        };
        expect(log.processingMethod).toBe('youtube_caption');
        expect(log.status).toBe('completed');
    });

    test('Whisper STT 처리 로그 구조', () => {
        const log: VideoProcessingLog = {
            index: 2,
            videoId: 'xyz789abc12',
            title: '하나손보 입원수술플랜',
            processingMethod: 'whisper_stt',
            transcriptLength: 12450,
            summaryLength: 4200,
            status: 'completed',
            processingTimeMs: 150000,
        };
        expect(log.processingMethod).toBe('whisper_stt');
        expect(log.transcriptLength).toBe(12450);
    });

    test('제목+설명 처리 로그 → quality_warning', () => {
        const log: VideoProcessingLog = {
            index: 3,
            videoId: 'qwe456rty78',
            title: '비급여 자기부담',
            processingMethod: 'title_description',
            transcriptLength: 150,
            summaryLength: 2100,
            status: 'quality_warning',
            processingTimeMs: 30000,
        };
        expect(log.status).toBe('quality_warning');
        expect(log.processingMethod).toBe('title_description');
    });
});

// ── buildProcessingLogMarkdown 처리 로그 마크다운 테스트 ──────────────────
describe('buildProcessingLogMarkdown 처리 로그 마크다운', () => {
    interface VideoProcessingLog {
        index: number;
        videoId: string;
        title: string;
        processingMethod: 'youtube_caption' | 'whisper_stt' | 'title_description';
        transcriptLength: number;
        summaryLength: number;
        status: 'completed' | 'quality_warning' | 'failed';
        processingTimeMs: number;
        captionLanguage?: string;
        captionType?: string;
    }

    // buildProcessingLogMarkdown 인라인 함수 정의
    // (crawlYoutubeChannels.ts의 동일 로직)
    function buildProcessingLogMarkdown(
        logs: VideoProcessingLog[],
        channelNames: string[],
        startTime: Date
    ): string {
        // 간소화된 버전 (핵심 구조만 검증)
        const rows = logs.map(log => `| ${log.index} | ${log.videoId} |`).join('\n');
        const completedCount = logs.filter(l => l.status === 'completed').length;
        return `# 유튜브 크롤링 처리 로그\n- **처리 채널**: ${channelNames.join(', ')}\n| 총 처리 영상 | ${logs.length}개 |\n| ✅ 완료 | ${completedCount}개 |\n${rows}`;
    }

    test('로그 마크다운 헤더에 채널명 포함', () => {
        const logs: VideoProcessingLog[] = [{
            index: 1, videoId: 'abc123def45', title: '테스트', processingMethod: 'youtube_caption',
            transcriptLength: 100, summaryLength: 50, status: 'completed', processingTimeMs: 1000,
        }];
        const markdown = buildProcessingLogMarkdown(logs, ['보험명의정닥터', 'ins-king'], new Date());
        expect(markdown).toContain('보험명의정닥터');
        expect(markdown).toContain('ins-king');
    });

    test('영상 수와 완료 수 정확히 반영', () => {
        const logs: VideoProcessingLog[] = [
            { index: 1, videoId: 'v1', title: 't1', processingMethod: 'youtube_caption', transcriptLength: 100, summaryLength: 50, status: 'completed', processingTimeMs: 1000 },
            { index: 2, videoId: 'v2', title: 't2', processingMethod: 'title_description', transcriptLength: 10, summaryLength: 20, status: 'quality_warning', processingTimeMs: 2000 },
        ];
        const markdown = buildProcessingLogMarkdown(logs, ['ch1'], new Date());
        expect(markdown).toContain('2개');
        expect(markdown).toContain('1개');
    });
});
