/**
 * v2.2.269 — /email (Gmail + Outlook) 순수 로직 테스트.
 * COM/네트워크는 통합 영역이라 제외 — 참조 파싱·프롬프트·트랜스크립트 컷만 고정.
 */
import { parseRef } from '../src/features/email/handlers';
import { buildEmailTranscript, buildEmailWikifyPrompt, buildReplyDraftPrompt } from '../src/features/email/emailPrompts';
import { normalizeSubject, isAutomated, groupThreads, renderBriefFallback, type MailLite } from '../src/features/email/emailBrief';

describe('parseRef — G3/O2 참조 파싱', () => {
    test('G/O 대소문자 + 공백 허용', () => {
        expect(parseRef('G3')).toEqual({ provider: 'gmail', idx: 2 });
        expect(parseRef('o2')).toEqual({ provider: 'outlook', idx: 1 });
        expect(parseRef('G 12')).toEqual({ provider: 'gmail', idx: 11 });
    });

    test('형식 오류 → null', () => {
        expect(parseRef('')).toBeNull();
        expect(parseRef('3')).toBeNull();
        expect(parseRef('X2')).toBeNull();
        expect(parseRef('G0')).toBeNull();
        expect(parseRef('gmail')).toBeNull();
    });
});

describe('buildEmailTranscript — 직렬화·컷', () => {
    const mail = (body: string, i = 1) => ({
        subject: '테스트', from: `sender${i}@x.com`, date: '2026-07-02 10:00', body,
    });

    test('메시지 구분자 + 발신자 포함', () => {
        const t = buildEmailTranscript([mail('안녕하세요', 1), mail('회신입니다', 2)]);
        expect(t).toContain('--- 메시지 1');
        expect(t).toContain('--- 메시지 2');
        expect(t).toContain('sender2@x.com');
    });

    test('메시지당 4,000자·전체 16,000자 컷', () => {
        const long = 'a'.repeat(10000);
        const t1 = buildEmailTranscript([mail(long)]);
        expect(t1.length).toBeLessThan(4200);
        const t5 = buildEmailTranscript([1, 2, 3, 4, 5].map(i => mail(long, i)));
        expect(t5.length).toBeLessThanOrEqual(16000);
    });
});

describe('프롬프트 — 환각 방지 규칙 포함', () => {
    test('wikify — 섹션 구조 + 원문 보존 규칙', () => {
        const p = buildEmailWikifyPrompt('계약 조건 변경', 'transcript');
        expect(p).toContain('# 계약 조건 변경');
        expect(p).toContain('한 줄 통찰');
        expect(p).toContain('외부 지식 추가 금지');
        expect(p).toContain('원문 그대로 보존');
    });

    test('reply — 지시사항 반영 + 사실 창작 금지', () => {
        const mail = { subject: 'S', from: 'a@b.c', date: '2026-07-02', body: '본문' };
        const withInst = buildReplyDraftPrompt(mail, '다음 주 미팅 제안으로 회신');
        expect(withInst).toContain('다음 주 미팅 제안으로 회신');
        expect(withInst).toContain('만들지 말 것');
        const noInst = buildReplyDraftPrompt(mail, '');
        expect(noInst).toContain('지시사항 없음');
    });
});

describe('normalizeSubject — RE:/FW: 겹겹이 제거', () => {
    test('여러 겹 접두 + 한글 접두', () => {
        expect(normalizeSubject('RE: FW: RE: 계약 검토')).toBe('계약 검토');
        expect(normalizeSubject('회신: 전달: 견적서')).toBe('견적서');
        expect(normalizeSubject('[QA] 결과 보고')).toBe('[qa] 결과 보고'); // 태그는 보존
    });
    test('빈 제목 → (제목 없음)', () => {
        expect(normalizeSubject('  ')).toBe('(제목 없음)');
    });
});

