import { z } from 'zod';
import { type APIClient, APIResponseSchema } from '../api.ts';
import {
	StorageListResponseSchema,
	StorageDeleteResponseSchema,
	StoragePresignResponseSchema,
	StorageStatsResponseSchema,
	StorageAnalyticsResponseSchema,
	type StorageListResponse,
	type StorageDeleteResponse,
	type StoragePresignResponse,
	type StorageStatsResponse,
	type StorageAnalyticsResponse,
} from './types.ts';
import { StorageObjectsResponseError } from './util.ts';

export const StorageListAPIResponseSchema = APIResponseSchema(StorageListResponseSchema);
export const StorageDeleteAPIResponseSchema = APIResponseSchema(StorageDeleteResponseSchema);
export const StoragePresignAPIResponseSchema = APIResponseSchema(StoragePresignResponseSchema);
export const StorageStatsAPIResponseSchema = APIResponseSchema(StorageStatsResponseSchema);
export const StorageAnalyticsAPIResponseSchema = APIResponseSchema(StorageAnalyticsResponseSchema);

export const ListStorageObjectsOptionsSchema = z.object({
	prefix: z.string().optional().describe('Optional object key prefix filter'),
	limit: z.number().optional().describe('Optional maximum number of objects to return'),
	offset: z.number().optional().describe('Optional pagination offset'),
});

export type ListStorageObjectsOptions = z.infer<typeof ListStorageObjectsOptionsSchema>;

/**
 * List objects in a storage bucket with optional prefix filtering and pagination.
 *
 * @param client - The API client to use for the request
 * @param bucketName - Name of the bucket to list objects from
 * @param options - Optional filtering/pagination options
 * @param extraHeaders - Optional extra headers (e.g. x-agentuity-orgid for CLI auth)
 * @returns Paginated list of objects with total count
 * @throws {StorageObjectsResponseError} If the request fails
 */
export async function listStorageObjects(
	client: APIClient,
	bucketName: string,
	options?: ListStorageObjectsOptions,
	extraHeaders?: Record<string, string>
): Promise<StorageListResponse> {
	const params = new URLSearchParams();
	if (options?.prefix) params.set('prefix', options.prefix);
	if (options?.limit !== undefined) {
		if (
			!Number.isFinite(options.limit) ||
			!Number.isInteger(options.limit) ||
			options.limit < 0
		) {
			throw new TypeError('limit must be a non-negative integer');
		}
		params.set('limit', String(options.limit));
	}
	if (options?.offset !== undefined) {
		if (
			!Number.isFinite(options.offset) ||
			!Number.isInteger(options.offset) ||
			options.offset < 0
		) {
			throw new TypeError('offset must be a non-negative integer');
		}
		params.set('offset', String(options.offset));
	}

	const query = params.toString();
	const url = `/storage/objects/${encodeURIComponent(bucketName)}${query ? `?${query}` : ''}`;

	const resp = await client.get<z.infer<typeof StorageListAPIResponseSchema>>(
		url,
		StorageListAPIResponseSchema,
		undefined,
		extraHeaders
	);

	if (resp.success) {
		return resp.data;
	}

	throw new StorageObjectsResponseError({ message: resp.message });
}

export const DeleteStorageObjectsOptionsSchema = z.object({
	key: z.string().optional().describe('Optional single object key to delete'),
	prefix: z.string().optional().describe('Optional prefix for bulk deletion'),
});

export type DeleteStorageObjectsOptions = z.infer<typeof DeleteStorageObjectsOptionsSchema>;

/**
 * Delete objects from a storage bucket.
 * Provide either `key` (single object) or `prefix` (all matching objects), but not both.
 *
 * @param client - The API client to use for the request
 * @param bucketName - Name of the bucket to delete from
 * @param options - Must include either key or prefix (mutually exclusive)
 * @param extraHeaders - Optional extra headers (e.g. x-agentuity-orgid for CLI auth)
 * @returns The count of deleted objects
 * @throws {StorageObjectsResponseError} If the request fails
 */
