import * as vscode from 'vscode';
import { AIService } from '../../core/services';
import { logError, logInfo } from '../../utils';
import { TELEGRAM_TOKEN_SECRET_KEY } from '../../extension/telegramCommands';
import { TelegramHttpClient } from '../../integrations/telegram/telegramClient';
import type { DiscoveredCandidate } from './stockDiscovery';

/**
 * Discover 결과의 *매력도 Top 5* 정리 + 채팅/텔레그램 발송.
 *
 * v2.2.266 재설계: Top 5 **선정은 코드**가 한다 — candidates 는 이미 멀티팩터
 * 합성점수(compositeScore) 내림차순으로 정렬돼 오므로 상위 5개가 곧 Top 5.
 * LLM 의 역할은 각 종목의 *한 줄 매력 포인트(pitch) 서술*뿐이다 (v2.2.211 judge
 * 철학과 동일: "수치 비교·순위는 코드로, LLM 은 서술만"). 따라서 LLM 이 실패해도
 * Top 5 는 항상 나온다 — pitch 만 결정론 폴백(점수 분해 문자열)으로 대체.
 *
 * 호출 흐름 (slashStocks 의 cmdDiscover 끝에 자동 chain):
 *   1) 상위 5개 확정 (코드)                — candidates.slice(0, 5)
 *   2) buildPitchPrompt → AIService.chat  — 5개 종목의 pitch 서술 요청
 *   3) parsePitches (관대한 번호 매칭)     — 실패 항목은 결정론 폴백 pitch
 *   4) renderForChat / renderForTelegram  — 점수 분해(Q/V/M/S) 포함 표시
 *   5) sendToTelegram(...)                — 텔레그램 발송 (실패 silent)
 */

export interface TopFiveItem {
    rank: number;
    name: string;
    symbol: string;
    /** LLM 이 한 줄로 정리한 매력 포인트. */
    pitch: string;
    /** 원본 후보 (선택적으로 ROE/영업이익률 등 인용용). */
    candidate?: DiscoveredCandidate;
}

export interface TopFiveResult {
    items: TopFiveItem[];
    /** LLM 의 종합 코멘트 (1-2 문장). 없을 수 있음. */
    summary?: string;
    /** LLM 응답 원문 (디버그 / fallback 출력용). */
    raw: string;
}

const SYSTEM_PROMPT = [
    '당신은 한국·미국 주식 발굴 결과를 서술하는 애널리스트 보조 도구다.',
    '아래 Top 5 는 코드가 멀티팩터 합성점수(Quality/Value/Momentum/Stability 백분위 랭크,',
    '투자성향 가중)로 이미 확정했다 — **순서를 바꾸거나 종목을 교체하지 말 것**.',
    '당신의 임무는 각 종목의 *한 줄 매력 포인트(pitch)* 서술뿐이다.',
    '',
    '**pitch 작성 규칙:**',
    '  - 제공된 수치·점수만 인용 (새 수치·사실 창작 금지)',
    '  - 30자 이내, 그 종목이 왜 상위인지 축(Q/V/M/S) 강점 중심으로',
    '  - value trap 가드 표시가 있는 종목은 "저평가이나 품질 확인 필요" 뉘앙스 반영',
    '',
    '**출력 형식 (반드시 이대로, 정확히 5줄 + 종합 1줄):**',
    '1. <종목명> (<심볼>) — <한 줄 매력 포인트>',
    '2. ...',
    '3. ...',
    '4. ...',
    '5. ...',
    '종합: <1-2 문장 — 이번 발굴 batch 의 공통 특징 또는 주의점>',
    '',
    '*다른 텍스트 절대 추가 금지.*',
].join('\n');

function buildPitchPrompt(top: DiscoveredCandidate[]): string {
    const lines: string[] = [
        `코드가 확정한 Top ${top.length}. 각 종목의 pitch 를 서술하라 (순서 변경 금지).`,
        '',
    ];
    for (let i = 0; i < top.length; i++) {
        const c = top[i];
        const f = c.fundamentals;
        const sc = c.score;
        lines.push(
            `${i + 1}. ${c.name} (${c.symbol}) [${c.market} · ${c.marketCapEok.toLocaleString()}억 · ${c.asStock.투자성향 ?? '-'}]`,
            `    종합 ${sc.total} (${sc.breakdown})${sc.valueTrapGuarded ? ' ⚠️value trap 가드 발동' : ''}${sc.fScore ? ` · miniF ${sc.fScore}` : ''}`,
            `    ROE ${f.roe?.toFixed(1) ?? '-'}% · 영업이익률 ${f.operatingMargin?.toFixed(1) ?? '-'}% · 유보율 ${f.retentionRatio?.toLocaleString() ?? '-'}% · 부채비율 ${f.debtRatio?.toFixed(1) ?? '-'}% · PER ${f.per?.toFixed(1) ?? '-'} · PBR ${f.pbr?.toFixed(1) ?? '-'}`,
            `    통과 (${c.passedKeywords.length}): ${c.passedKeywords.join(', ')}`,
            '',
        );
    }
    return lines.join('\n');
}

