import { tandoorConfig } from '$lib/server/tandoor-config';
import { z } from 'zod';
import { logError } from './utils/logger';
/**
 * Tandoor Recipe Export Format
 * Based on the Default/JSON-LD Tandoor export format
 */
export const TandoorRecipeSchema = z.object({
	name: z.string(),
	author: z.string().optional().nullable(),
	description: z.string().optional().nullable(),
	servings: z.number().optional().nullable(),
	servings_text: z.string().optional().nullable(),
	keywords: z.array(z.string()).optional(),
	prep_time: z.string().optional(),
	cook_time: z.string().optional(),
	waiting_time: z.string().optional(),
	steps: z
		.array(
			z.object({
				step: z.number(),
				instruction: z.string(),
				ingredients: z
					.array(
						z.object({
							food: z.object({
								id: z.number(),
								name: z.string()
							}),
							unit: z
								.object({
									id: z.number(),
									name: z.string()
								})
								.nullable(),
							amount: z.number(),
							note: z.string().optional()
						})
					)
					.optional()
			})
		)
		.optional(),
	ingredients: z
		.array(
			z.object({
				food: z.object({
					name: z.string()
				}),
				unit: z
					.object({
						name: z.string()
					})
					.nullable(),
				amount: z.number(),
				note: z.string().optional()
			})
		)
		.optional()
});

export type TandoorRecipe = z.infer<typeof TandoorRecipeSchema>;

interface ExtractedRecipe {
	name: string;
	servings: number | null;
	description: string | null;
	ingredients: Array<{
		item: string;
		amount: string;
		unit: string;
	}> | null;
	steps: string[] | null;
	image?: string | null;
}

/**
 * DTO for Tandoor Recipe API (POST /api/recipe/)
 * Matches the Tandoor endpoint schema for recipe creation
 */
interface TandoorRecipeDTO {
	name: string;
	description?: string;
	keywords: Array<{
		name: string;
		description?: string;
	}>;
	steps: Array<{
		name?: string;
		instruction: string;
		ingredients: Array<{
			food: {
				name: string;
			};
			unit: {
				name: string;
			} | null;
			amount: string;
			note?: string;
		}>;
		order?: number;
		show_as_header?: boolean;
	}>;
	working_time?: number;
	waiting_time?: number;
	servings?: number;
	servings_text?: string;
	private?: boolean;
	show_ingredient_overview?: boolean;
}

/**
 * Helper function to make authenticated API calls
 */
async function fetchFromTandoor<T>(
	url: string,
	options: Partial<RequestInit> = { method: 'GET' }
): Promise<{ ok: boolean; data?: T; error?: string }> {
	const headers = new Headers({
		'Content-Type': 'application/json',
		Accept: 'application/json',
		Authorization: `Bearer ${tandoorConfig.token}`
	});

	// Merge any additional headers from options
	if (options.headers) {
		const optHeaders = new Headers(options.headers);
		optHeaders.forEach((value, key) => {
			headers.set(key, value);
		});
	}

	console.debug(`Fetching from Tandoor: ${url}`, {
		method: options.method,
		headers: Object.fromEntries(headers),
		body: options.body
	});
	try {
		const response = await fetch(`${tandoorConfig.serverUrl}${url}`, {
			...options,
			headers
		});

		if (!response.ok) {
			const errorBody = await response.json().catch(() => ({}));
			logError(`[Tandoor] API Error ${response.status}: ${response.statusText}`, errorBody);
			return {
				ok: false,
				error: `API Error: ${response.statusText} - ${JSON.stringify(errorBody)}`
			};
		}

		const data = (await response.json()) as T;
		console.debug(`Tandoor response OK:`, data);
		return { ok: true, data };
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : 'Unknown error';
		logError('[Tandoor] Fetch error', error);
		return {
			ok: false,
			error: `Fetch error: ${errorMsg}`
		};
	}
}

/**
 * Partitions ingredients across steps by distributing them evenly
 * When step association is unknown, this spreads ingredients proportionally
 */
function partitionIngredientsAcrossSteps(
	ingredients: Array<{
		item: string;
		amount: string;
		unit: string;
	}>,
	stepCount: number
): Array<Array<{ item: string; amount: string; unit: string }>> {
	if (stepCount === 0 || !ingredients || ingredients.length === 0) {
		return [];
	}

	const partitions: Array<Array<{ item: string; amount: string; unit: string }>> = Array.from(
		{ length: stepCount },
		() => []
	);

	// Distribute ingredients round-robin across steps
	ingredients.forEach((ingredient, index) => {
		partitions[index % stepCount].push(ingredient);
	});

	console.debug(
		`Partitioned ${ingredients.length} ingredients across ${stepCount} steps:`,
		partitions
	);
	return partitions;
}

/**
 * Parses amount string to a number, handling special cases
 * Returns null if amount cannot be parsed to a valid number
 */