export async function deleteStorageObjects(
	client: APIClient,
	bucketName: string,
	options: DeleteStorageObjectsOptions,
	extraHeaders?: Record<string, string>
): Promise<StorageDeleteResponse> {
	if (!options.key && !options.prefix) {
		throw new StorageObjectsResponseError({ message: "Either 'key' or 'prefix' is required" });
	}
	if (options.key && options.prefix) {
		throw new StorageObjectsResponseError({
			message: "Provide either 'key' or 'prefix', not both",
		});
	}

	const params = new URLSearchParams();
	if (options.key) params.set('key', options.key);
	if (options.prefix) params.set('prefix', options.prefix);

	const url = `/storage/objects/${encodeURIComponent(bucketName)}?${params.toString()}`;

	const resp = await client.delete<z.infer<typeof StorageDeleteAPIResponseSchema>>(
		url,
		StorageDeleteAPIResponseSchema,
		undefined,
		extraHeaders
	);

	if (resp.success) {
		return resp.data;
	}

	throw new StorageObjectsResponseError({ message: resp.message });
}

/**
 * Generate a presigned URL for downloading or uploading an object.
 *
 * @param client - The API client to use for the request
 * @param bucketName - Name of the bucket
 * @param key - Object key
 * @param operation - 'download' (default) or 'upload'
 * @param extraHeaders - Optional extra headers (e.g. x-agentuity-orgid for CLI auth)
 * @returns Presigned URL and expiry info
 * @throws {StorageObjectsResponseError} If the request fails
 */
export async function presignStorageObject(
	client: APIClient,
	bucketName: string,
	key: string,
	operation: 'download' | 'upload' = 'download',
	extraHeaders?: Record<string, string>
): Promise<StoragePresignResponse> {
	if (!key) {
		throw new StorageObjectsResponseError({ message: "'key' must be a non-empty string" });
	}
	const params = new URLSearchParams();
	params.set('key', key);
	if (operation !== 'download') {
		params.set('operation', operation);
	}

	const url = `/storage/presign/${encodeURIComponent(bucketName)}?${params.toString()}`;

	const resp = await client.get<z.infer<typeof StoragePresignAPIResponseSchema>>(
		url,
		StoragePresignAPIResponseSchema,
		undefined,
		extraHeaders
	);

	if (resp.success) {
		return resp.data;
	}

	throw new StorageObjectsResponseError({ message: resp.message });
}

/**
 * Get aggregate stats for a storage bucket (object count, total size).
 *
 * @param client - The API client to use for the request
 * @param bucketName - Name of the bucket
 * @param extraHeaders - Optional extra headers (e.g. x-agentuity-orgid for CLI auth)
 * @returns Bucket statistics
 * @throws {StorageObjectsResponseError} If the request fails
 */
export async function getStorageStats(
	client: APIClient,
	bucketName: string,
	extraHeaders?: Record<string, string>
): Promise<StorageStatsResponse> {
	const url = `/storage/stats/${encodeURIComponent(bucketName)}`;

	const resp = await client.get<z.infer<typeof StorageStatsAPIResponseSchema>>(
		url,
		StorageStatsAPIResponseSchema,
		undefined,
		extraHeaders
	);

	if (resp.success) {
		return resp.data;
	}

	throw new StorageObjectsResponseError({ message: resp.message });
}

export const GetStorageAnalyticsOptionsSchema = z.object({
	days: z.number().optional().describe('Optional number of days of analytics history to return'),
});

export type GetStorageAnalyticsOptions = z.infer<typeof GetStorageAnalyticsOptionsSchema>;

/**
 * Get storage analytics for the org: summary totals, per-bucket breakdown, and daily snapshots.
 *
 * @param client - The API client to use for the request
 * @param options - Optional options (days for sparkline history, default 180)
 * @param extraHeaders - Optional extra headers (e.g. x-agentuity-orgid for CLI auth)
 * @returns Analytics data with summary, buckets, and daily snapshots
 * @throws {StorageObjectsResponseError} If the request fails
 */
export async function getStorageAnalytics(
	client: APIClient,
	options?: GetStorageAnalyticsOptions,
	extraHeaders?: Record<string, string>
): Promise<StorageAnalyticsResponse> {
	const params = new URLSearchParams();
	if (options?.days !== undefined) {
		if (!Number.isFinite(options.days) || !Number.isInteger(options.days) || options.days < 0) {
			throw new TypeError('days must be a non-negative integer');
		}
		params.set('days', String(options.days));
	}

	const query = params.toString();
	const url = `/storage/analytics${query ? `?${query}` : ''}`;

	const resp = await client.get<z.infer<typeof StorageAnalyticsAPIResponseSchema>>(
		url,
		StorageAnalyticsAPIResponseSchema,
		undefined,
		extraHeaders
	);

	if (resp.success) {
		return resp.data;
	}

	throw new StorageObjectsResponseError({ message: resp.message });
}
