import { S3Client, PutObjectCommand, DeleteObjectCommand, ListObjectsV2Command, CopyObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { env } from '$env/dynamic/private';

// Configuration du client S3 pour Cloudflare R2
// On utilise une fonction pour créer le client à la demande (lazy initialization)
// car les variables d'environnement dynamiques ne sont disponibles qu'au runtime
function getR2Client() {
	return new S3Client({
		region: 'auto',
		endpoint: `https://${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
		credentials: {
			accessKeyId: env.CLOUDFLARE_R2_ACCESS_KEY_ID!,
			secretAccessKey: env.CLOUDFLARE_R2_SECRET_ACCESS_KEY!
		}
	});
}

// Export alias pour compatibilité
export const getCloudflareR2Client = getR2Client;

function getBucket() {
	return env.CLOUDFLARE_R2_BUCKET_NAME!;
}

function getPublicUrl() {
	return env.CLOUDFLARE_R2_PUBLIC_URL!;
}

/**
 * Génère une clé unique pour un fichier dans R2
 */
export function generateMediaKey(userId: string, filename: string, folder?: string): string {
	const cleanUserId = userId.replace('user:', '');
	const timestamp = Date.now();
	const randomStr = Math.random().toString(36).substring(2, 8);
	const extension = filename.split('.').pop()?.toLowerCase() || '';
	const sanitizedName = filename
		.replace(/\.[^/.]+$/, '') // Enlever l'extension
		.replace(/[^a-zA-Z0-9-_]/g, '_') // Remplacer caractères spéciaux
		.substring(0, 50); // Limiter la longueur
	
	const basePath = folder 
		? `users/${cleanUserId}/${folder}` 
		: `users/${cleanUserId}/media`;
	
	return `${basePath}/${timestamp}_${randomStr}_${sanitizedName}.${extension}`;
}

/**
 * Génère le nom de dossier pour une entreprise
 * Format: nom_raccourci-siret (ou nom_raccourci si pas de siret)
 */
export function generateCompanyFolderName(name: string, siret?: string | null): string {
	// Créer un nom raccourci: supprimer accents, caractères spéciaux, limiter longueur
	const shortName = name
		.normalize('NFD').replace(/[\u0300-\u036f]/g, '') // Supprimer accents
		.toLowerCase()
		.replace(/[^a-z0-9\s-]/g, '') // Garder uniquement alphanumériques, espaces, tirets
		.replace(/\s+/g, '-') // Espaces → tirets
		.replace(/-+/g, '-') // Éviter tirets multiples
		.substring(0, 30) // Limiter longueur
		.replace(/^-|-$/g, ''); // Supprimer tirets en début/fin
	
	if (siret && siret.trim()) {
		const cleanSiret = siret.replace(/\s/g, ''); // Supprimer espaces dans le SIRET
		return `${shortName}-${cleanSiret}`;
	}
	
	return shortName;
}

/**
 * Génère le chemin R2 du dossier d'une entreprise
 */
export function getCompanyR2Path(companyFolderName: string): string {
	return `clients/annonceurs/${companyFolderName}`;
}

/**
 * Crée le dossier d'une entreprise sur R2 avec un sous-dossier ads/
 * Note: R2 n'a pas de vrais dossiers, on crée un fichier .gitkeep pour les matérialiser
 */
export async function createCompanyFolder(name: string, siret?: string | null): Promise<{ folderPath: string; folderName: string }> {
	const folderName = generateCompanyFolderName(name, siret);
	const basePath = getCompanyR2Path(folderName);
	
	// Créer un fichier .gitkeep dans le dossier principal et dans ads/
	const gitkeepContent = Buffer.from('');
	
	await Promise.all([
		uploadToR2(gitkeepContent, `${basePath}/.gitkeep`, 'text/plain'),
		uploadToR2(gitkeepContent, `${basePath}/ads/.gitkeep`, 'text/plain')
	]);
	
	return { folderPath: basePath, folderName };
}

/**
 * Génère un nom de dossier pour une publicité
 * Format: nom_raccourci-id
 */
export function generateAdFolderName(adName: string, adId: string): string {
	const cleanId = adId.replace('ad:', '');
	const shortName = adName
		.normalize('NFD').replace(/[\u0300-\u036f]/g, '') // Supprimer accents
		.toLowerCase()
		.replace(/[^a-z0-9\s-]/g, '') // Garder uniquement alphanumériques, espaces, tirets
		.replace(/\s+/g, '-') // Espaces → tirets
		.replace(/-+/g, '-') // Éviter tirets multiples
		.substring(0, 30) // Limiter longueur
		.replace(/^-|-$/g, ''); // Supprimer tirets en début/fin
	
	return `${shortName}-${cleanId}`;
}

/**
 * Génère le chemin de stockage d'une pub pour une entreprise
 */
export function getCompanyAdPath(companyFolderName: string, adId: string, filename: string, adName?: string): string {
	const adFolderName = adName ? generateAdFolderName(adName, adId) : adId.replace('ad:', '');
	return `clients/annonceurs/${companyFolderName}/ads/${adFolderName}/${filename}`;
}

/**
 * Génère une URL signée pour upload direct vers R2 (PUT)
 * Utilisé par Plainly pour uploader directement les rendus
 */
export async function generatePresignedUploadUrl(
	key: string,
	contentType: string = 'video/mp4',
	expiresIn: number = 3600 // 1 heure par défaut
): Promise<{ uploadUrl: string; publicUrl: string; key: string }> {
	const command = new PutObjectCommand({
		Bucket: getBucket(),
		Key: key,
		ContentType: contentType
	});

	const uploadUrl = await getSignedUrl(getR2Client(), command, { expiresIn });
	const publicUrl = `${getPublicUrl()}/${key}`;

	return { uploadUrl, publicUrl, key };
}

/**
 * Upload un fichier sur Cloudflare R2
 */
export async function uploadToR2(
	file: File | Buffer | Uint8Array,
	key: string,
	contentType: string,
	metadata?: Record<string, string>
): Promise<{ url: string; key: string }> {
	let body: Buffer | Uint8Array;
	
	if (file instanceof File) {
		body = Buffer.from(await file.arrayBuffer());
	} else {
		body = file;
	}
	
	// Nettoyer les clés metadata pour éviter les erreurs de caractères invalides
	const cleanMetadata: Record<string, string> = {};
	if (metadata) {
		for (const [key, value] of Object.entries(metadata)) {
			// Remplacer les caractères non valides par des tirets, convertir en minuscules
			const cleanKey = key.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
			// Encoder les valeurs pour éviter les caractères non-ASCII dans les headers HTTP
			// On utilise encodeURIComponent pour les valeurs qui pourraient contenir des caractères spéciaux
			const cleanValue = String(value).replace(/[^\x20-\x7E]/g, '_'); // Garder uniquement les caractères ASCII imprimables
			cleanMetadata[cleanKey] = cleanValue;
		}
	}
	
	const command = new PutObjectCommand({
		Bucket: getBucket(),
		Key: key,
		Body: body,
		ContentType: contentType,
		...(Object.keys(cleanMetadata).length > 0 && { Metadata: cleanMetadata })
	});
	
	await getR2Client().send(command);
	
	return {
		url: `${getPublicUrl()}/${key}`,
		key
	};
}

/**
 * Génère une URL pré-signée pour upload direct
 */
export async function getUploadPresignedUrl(
	key: string,
	contentType: string,
	expiresIn: number = 3600
): Promise<string> {
	const command = new PutObjectCommand({
		Bucket: getBucket(),
		Key: key,
		ContentType: contentType
	});
	
	return getSignedUrl(getR2Client(), command, { expiresIn });
}

/**
 * Supprime un fichier de R2
 */
export async function deleteFromR2(key: string): Promise<void> {
	const command = new DeleteObjectCommand({
		Bucket: getBucket(),
		Key: key
	});
	
	await getR2Client().send(command);
}

/**
 * Supprime plusieurs fichiers de R2
 */
export async function deleteMultipleFromR2(keys: string[]): Promise<void> {
	await Promise.all(keys.map(key => deleteFromR2(key)));
}

/**
 * Liste les fichiers dans un préfixe (dossier)
 */
export async function listFilesInR2(
	prefix: string,
	maxKeys: number = 1000
): Promise<{ key: string; size: number; lastModified: Date }[]> {
	const command = new ListObjectsV2Command({
		Bucket: getBucket(),
		Prefix: prefix,
		MaxKeys: maxKeys
	});
	
	const response = await getR2Client().send(command);
	
	return (response.Contents || []).map(item => ({
		key: item.Key || '',
		size: item.Size || 0,
		lastModified: item.LastModified || new Date()
	}));
}

/**
 * Copie un fichier dans R2 (pour déplacer entre dossiers)
 */
export async function copyInR2(sourceKey: string, destinationKey: string): Promise<void> {
	const command = new CopyObjectCommand({
		Bucket: getBucket(),
		CopySource: `${getBucket()}/${sourceKey}`,
		Key: destinationKey
	});
	
	await getR2Client().send(command);
}

/**
 * Déplace un fichier dans R2
 */
export async function moveInR2(sourceKey: string, destinationKey: string): Promise<void> {
	await copyInR2(sourceKey, destinationKey);
	await deleteFromR2(sourceKey);
}

/**
 * Génère l'URL publique d'un fichier
 */
export function getFilePublicUrl(key: string): string {
	return `${getPublicUrl()}/${key}`;
}

// Export alias pour compatibilité
export const getR2PublicUrl = getFilePublicUrl;

/**
 * Détermine le type de média à partir du MIME type
 */
export function getMediaType(mimeType: string): 'image' | 'video' | 'audio' | 'document' {
	if (mimeType.startsWith('image/')) return 'image';
	if (mimeType.startsWith('video/')) return 'video';
	if (mimeType.startsWith('audio/')) return 'audio';
	return 'document';
}

/**
 * Vérifie si un fichier est autorisé
 */
export function isAllowedFile(mimeType: string, maxSizeMb: number = 100): { allowed: boolean; reason?: string } {
	const allowedTypes = [
		// Images
		'image/jpeg',
		'image/png',
		'image/gif',
		'image/webp',
		'image/svg+xml',
		// Vidéos
		'video/mp4',
		'video/webm',
		'video/quicktime',
		'video/x-msvideo',
		// Audio
		'audio/mpeg',
		'audio/wav',
		'audio/ogg',
		// Documents
		'application/pdf'
	];
	
	if (!allowedTypes.includes(mimeType)) {
		return { allowed: false, reason: `Type de fichier non autorisé: ${mimeType}` };
	}
	
	return { allowed: true };
}

/**
 * Vérifie si une image est principalement un fond uni (couleur solide)
 */
async function isUniformBackground(imagePath: string): Promise<boolean> {
	try {
		const sharp = await import('sharp');
		
		// Réduire l'image à 10x10 pour analyser les variations de couleur
		const resized = await sharp.default(imagePath)
			.resize(10, 10)
			.raw()
			.toBuffer({ resolveWithObject: true });

		const data = resized.data;
		const channels = resized.info.channels;
		
		if (data.length === 0) return false;

		// Calculer la moyenne et la variance pour chaque canal
		let sumR = 0, sumG = 0, sumB = 0;
		let countPixels = 0;

		for (let i = 0; i < data.length; i += channels) {
			sumR += data[i];
			sumG += data[i + 1];
			sumB += data[i + 2];
			countPixels++;
		}

		const avgR = sumR / countPixels;
		const avgG = sumG / countPixels;
		const avgB = sumB / countPixels;

		// Calculer la variance
		let varR = 0, varG = 0, varB = 0;
		for (let i = 0; i < data.length; i += channels) {
			varR += Math.pow(data[i] - avgR, 2);
			varG += Math.pow(data[i + 1] - avgG, 2);
			varB += Math.pow(data[i + 2] - avgB, 2);
		}

		varR /= countPixels;
		varG /= countPixels;
		varB /= countPixels;

		// Si la variance est faible (< 300), c'est un fond uni
		const avgVariance = (varR + varG + varB) / 3;
		return avgVariance < 300;
	} catch (err) {
		console.error('Error checking background uniformity:', err);
		return false; // Si erreur, considérer comme non-uniforme
	}
}

/**
 * Crée un JPEG valide minimal (1x1 pixel)
 */
function createMinimalJpeg(r: number, g: number, b: number): Buffer {
	// JPEG minimal valide: SOI + APP0 + SOF + DHT + DQT + SOS + image data + EOI
	// Ceci est un JPEG 1x1 hardcodé avec une couleur
	const hexStr = [
		'ffd8ffe0001a4a46494600010100000100010000',
		'ffdb004300080606070605080707070909080a0c140d0c0b0b0c1912130f141d1a1f1e1d1a1c1c20242e2720222c231c1c2837292c30313434341f27393d3832',
		'ffc0000b080001000101011100',
		'ffc4001500010100000000000000000000000000000001',
		'ffda00080101000000003f00',
		'ffd9'
	];
	
	// Ceci est une simple JPEG 1x1 - pour tester
	const baseJpeg = Buffer.concat([
		Buffer.from([0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00]),
		Buffer.from([0xFF, 0xDB, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0A, 0x0A, 0x09, 0x08, 0x09, 0x09, 0x0A, 0x0C, 0x0F, 0x0C, 0x0A, 0x0B, 0x0E, 0x0B, 0x09, 0x09, 0x0D, 0x11, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x11, 0x0A, 0x0C, 0x12, 0x13, 0x0F, 0x13, 0x10, 0x0F]),
		Buffer.from([0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00]),
		Buffer.from([0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B]),
		Buffer.from([0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3F, 0x00]),
		Buffer.from([r, g, b]),
		Buffer.from([0xFF, 0xD9])
	]);
	
	return baseJpeg;
}

/**
 * Génère un thumbnail placeholder basé sur le hash du nom de la vidéo
 * Retourne l'URL du thumbnail ou null si upload impossible
 */
async function generatePlaceholderThumbnail(
	videoFileName: string,
	userId: string
): Promise<string | null> {
	try {
		const crypto = await import('crypto');

		const cleanUserId = userId.replace('user:', '');
		const videoName = videoFileName.replace(/\.[^/.]+$/, '');

		// Générer une couleur basée sur le hash du nom du fichier
		const hash = crypto.createHash('md5').update(videoFileName).digest('hex');
		
		// Obtenir 3 composantes de couleur du hash
		const r = parseInt(hash.substring(0, 2), 16);
		const g = parseInt(hash.substring(2, 4), 16);
		const b = parseInt(hash.substring(4, 6), 16);

		console.log(`[Thumbnail] Creating placeholder for ${videoFileName} with color rgb(${r}, ${g}, ${b})`);

		// Créer un JPEG minimal
		const thumbnailBuffer = createMinimalJpeg(r, g, b);

		console.log(`[Thumbnail] Generated JPEG buffer size: ${thumbnailBuffer.length} bytes`);

		// Générer une clé R2 pour le thumbnail
		const thumbKey = `users/${cleanUserId}/thumbnails/${Date.now()}_${videoName}_placeholder.jpg`;

		console.log(`[Thumbnail] Uploading to R2: ${thumbKey}`);

		// Upload le thumbnail
		const { url } = await uploadToR2(
			thumbnailBuffer,
			thumbKey,
			'image/jpeg'
		);

		console.log(`[Thumbnail] Successfully generated for ${videoFileName} -> ${url}`);
		return url;
	} catch (err) {
		console.error('[Thumbnail] Error generating placeholder:', err);
		return null;
	}
}

/**
 * Extrait le premier frame d'une vidéo et l'envoie à R2
 * Si FFmpeg n'est pas disponible, génère un thumbnail placeholder
 * Retourne l'URL du thumbnail ou null si extraction impossible
 */
export async function generateVideoThumbnail(
	videoPath: string,
	userId: string,
	videoFileName: string
): Promise<string | null> {
	const path = await import('path');
	const fs = await import('fs');
	const os = await import('os');
	
	try {
		// Essayer avec FFmpeg si disponible
		try {
			// @ts-ignore - ffmpeg-static types are not well-defined
			const ffmpegStaticModule = await import('ffmpeg-static');
			const ffmpegPath = (ffmpegStaticModule.default || ffmpegStaticModule) as string;
			
			console.log('[Thumbnail] FFmpeg static path:', ffmpegPath);
			
			// Vérifier que le fichier FFmpeg existe
			if (!fs.existsSync(ffmpegPath)) {
				throw new Error(`FFmpeg binary not found at: ${ffmpegPath}`);
			}

			const { execFile } = await import('child_process');
			const util = await import('util');
			const execFilePromise = util.promisify(execFile);

			const tempDir = os.tmpdir();
			const cleanUserId = userId.replace('user:', '');
			const videoName = videoFileName.replace(/\.[^/.]+$/, '');

			console.log('[Thumbnail] Input video:', videoPath);
			console.log('[Thumbnail] Video exists:', fs.existsSync(videoPath));

			// Extraire le premier frame
			const thumbnailFileName = `thumb_${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`;
			const thumbnailPath = path.join(tempDir, thumbnailFileName);

			console.log('[Thumbnail] Output path:', thumbnailPath);

			await Promise.race([
				new Promise<void>(async (resolve, reject) => {
					try {
						console.log('[Thumbnail] Starting FFmpeg extraction...');
						
						// Utiliser execFile pour appeler FFmpeg directement
						const { stdout, stderr } = await execFilePromise(ffmpegPath, [
							'-i', videoPath,
							'-vframes', '1',
							'-f', 'image2',
							'-update', '1',
							'-y',
							thumbnailPath
						]);
						
						console.log('[Thumbnail] FFmpeg stdout:', stdout);
						if (stderr) {
							console.log('[Thumbnail] FFmpeg stderr:', stderr.substring(0, 500));
						}
						
						if (fs.existsSync(thumbnailPath)) {
							console.log('[Thumbnail] Frame extraction completed successfully');
							resolve();
						} else {
							reject(new Error('Thumbnail file was not created'));
						}
					} catch (err) {
						console.error('[Thumbnail] FFmpeg execution error:', err);
						reject(err);
					}
				}),
				new Promise<void>((_, reject) =>
					setTimeout(() => reject(new Error('FFmpeg timeout after 30s')), 30000)
				)
			]);

			if (!fs.existsSync(thumbnailPath)) {
				console.warn('[Thumbnail] Thumbnail file not created at:', thumbnailPath);
				return generatePlaceholderThumbnail(videoFileName, userId);
			}

			console.log('[Thumbnail] File created successfully at:', thumbnailPath);

			try {
				// Lire le fichier thumbnail
				const thumbnailBuffer = fs.readFileSync(thumbnailPath);
				console.log('[Thumbnail] Thumbnail buffer size:', thumbnailBuffer.length);

				// Générer une clé R2 pour le thumbnail
				const thumbKey = `users/${cleanUserId}/thumbnails/${Date.now()}_${videoName}.jpg`;

				console.log('[Thumbnail] Uploading to R2:', thumbKey);

				// Upload le thumbnail
				const { url } = await uploadToR2(
					thumbnailBuffer,
					thumbKey,
					'image/jpeg'
				);

				// Nettoyer le fichier temporaire
				try {
					fs.unlinkSync(thumbnailPath);
				} catch (e) {
					console.warn('[Thumbnail] Could not delete temp file:', e);
				}

				console.log('[Thumbnail] ✅ Successfully generated:', url);
				return url;
			} catch (err) {
				console.error('[Thumbnail] Error uploading to R2:', err);
				try {
					fs.unlinkSync(thumbnailPath);
				} catch (e) {
					// Ignorer
				}
				return generatePlaceholderThumbnail(videoFileName, userId);
			}
		} catch (ffmpegErr) {
			console.warn('[Thumbnail] FFmpeg not available, using placeholder');
			console.warn('[Thumbnail] Error details:', ffmpegErr instanceof Error ? ffmpegErr.message : String(ffmpegErr));
			return generatePlaceholderThumbnail(videoFileName, userId);
		}
	} catch (err) {
		console.error('[Thumbnail] Unexpected error:', err);
		return generatePlaceholderThumbnail(videoFileName, userId);
	}
}