function parseAmount(amountStr: string): number | null {
	if (!amountStr || typeof amountStr !== 'string') {
		return null;
	}

	const trimmed = amountStr.trim().toLowerCase();

	// Skip special cases that can't be converted to numbers
	if (!trimmed || trimmed === 'q.b.' || trimmed === 'qb' || trimmed === 'to taste') {
		return null;
	}

	// Try to extract the first number from the string
	const numberMatch = trimmed.match(/^[\d.,]+/);
	if (!numberMatch) {
		return null;
	}

	const numStr = numberMatch[0].replace(',', '.');
	const parsed = parseFloat(numStr);

	// Return null for zero or invalid numbers
	if (isNaN(parsed) || parsed === 0) {
		return null;
	}

	return parsed;
}

/**
 * Builds a complete Tandoor Recipe DTO from extracted recipe data
 * Includes ingredients partitioned across steps
 */
function buildTandoorRecipeDTO(recipe: ExtractedRecipe): TandoorRecipeDTO {
	const stepCount = recipe.steps?.length || 1;
	const ingredientPartitions = partitionIngredientsAcrossSteps(recipe.ingredients || [], stepCount);

	const steps: TandoorRecipeDTO['steps'] = (recipe.steps || []).map((instruction, index) => {
		// Map ingredients, converting unparseable amounts to 1 q.b.
		const mappedIngredients = (ingredientPartitions[index] || []).map((ing) => {
			const amount = parseAmount(ing.amount);

			if (amount === null) {
				console.debug(
					`Converting ingredient with unparseable amount to 1 q.b.: ${ing.item} (${ing.amount})`
				);
				return {
					food: {
						name: ing.item
					},
					unit: { name: 'q.b.' },
					amount: '1',
					note: ''
				};
			}

			return {
				food: {
					name: ing.item
				},
				unit: ing.unit && ing.unit.trim() ? { name: ing.unit } : null,
				amount: amount.toString(),
				note: ''
			};
		});

		return {
			instruction,
			order: index,
			ingredients: mappedIngredients
		};
	});

	return {
		name: recipe.name,
		description: recipe.description || undefined,
		keywords: [],
		steps,
		servings: recipe.servings || undefined,
		servings_text: recipe.servings ? `${recipe.servings} servings` : undefined,
		private: false,
		show_ingredient_overview: true
	};
}

/**
 * Uploads a recipe to Tandoor server using the DTO-based approach
 * Creates recipe with ingredients partitioned across steps in a single request
 */
export async function uploadRecipeWithIngredientsDTO(
	recipe: ExtractedRecipe
): Promise<{ success: boolean; recipeId?: number; imageUrl?: string; error?: string }> {
	try {
		// Validate token
		const token = tandoorConfig.token;
		if (!token) {
			return {
				success: false,
				error: 'TANDOOR_TOKEN environment variable not set'
			};
		}

		// Build the complete DTO
		const recipeDTO = buildTandoorRecipeDTO(recipe);
		console.debug('Uploading recipe with ingredients DTO:', recipeDTO);

		// Call the API with the DTO
		const recipeResult = await fetchFromTandoor<{ id: number }>(`/api/recipe/`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify(recipeDTO)
		});

		if (!recipeResult.ok || !recipeResult.data) {
			console.error('Recipe creation failed:', recipeResult.error);
			return {
				success: false,
				error: `Failed to create recipe: ${recipeResult.error}`
			};
		}

		const createdRecipe = recipeResult.data;
		console.debug('Successfully created recipe with ID:', createdRecipe.id);

		return {
			success: true,
			recipeId: createdRecipe.id,
			imageUrl: recipe.image || undefined
		};
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : 'Unknown error';
		logError('[Tandoor] Error uploading recipe', error);
		return {
			success: false,
			error: `Error uploading to Tandoor: ${errorMsg}`
		};
	}
}

/**
 * Determine if a string is a direct HTTP(S) URL
 */
function isDirectUrl(url: string): boolean {
	return url.startsWith('http://') || url.startsWith('https://');
}

/**
 * Determine if a string is a base64 data URL
 */
function isDataUrl(url: string): boolean {
	return url.startsWith('data:');
}

/**
 * Extract MIME type and base64 data from data URL
 */
function parseDataUrl(dataUrl: string): { mimeType: string; base64Data: string } | null {
	const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
	if (!match) return null;
	return {
		mimeType: match[1],
		base64Data: match[2]
	};
}

/**
 * Convert MIME type to file extension
 */
function getExtensionFromMimeType(mimeType: string): string {
	const mimeToExt: Record<string, string> = {
		'image/jpeg': '.jpg',
		'image/jpg': '.jpg',
		'image/png': '.png',
		'image/gif': '.gif',
		'image/webp': '.webp'
	};
	return mimeToExt[mimeType] || '.jpg';
}

