import * as vscode from 'vscode';
import { ChatMessage } from '../../agent';
import { buildApiUrl, summarizeText } from '../../utils';
import { buildEngineMessageVariants } from '../../lib/contextBuilders/engineMessages';
import { samplingToRestBody } from '../../lmstudio/streamer';
import { lmStudioSamplingFromConfig } from '../../lib/contextBuilders/lmStudioSampling';

export interface CallNonStreamingDeps {
    context: vscode.ExtensionContext;
}

export async function callNonStreaming(deps: CallNonStreamingDeps, params: {
    baseUrl: string;
    modelName: string;
    engine: 'lmstudio' | 'ollama';
    messages: ChatMessage[];
    temperature: number;
    maxTokens?: number;
    contextLength?: number;
    signal?: AbortSignal;
}): Promise<{ text: string; stopReason?: string }> {
    const { baseUrl, modelName, engine, messages, temperature, signal } = params;
    const maxTokens = Math.max(256, params.maxTokens ?? 4096);

    // Cloud routing — streaming Response 를 받아 끝까지 모아서 텍스트로 환원.
    // Non-streaming 전용 endpoint 를 따로 두지 않고 stream 결과를 모으는 게 단순.
    try {
        const { parseModelPrefix, streamCloudCompletion } =
            require('../../features/providers') as typeof import('../../features/providers');
        const hit = parseModelPrefix(modelName);
        if (hit) {
            const response = await streamCloudCompletion(deps.context, hit, {
                messages: messages.map((m) => ({ role: m.role as any, content: m.content })),
                temperature,
                maxTokens,
                signal,
            });
            if (!response.ok) {
                const errText = await response.text().catch(() => '');
                throw new Error(`Cloud (${hit.provider}) ${response.status}: ${summarizeText(errText, 200)}`);
            }
            // OpenAI 호환 SSE 를 통째로 읽어 delta.content 합치기.
            const raw = await response.text();
            let acc = '';
            for (const line of raw.split('\n')) {
                const t = line.trim();
                if (!t.startsWith('data:')) continue;
                const payload = t.slice(5).trim();
                if (!payload || payload === '[DONE]') continue;
                try {
                    const obj = JSON.parse(payload);
                    const delta = obj?.choices?.[0]?.delta?.content;
                    if (typeof delta === 'string') acc += delta;
                } catch { /* skip malformed */ }
            }
            return { text: acc, stopReason: 'stop' };
        }
    } catch (e) {
        const msg = (e as Error)?.message ?? '';
        if (msg.startsWith('Cloud (')) throw e;
    }

    const numCtx = Math.max(2048, params.contextLength ?? 32768);
    const apiUrl = buildApiUrl(baseUrl, engine, 'chat');
    const variants = buildEngineMessageVariants(messages, engine);
    const sampling = samplingToRestBody(lmStudioSamplingFromConfig());
    const body = {
        model: modelName,
        messages: variants[0].messages,
        stream: false,
        ...(engine === 'lmstudio'
            ? { max_tokens: maxTokens, temperature, ...sampling }
            : { options: { num_ctx: numCtx, num_predict: maxTokens, temperature, ...sampling } }),
    };
    const response = await fetch(apiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
        signal,
    });
    if (!response.ok) {
        const errText = await response.text().catch(() => '');
        throw new Error(`Non-streaming fallback returned ${response.status}: ${summarizeText(errText, 200)}`);
    }
    const text = await response.text();
    try {
        const json = JSON.parse(text);
        if (engine === 'lmstudio') {
            return {
                text: json?.choices?.[0]?.message?.content ?? '',
                stopReason: json?.choices?.[0]?.finish_reason,
            };
        }
        return {
            text: json?.message?.content ?? json?.response ?? '',
            stopReason: json?.done_reason ?? (json?.done === true ? 'stop' : undefined),
        };
    } catch {
        return { text: '' };
    }
}