describe('isAutomated — 자동발신 분리', () => {
    test('노리플라이·서비스 발신 감지', () => {
        expect(isAutomated('no-reply@apple.com', 'WWDC 안내')).toBe(true);
        expect(isAutomated('App Store Connect', 'Version approved')).toBe(true);
        expect(isAutomated('AWS Marketplace', 'Product Update')).toBe(true);
    });
    test('사람 발신은 통과', () => {
        expect(isAutomated('김원일(칼리버스)', 'FW: 문의사항입니다')).toBe(false);
    });
});

describe('groupThreads — 주제 묶기 + 답변 여부', () => {
    const mail = (o: Partial<MailLite>): MailLite => ({
        subject: '계약 검토', from: '김철수', received: '2026-07-01 10:00', ...o,
    });

    test('RE:/FW: 가 같은 스레드로 묶이고 참조번호 보존', () => {
        const t = groupThreads([
            mail({ ref: 'O1', subject: 'RE: 계약 검토', received: '2026-07-02 09:00' }),
            mail({ ref: 'O2', subject: 'FW: 계약 검토', received: '2026-07-01 08:00' }),
        ], []);
        expect(t).toHaveLength(1);
        expect(t[0].inCount).toBe(2);
        expect(t[0].refs).toEqual(['O1', 'O2']);
    });

    test('마지막이 상대방 → 회신대기(in), 내 발신이 더 최근 → 공넘김(out)', () => {
        const inbox = [mail({ ref: 'O1', received: '2026-07-01 10:00' })];
        const waiting = groupThreads(inbox, [])[0];
        expect(waiting.lastDirection).toBe('in');
        const answered = groupThreads(inbox, [
            mail({ subject: 'RE: 계약 검토', from: '나', received: '2026-07-01 11:00', outgoing: true }),
        ])[0];
        expect(answered.lastDirection).toBe('out');
    });

    test('발신만 있는 주제는 제외, 정렬 = 회신대기 → 공넘김 → 자동발신', () => {
        const t = groupThreads([
            mail({ ref: 'O1', subject: '뉴스레터 7월호', from: 'no-reply@x.com', received: '2026-07-02 12:00' }),
            mail({ ref: 'O2', subject: '견적 문의', received: '2026-07-01 09:00' }),
            mail({ ref: 'O3', subject: '일정 확인', received: '2026-07-02 08:00' }),
        ], [
            mail({ subject: 'RE: 일정 확인', from: '나', received: '2026-07-02 09:00', outgoing: true }),
            mail({ subject: '단독 발신 건', from: '나', received: '2026-07-02 10:00', outgoing: true }),
        ]);
        expect(t).toHaveLength(3); // '단독 발신 건' 제외
        expect(t[0].subject).toBe('견적 문의');       // 회신대기
        expect(t[1].subject).toContain('일정 확인');   // 공넘김
        expect(t[2].automated).toBe(true);            // 자동발신 맨 뒤
    });

    test('renderBriefFallback — 섹션·마크다운 리스트 생성', () => {
        const t = groupThreads([mail({ ref: 'O1' })], []);
        const out = renderBriefFallback(t);
        expect(out).toContain('🔴 회신 대기');
        expect(out).toContain('- ');
        expect(out).toContain('[O1]');
    });
});

describe('groupThreads — 타임라인 (경과 재료)', () => {
    const m = (o: Partial<MailLite>): MailLite => ({
        subject: '계약 검토', from: '김철수', received: '2026-07-01 10:00', ...o,
    });

    test('수신+발신 시간순 흐름, 발신자는 "나"로', () => {
        const t = groupThreads([
            m({ ref: 'O1', snippet: '검토 부탁드립니다', received: '2026-06-30 09:00' }),
            m({ ref: 'O2', snippet: '재질문 드립니다', received: '2026-07-02 10:00' }),
        ], [
            m({ subject: 'RE: 계약 검토', from: '나', received: '2026-07-01 11:00', outgoing: true }),
        ])[0];
        expect(t.timeline.map(e => e.from)).toEqual(['김철수', '나', '김철수']);
        expect(t.timeline[0].snippet).toContain('검토 부탁');
        expect(t.timeline[1].outgoing).toBe(true);
    });

    test('타임라인은 최근 6개로 캡', () => {
        const inbox = Array.from({ length: 9 }, (_, i) =>
            m({ ref: `O${i + 1}`, received: `2026-07-0${(i % 3) + 1} 0${i}:00` }));
        const t = groupThreads(inbox, [])[0];
        expect(t.timeline.length).toBe(6);
    });
});