/**
 * Uploads an image to a Tandoor recipe using proper multipart/form-data format
 *
 * Always downloads the image and uploads as a File object (not Blob).
 * This ensures proper multipart encoding with filename and MIME type metadata.
 *
 * Handles two source formats:
 * - Direct HTTP(S) URLs: Downloads from URL, detects MIME from response headers
 * - Base64 data URLs: Decodes base64, uses embedded MIME type
 *
 * @param recipeId - Tandoor recipe ID
 * @param imageUrl - Image URL (HTTP(S) URL or base64 data URL)
 * @returns Success status and optional error message
 */
export async function uploadRecipeImage(
	recipeId: number,
	imageUrl: string
): Promise<{ success: boolean; error?: string }> {
	try {
		const token = tandoorConfig.token;
		if (!token) {
			return { success: false, error: 'TANDOOR_TOKEN not set' };
		}

		console.log(`[Tandoor Upload] Recipe ID: ${recipeId}`);
		console.log(
			`[Tandoor Upload] Image type: ${isDirectUrl(imageUrl) ? 'URL' : isDataUrl(imageUrl) ? 'Base64' : 'Unknown'}`
		);
		console.log(`[Tandoor Upload] Image source: ${imageUrl.substring(0, 100)}...`);

		let buffer: Buffer;
		let mimeType: string;
		let sourceType: string;

		// Handle base64 data URLs
		if (isDataUrl(imageUrl)) {
			sourceType = 'base64';
			console.log('[Tandoor Upload] Decoding base64 data URL');

			const parsed = parseDataUrl(imageUrl);
			if (!parsed) {
				return { success: false, error: 'Invalid data URL format' };
			}

			buffer = Buffer.from(parsed.base64Data, 'base64');
			mimeType = parsed.mimeType;
			console.log(`[Tandoor Upload] Decoded ${buffer.length} bytes, MIME: ${mimeType}`);
		}
		// Handle direct HTTP(S) URLs
		else if (isDirectUrl(imageUrl)) {
			sourceType = 'url';
			console.log('[Tandoor Upload] Downloading image from URL');

			const response = await fetch(imageUrl);
			if (!response.ok) {
				return {
					success: false,
					error: `Failed to download image: ${response.status} ${response.statusText}`
				};
			}

			// Get MIME type from response headers (most reliable)
			mimeType = response.headers.get('content-type') || 'image/jpeg';
			// Remove charset if present (e.g., "image/jpeg; charset=utf-8")
			mimeType = mimeType.split(';')[0].trim();

			const arrayBuffer = await response.arrayBuffer();
			buffer = Buffer.from(arrayBuffer);
			console.log(`[Tandoor Upload] Downloaded ${buffer.length} bytes, MIME: ${mimeType}`);
		}
		// Unknown format
		else {
			return { success: false, error: 'Unsupported image format (not HTTP(S) URL or data URL)' };
		}

		// Create proper File object (critical for multipart/form-data)
		const extension = getExtensionFromMimeType(mimeType);
		const filename = `recipe-image${extension}`;

		// In Node.js, we must create a File from Blob (Blob alone doesn't work)
		// Convert Buffer to Uint8Array for compatibility
		const uint8Array = new Uint8Array(buffer);
		const blob = new Blob([uint8Array], { type: mimeType });
		const file = new File([blob], filename, { type: mimeType });

		console.log(`[Tandoor Upload] Created File: ${filename} (${file.size} bytes, ${file.type})`);

		// Upload to Tandoor
		const formData = new FormData();
		formData.append('image', file);

		console.log('[Tandoor Upload] Uploading to Tandoor...');
		const uploadResponse = await fetch(`${tandoorConfig.serverUrl}/api/recipe/${recipeId}/image/`, {
			method: 'PUT',
			headers: {
				Authorization: `Bearer ${token}`
				// DO NOT set Content-Type - let fetch set it with boundary
			},
			body: formData
		});

		if (!uploadResponse.ok) {
			const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
			const responseHeaders = JSON.stringify(Object.fromEntries(uploadResponse.headers.entries()));

			console.error(
				`[Tandoor Upload] Failed: ${uploadResponse.status} ${uploadResponse.statusText}`
			);
			console.error(`[Tandoor Upload] Response headers: ${responseHeaders}`);
			console.error(`[Tandoor Upload] Response body: ${errorText.substring(0, 500)}`);
			console.error(
				`[Tandoor Upload] File metadata: ${filename}, ${file.size} bytes, ${file.type}`
			);

			return {
				success: false,
				error: `Upload failed (${uploadResponse.status}): ${errorText.substring(0, 200)}`
			};
		}

		console.log(`[Tandoor Upload] ✓ Success (${sourceType}, ${file.size} bytes)`);
		return { success: true };
	} catch (error) {
		const errorMsg = error instanceof Error ? error.message : 'Unknown error';
		logError('[Tandoor Upload] Exception', error);
		// Don't fail recipe creation if image fails
		return { success: false, error: errorMsg };
	}
}
