import OpenAI from 'openai';
import { env } from '$env/dynamic/private';
import { logError } from './utils/logger';

const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;

const parseTimeoutMs = (raw: string | undefined): number => {
	if (!raw) return DEFAULT_REQUEST_TIMEOUT_MS;
	const n = Number(raw);
	return Number.isFinite(n) && n > 0 ? n : DEFAULT_REQUEST_TIMEOUT_MS;
};

export const createLLM = () => {
	const baseURL = env.OPENAI_BASE_URL;
	const apiKey = env.OPENAI_API_KEY;
	const model = env.LLM_MODEL || 'gpt-4o';
	const timeout = parseTimeoutMs(env.LLM_REQUEST_TIMEOUT_MS);

	console.log('[LLM] Initializing client...');
	console.log('[LLM] Base URL:', baseURL);
	console.log('[LLM] Model:', model);
	console.log('[LLM] Request timeout (ms):', timeout);

	if (!baseURL) {
		throw new Error('OPENAI_BASE_URL environment variable is not set');
	}

	if (!apiKey) {
		throw new Error('OPENAI_API_KEY environment variable is not set');
	}

	const client = new OpenAI({
		apiKey,
		baseURL,
		timeout,
		maxRetries: 0
	});

	return { client, model };
};

/**
 * Health check for LLM service
 */
export async function checkLLMHealth(): Promise<boolean> {
	try {
		const { client } = createLLM();
		await client.models.list();
		console.log('[LLM] Health check passed');
		return true;
	} catch (e) {
		logError('[LLM] Health check failed', e);
		return false;
	}
}

/**
 * Strip a trailing /v1 (or /v1/) from a base URL to get the llama-swap root.
 * llama-swap exposes both /v1/* (OpenAI-compatible) and /running, /upstream, etc.
 * at the bare root.
 */
function llamaSwapRoot(baseURL: string): string {
	return baseURL.replace(/\/v1\/?$/, '').replace(/\/$/, '');
}

interface RunningModelEntry {
	model: string;
	state?: string;
}

/**
 * Query llama-swap's /running endpoint and report whether `model` is currently
 * loaded and ready to serve. Returns false on any error (treat as cold).
 *
 * Why we don't fold this into checkModelAvailability(): /v1/models lists every
 * model llama-swap is configured to swap to (not just loaded ones), while
 * /running returns only the in-VRAM instance. Both signals are useful.
 */
export async function isModelLoaded(model: string): Promise<boolean> {
	const baseURL = env.OPENAI_BASE_URL;
	if (!baseURL) return false;

	try {
		const url = `${llamaSwapRoot(baseURL)}/running`;
		const response = await fetch(url, {
			signal: AbortSignal.timeout(5_000)
		});
		if (!response.ok) return false;
		const data = (await response.json()) as { running?: RunningModelEntry[] };
		const running = data.running ?? [];
		return running.some((m) => m.model === model && (m.state ?? 'ready') === 'ready');
	} catch (e) {
		logError('[LLM] isModelLoaded check failed', e);
		return false;
	}
}

/**
 * Check if a specific model is available in the OpenAI-compatible API
 * @param model - The model ID to check for availability
 * @returns Object with available status and optional message
 */
export async function checkModelAvailability(
	model: string
): Promise<{ available: boolean; message?: string }> {
	try {
		console.log('[LLM] Checking model availability:', model);
		const { client } = createLLM();
		const response = await client.models.list();
		const models = response.data || [];

		const foundModel = models.find((m) => m.id === model);

		if (foundModel) {
			console.log('[LLM] Model available:', model);
			return { available: true };
		} else {
			const availableModels = models.map((m) => m.id).join(', ');
			console.warn('[LLM] Model not found:', model);
			console.warn('[LLM] Available models:', availableModels);
			return {
				available: false,
				message: `Model "${model}" not found. Available models: ${availableModels}`
			};
		}
	} catch (e) {
		logError('[LLM] Model availability check failed', e);
		return {
			available: false,
			message: `Failed to check model availability: ${(e as Error).message}`
		};
	}
}