describe('buildInboxBriefPrompt — PM 업무 허브 형식 (v2.2.288)', () => {
    test('정합성·결정/액션 분리·P등급·장기 구분·완료 예상 포함', () => {
        const { buildInboxBriefPrompt } = require('../src/features/email/emailPrompts');
        const p = buildInboxBriefPrompt('[1] 상태=회신대기 | 주제="테스트"');
        expect(p).toContain('상태: 🟣 결정 필요');
        expect(p).toContain('우선순위: 🔥 오늘');
        expect(p).toContain('1:1 동일');                 // Today Focus 숫자 정합성
        expect(p).toContain('Yes/No 또는 방향');          // 결정 ≠ 액션
        expect(p).toContain('P1 🚨 고객');                // P등급 영향
        expect(p).toContain('상위 5건');                  // 확인만 컷
        expect(p).toContain('상대 답변 대기');             // 장기 대기 성격 구분
        expect(p).toContain('내 후속 지연');
        expect(p).toContain('오늘·3');                    // 프로젝트 건수 병기
        expect(p).toContain('최근 업데이트');              // Timeline 1건 대체
        expect(p).toContain('글자 그대로 복사');           // 인명 오타 방지
        expect(p).toContain('오늘 완료 예상');             // 결과물 중심 요약
        expect(p).toContain('메일 명시 기한은 그대로');     // 기한 환각 방지 유지
        expect(p).toContain('737시간 같은 표기 금지');
        expect(p).toContain('오늘 안 하면');
    });
});

describe('polishBriefLabels — 라벨 오타 후처리 (v2.2.288)', () => {
    const { polishBriefLabels } = require('../src/features/email/emailPrompts');

    test('알려진 오타 패턴 교정', () => {
        const out = polishBriefLabels('- 내 액점: 회신\n- 권 량: 오늘\n🔴 회신_필요\n## ⚠ 이번 처리 (3건)');
        expect(out).toContain('내 액션:');
        expect(out).toContain('권장기한:');
        expect(out).toContain('회신 필요');
        expect(out).toContain('⚠ 이번주 처리');
        expect(out).not.toContain('액점');
    });

    test('정상 텍스트는 무변경', () => {
        const ok = '- 내 액션: 회신\n- 권장기한: 오늘\n🔴 회신 필요\n## ⚠ 이번주 처리 (3건)\n본문 내용은 그대로';
        expect(polishBriefLabels(ok)).toBe(ok);
    });
});

describe('buildEmailCanonicalWikifyPrompt — /wikify 정본 포맷 사용 (v2.2.277)', () => {
    test('frontmatter + 정본 섹션 + 이메일 특수 규칙 포함', () => {
        const { buildEmailCanonicalWikifyPrompt } = require('../src/features/datacollect/prompts/wikifyPrompt');
        const p = buildEmailCanonicalWikifyPrompt('QA 결과 보고', 'transcript-body', { participants: ['최성연'], dateRange: '2026-07-01 ~ 2026-07-02' }, null);
        // 정본 골격
        expect(p).toContain('id: ');
        expect(p).toContain('raw_sources');
        expect(p).toContain('## 🎯 한 줄 통찰');
        expect(p).toContain('## 🔗 지식 그래프');
        expect(p).toContain('## 📚 출처');
        // 이메일 특수 규칙
        expect(p).toContain('누가·언제·무엇을');
        expect(p).toContain('| 누가 | 무엇을 | 기한 |');
        expect(p).toContain('최성연');
        expect(p).toContain('transcript-body');
        // 환각 방지
        expect(p).toContain('지어내지');
    });
});

