import {
	DeleteObjectCommand,
	GetObjectCommand,
	PutObjectCommand,
	S3Client,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// S3 API abstraction — provider-agnostic (Garage, Cloudflare R2, AWS S3).

const s3 = new S3Client({
	endpoint: process.env.S3_ENDPOINT,
	region: process.env.S3_REGION ?? "us-east-1",
	credentials: {
		accessKeyId: process.env.S3_ACCESS_KEY!,
		secretAccessKey: process.env.S3_SECRET_KEY!,
	},
	forcePathStyle: true, // REQUIRED for Garage and most S3-compatible services
});

const bucket = process.env.S3_BUCKET ?? "gearbox-images";
const presignExpiry = Number.parseInt(
	process.env.S3_PRESIGN_EXPIRY ?? "3600",
	10,
);

export async function uploadImage(
	buffer: Buffer | ArrayBuffer,
	filename: string,
	contentType: string,
): Promise<void> {
	await s3.send(
		new PutObjectCommand({
			Bucket: bucket,
			Key: filename,
			Body: Buffer.from(buffer),
			ContentType: contentType,
		}),
	);
}

export async function deleteImage(filename: string): Promise<void> {
	await s3.send(
		new DeleteObjectCommand({
			Bucket: bucket,
			Key: filename,
		}),
	);
}

export async function getImageUrl(filename: string): Promise<string> {
	const command = new GetObjectCommand({
		Bucket: bucket,
		Key: filename,
	});
	return getSignedUrl(s3, command, { expiresIn: presignExpiry });
}

/**
 * Enrich a record that has an imageFilename with a presigned imageUrl.
 * Returns null imageUrl when imageFilename is null.
 */
export async function withImageUrl<T extends { imageFilename: string | null }>(
	record: T,
): Promise<T & { imageUrl: string | null }> {
	return {
		...record,
		imageUrl: record.imageFilename
			? await getImageUrl(record.imageFilename)
			: null,
	};
}

/**
 * Batch version of withImageUrl. Uses Promise.all for parallelism.
 */
export async function withImageUrls<T extends { imageFilename: string | null }>(
	records: T[],
): Promise<(T & { imageUrl: string | null })[]> {
	return Promise.all(records.map((record) => withImageUrl(record)));
}