/** LLM 실패/누락 시 pitch 결정론 폴백 — 점수 분해 + 최강 축 언급. */
function fallbackPitch(c: DiscoveredCandidate): string {
    const sc = c.score;
    const axes: [string, number | undefined][] = [
        ['퀄리티', sc.quality], ['밸류', sc.value], ['모멘텀', sc.momentum], ['안정성', sc.stability],
    ];
    const best = axes.filter((a): a is [string, number] => a[1] !== undefined)
        .sort((a, b) => b[1] - a[1])[0];
    const guard = sc.valueTrapGuarded ? ' (품질 확인 필요)' : '';
    return best
        ? `종합 ${sc.total} — ${best[0]} 강점 (${sc.breakdown})${guard}`
        : `종합 ${sc.total} (${sc.breakdown})${guard}`;
}

/**
 * LLM 응답에서 순번별 pitch 만 추출 (관대한 번호 매칭). 순위·종목은 코드가 확정했으므로
 * LLM 텍스트의 종목명/심볼은 신뢰하지 않는다 — rank 번호로만 pitch 를 매핑.
 *   - "1. <이름> (<심볼>) — <문구>" / "1) ..." / "1: ..." 모두 받음
 */
export function parsePitches(raw: string, count: number): Map<number, string> {
    const pitches = new Map<number, string>();
    const itemRe = /^\s*([1-9])[\.\)\:]\s+.*?[—\-:]\s*(.+)$/;
    for (const line of raw.split('\n')) {
        const m = line.match(itemRe);
        if (!m) continue;
        const rank = parseInt(m[1], 10);
        if (rank < 1 || rank > count || pitches.has(rank)) continue;
        const pitch = m[2].trim();
        if (pitch) pitches.set(rank, pitch);
    }
    return pitches;
}

export async function analyzeTopCandidates(
    candidates: DiscoveredCandidate[],
    onProgress?: (msg: string) => void,
): Promise<TopFiveResult> {
    if (candidates.length === 0) {
        return { items: [], raw: '' };
    }
    // (1) Top 5 확정 — 코드. candidates 는 이미 합성점수 내림차순.
    const top = candidates.slice(0, 5);
    let raw = '';
    let summary: string | undefined;
    const pitches = new Map<number, string>();

    // (2) LLM 은 pitch 서술만 — 실패해도 Top 5 는 그대로 (결정론 폴백 pitch).
    onProgress?.('🤖 LLM pitch 서술 중 — Top ' + top.length + ' (순위는 합성점수로 확정됨)...');
    const ai = new AIService();
    try {
        const result = await ai.chat({
            system: SYSTEM_PROMPT,
            user: buildPitchPrompt(top),
            timeoutMs: 90_000,
        });
        if (!result.empty && result.content.trim()) {
            raw = result.content;
            for (const [rank, p] of parsePitches(raw, top.length)) pitches.set(rank, p);
            const summaryMatch = raw.match(/(?:종합|총평)\s*[:：]\s*(.+?)(?:\n\n|$)/s);
            summary = summaryMatch ? summaryMatch[1].replace(/\s+/g, ' ').trim() : undefined;
        }
    } catch (e: any) {
        logError('Discovery analyzer: LLM pitch 호출 실패 — 결정론 폴백 사용.', { error: e?.message ?? String(e) });
    }

    // (3) 항목 조립 — 순위·종목 = 코드, pitch = LLM 또는 폴백.
    const items: TopFiveItem[] = top.map((c, i) => ({
        rank: i + 1,
        name: c.name,
        symbol: c.symbol,
        pitch: pitches.get(i + 1) || fallbackPitch(c),
        candidate: c,
    }));
    logInfo('Discovery analyzer: Top 5 확정 (합성점수 순).', {
        llmPitches: pitches.size, fallbacks: items.length - pitches.size,
    });
    return { items, summary, raw };
}