describe('injectTimeHeader — 기준 시점 헤더 (v2.2.282)', () => {
    const { injectTimeHeader } = require('../src/features/email/emailPrompts');

    test('frontmatter + H1 아래에 시점 헤더 삽입', () => {
        const doc = '---\nid: x\n---\n# [[제목]]\n\n## 🎯 한 줄 통찰\n내용';
        const out = injectTimeHeader(doc, '2026-06-28 10:00 ~ 2026-07-02 20:02', '2026-07-03');
        const lines = out.split('\n');
        const h1 = lines.findIndex((l: string) => l.startsWith('# '));
        expect(lines[h1 + 2]).toContain('🕒 **기준 시점**');
        expect(out).toContain('2026-07-02 20:02');
        expect(out).toContain('문서 생성 2026-07-03');
        expect(out).toContain('변했을 수 있음');
    });

    test('H1 없으면 frontmatter 뒤에, 기간 없으면 원문 그대로', () => {
        const noH1 = injectTimeHeader('본문뿐', '2026-07-01 ~ 2026-07-02', '2026-07-03');
        expect(noH1.split('\n')[1]).toContain('기준 시점');
        expect(injectTimeHeader('doc', '', '2026-07-03')).toBe('doc');
    });

    test('프롬프트에 날짜 병기 규칙 포함 (시점 없는 현재형 금지)', () => {
        const { buildEmailCanonicalWikifyPrompt } = require('../src/features/datacollect/prompts/wikifyPrompt');
        const p = buildEmailCanonicalWikifyPrompt('T', 'B', {}, null);
        expect(p).toContain('날짜 없는 현재형 서술 금지');
        expect(p).toContain('기준 진행 중');
    });
});

describe('isAutomated — v2.2.283 노이즈 필터 확장 (텔레그램 미회신 오탐)', () => {
    test('Confluence 다이제스트·AWS·광고·[Notification] 잡힘', () => {
        expect(isAutomated('Confluence', '한예성 님, 일일 다이제스트를 놓치지 마세요')).toBe(true);
        expect(isAutomated('Amazon Web Services', 'Upcoming routine retirement')).toBe(true);
        expect(isAutomated('Apple', '(광고) WWDC26에서 발표된 최신 소식')).toBe(true);
        expect(isAutomated('김성환(칼리버스)', 'FW: [Notification] Upcoming routine retirement')).toBe(true);
        expect(isAutomated('플래티어 IDT', '[플래티어IDT 뉴스레터] 소식')).toBe(true);
    });
    test('사람 발신 업무 메일은 여전히 통과', () => {
        expect(isAutomated('김성환(칼리버스-칼리버스)', 'RE: 메타버스 기술 취약점 진단 견적서 및 계약서 송부의 건')).toBe(false);
        expect(isAutomated('오실묵(롯데이노베이트)', 'AWS 미사용 서비스 종료 검토의 건')).toBe(false);
    });
});

describe('groupThreads — CC 수신 판별 (v2.2.290)', () => {
    const m = (o: Partial<MailLite>): MailLite => ({
        subject: '참조 공유', from: '김철수', received: '2026-07-03 10:00', ...o,
    });

    test('수신 전부 CC → ccOnly=true (확인만 분류 대상)', () => {
        const t = groupThreads([m({ ref: 'O1', ccOnly: true }), m({ ref: 'O2', ccOnly: true, received: '2026-07-02 09:00' })], []);
        expect(t[0].ccOnly).toBe(true);
    });

    test('하나라도 To 수신이면 ccOnly=false (회신 대상 유지)', () => {
        const t = groupThreads([m({ ref: 'O1', ccOnly: true }), m({ ref: 'O2', ccOnly: false, received: '2026-07-02 09:00' })], []);
        expect(t[0].ccOnly).toBe(false);
    });

    test('ccOnly 미수집(undefined)이면 false — 오탐 방지', () => {
        const t = groupThreads([m({ ref: 'O1' })], []);
        expect(t[0].ccOnly).toBe(false);
    });
});
