import { getSettings, saveSettings } from "../storage/db";
import type { Settings } from "../types";
import type { GeminiConfig } from "./config";

const GEMINI_BASE_URL =
	"https://generativelanguage.googleapis.com/v1beta/models";
const RETRY_DELAYS = [1000, 2000, 4000];
const TEXT_RATE_LIMIT = 10;
const IMAGE_RATE_LIMIT = 5;

function sleep(ms: number): Promise<void> {
	return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function generateText(
	prompt: string,
	systemPrompt: string,
	config: GeminiConfig,
): Promise<string | null> {
	const url = `${GEMINI_BASE_URL}/${config.geminiModel}:generateContent?key=${config.geminiApiKey}`;
	const body = {
		contents: [{ role: "user", parts: [{ text: prompt }] }],
		systemInstruction: { parts: [{ text: systemPrompt }] },
	};

	for (let attempt = 0; attempt < 3; attempt++) {
		try {
			const res = await fetch(url, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify(body),
			});

			if (!res.ok) {
				if (attempt < 2) {
					await sleep(RETRY_DELAYS[attempt] ?? 1000);
					continue;
				}
				return null;
			}

			const data = await res.json();
			return data.candidates[0].content.parts[0].text;
		} catch {
			if (attempt < 2) {
				await sleep(RETRY_DELAYS[attempt] ?? 1000);
				continue;
			}
			return null;
		}
	}

	return null;
}

async function blobToBase64(blob: Blob): Promise<string> {
	const buffer = await blob.arrayBuffer();
	const bytes = new Uint8Array(buffer);
	let binary = "";
	for (const byte of bytes) {
		binary += String.fromCharCode(byte);
	}
	return btoa(binary);
}

function base64ToBlob(base64: string, mimeType: string): Blob {
	const binary = atob(base64);
	const bytes = new Uint8Array(binary.length);
	for (let i = 0; i < binary.length; i++) {
		bytes[i] = binary.charCodeAt(i);
	}
	return new Blob([bytes], { type: mimeType });
}

export async function generateImage(
	prompt: string,
	referenceImages: Blob[],
	config: GeminiConfig,
): Promise<Blob | null> {
	const url = `${GEMINI_BASE_URL}/${config.imageModel}:generateContent?key=${config.geminiApiKey}`;

	try {
		const parts: Array<Record<string, unknown>> = [];

		// Add reference images as inlineData parts
		for (const refImage of referenceImages) {
			const base64 = await blobToBase64(refImage);
			parts.push({
				inlineData: {
					mimeType: refImage.type || "image/png",
					data: base64,
				},
			});
		}

		// Add text prompt as last part
		parts.push({ text: prompt });

		const body = {
			contents: [{ role: "user", parts }],
			generationConfig: { responseModalities: ["IMAGE"] },
		};

		const res = await fetch(url, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify(body),
		});

		if (!res.ok) return null;

		const data = await res.json();
		const inlineData = data.candidates[0].content.parts[0].inlineData;
		return base64ToBlob(inlineData.data, inlineData.mimeType || "image/png");
	} catch {
		return null;
	}
}

function getTodayDate(): string {
	return new Date().toISOString().split("T")[0] ?? "";
}

function getDefaultSettings(): Settings {
	return {
		id: 1,
		audioEnabled: true,
		speechEnabled: false,
		apiKey: "",
		apiCallsToday: { text: 0, image: 0 },
		lastApiCallDate: getTodayDate(),
	};
}

async function getOrCreateSettings(db: IDBDatabase): Promise<Settings> {
	const settings = await getSettings(db);
	if (!settings) {
		const defaults = getDefaultSettings();
		await saveSettings(db, defaults);
		return defaults;
	}
	// Migrate old settings that don't have rate-limit fields
	if (!settings.apiCallsToday) {
		settings.apiCallsToday = { text: 0, image: 0 };
		settings.lastApiCallDate = getTodayDate();
		await saveSettings(db, settings);
	}
	return settings;
}

export async function checkRateLimit(
	db: IDBDatabase,
	type: "text" | "image",
): Promise<boolean> {
	const settings = await getOrCreateSettings(db);
	const today = getTodayDate();

	// Reset if date changed
	if (settings.lastApiCallDate !== today) {
		settings.apiCallsToday = { text: 0, image: 0 };
		settings.lastApiCallDate = today;
		await saveSettings(db, settings);
	}

	return type === "text"
		? settings.apiCallsToday.text < TEXT_RATE_LIMIT
		: settings.apiCallsToday.image < IMAGE_RATE_LIMIT;
}

export async function incrementApiCall(
	db: IDBDatabase,
	type: "text" | "image",
): Promise<void> {
	const settings = await getOrCreateSettings(db);
	const today = getTodayDate();

	// Reset if date changed
	if (settings.lastApiCallDate !== today) {
		settings.apiCallsToday = { text: 0, image: 0 };
		settings.lastApiCallDate = today;
	}

	settings.apiCallsToday[type]++;
	settings.lastApiCallDate = today;
	await saveSettings(db, settings);
}