/** 채팅 webview 용 — markdown 친화적 멀티라인. */
export function renderTopFiveForChat(result: TopFiveResult): string {
    if (result.items.length === 0) {
        return result.raw
            ? `\n🤖 **LLM 분석 결과** (형식 파싱 실패 — 원문 표시)\n\n${result.raw}\n`
            : '\n⚠️ LLM 분석 실패 (빈 응답 또는 timeout).\n';
    }
    const lines: string[] = ['\n🎯 **Astra 매력도 Top 5** (멀티팩터 합성점수 순)\n'];
    for (const it of result.items) {
        lines.push(`${it.rank}. **${it.name}** (${it.symbol}) — ${it.pitch}`);
        if (it.candidate) {
            const f = it.candidate.fundamentals;
            const sc = it.candidate.score;
            lines.push(`   점수: 종합 **${sc.total}** (${sc.breakdown})${sc.valueTrapGuarded ? ' · ⚠️ value trap 가드' : ''}${sc.fScore ? ` · miniF ${sc.fScore}` : ''}`);
            lines.push(`   근거: ROE ${f.roe?.toFixed(1) ?? '-'}%, 영업이익률 ${f.operatingMargin?.toFixed(1) ?? '-'}%, 유보율 ${f.retentionRatio?.toLocaleString() ?? '-'}%, 통과 ${it.candidate.passedKeywords.length}개 (${it.candidate.passedKeywords.join(', ')})`);
        }
    }
    if (result.summary) {
        lines.push('', `💬 ${result.summary}`);
    }
    return lines.join('\n') + '\n';
}

/** 텔레그램용 — Markdown V1, 짧고 깔끔. 4096자 제한 안에서. */
function pickChatIdForReport(): number | null {
    const cfg = vscode.workspace.getConfiguration('g1nation');
    const allowed = cfg.get<number[]>('telegram.allowedChatIds', []) || [];
    if (allowed.length > 0 && Number.isFinite(allowed[0]) && allowed[0] !== 0) {
        return allowed[0];
    }
    const fallback = cfg.get<number>('stocks.telegramChatId', 0);
    return fallback && Number.isFinite(fallback) ? fallback : null;
}

function formatKstNow(): string {
    const fmt = new Intl.DateTimeFormat('ko-KR', {
        timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', hour12: false,
    });
    const parts = fmt.formatToParts(new Date());
    const get = (t: string) => parts.find(p => p.type === t)?.value || '';
    return `${get('year')}-${get('month')}-${get('day')} ${get('hour')}:${get('minute')} KST`;
}

export function renderTopFiveForTelegram(
    result: TopFiveResult,
    rangeLabel: string,
): string {
    if (result.items.length === 0) {
        return `🎯 *Astra 발굴 Top 5* (${rangeLabel})\n${formatKstNow()}\n\n⚠️ LLM 분석 실패 — 채팅 창에서 raw 결과 확인.`;
    }
    const lines: string[] = [
        `🎯 *Astra 발굴 Top 5* (${rangeLabel})`,
        formatKstNow(),
        '',
    ];
    for (const it of result.items) {
        lines.push(`${it.rank}\\. *${it.name}* (\`${it.symbol}\`)`);
        lines.push(`   ${it.pitch}`);
        if (it.candidate) {
            const f = it.candidate.fundamentals;
            const sc = it.candidate.score;
            lines.push(`   종합 ${sc.total} (${sc.breakdown})${sc.valueTrapGuarded ? ' ⚠️VT가드' : ''}`);
            lines.push(`   ROE ${f.roe?.toFixed(1) ?? '-'}% · OM ${f.operatingMargin?.toFixed(1) ?? '-'}% · 유보 ${f.retentionRatio?.toLocaleString() ?? '-'}%`);
        }
        lines.push('');
    }
    if (result.summary) {
        lines.push(`💬 ${result.summary}`);
    }
    const joined = lines.join('\n');
    return joined.length > 3800 ? joined.slice(0, 3800) + '\n…(잘림)' : joined;
}

export async function sendTopFiveToTelegram(
    context: vscode.ExtensionContext,
    text: string,
): Promise<{ ok: boolean; reason?: string }> {
    const chatId = pickChatIdForReport();
    if (chatId === null) {
        return { ok: false, reason: '텔레그램 chatId 미설정 (allowedChatIds 또는 stocks.telegramChatId)' };
    }
    const token = (await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY)) || '';
    if (!token.trim()) {
        return { ok: false, reason: '텔레그램 봇 토큰 없음' };
    }
    const client = new TelegramHttpClient({ getToken: () => token });
    try {
        await client.sendMessage({ chatId, text, parseMode: 'Markdown' });
        logInfo('Top 5 텔레그램 발송 완료.', { chatId, chars: text.length });
        return { ok: true };
    } catch (e: any) {
        logError('Top 5 텔레그램 발송 실패.', { chatId, error: e?.message ?? String(e) });
        return { ok: false, reason: e?.message ?? String(e) };
    }
}
