/**
 * Resource Manager Utility
 * Provides proper cleanup for intervals, timeouts, and other resources
 */

export interface ManagedResource {
	id: string;
	type: 'interval' | 'timeout' | 'process' | 'custom';
	resource: any;
	cleanup: () => void | Promise<void>;
	createdAt: number;
	description?: string;
}

export interface ResourceManagerOptions {
	autoCleanupOnExit?: boolean;
	maxAge?: number; // Maximum age in milliseconds before auto-cleanup
	maxResources?: number; // Maximum number of resources to track
}

export class ResourceManager {
	private static instance: ResourceManager;
	private resources = new Map<string, ManagedResource>();
	private cleanupInterval?: NodeJS.Timeout;
	private options: ResourceManagerOptions;

	private constructor(options: ResourceManagerOptions = {}) {
		this.options = {
			autoCleanupOnExit: true,
			maxAge: 5 * 60 * 1000, // 5 minutes default
			maxResources: 1000,
			...options
		};

		if (this.options.autoCleanupOnExit) {
			this.setupExitHandlers();
		}

		// Schedule periodic cleanup
		this.cleanupInterval = setInterval(() => {
			this.performPeriodicCleanup();
		}, 30000); // Every 30 seconds
	}

	public static getInstance(options?: ResourceManagerOptions): ResourceManager {
		if (!ResourceManager.instance) {
			ResourceManager.instance = new ResourceManager(options);
		}
		return ResourceManager.instance;
	}

	/**
	 * Create and register a managed interval
	 */
	public createInterval(
		callback: () => void | Promise<void>,
		delay: number,
		description?: string
	): { id: string; intervalId: NodeJS.Timeout } {
		const id = this.generateId('interval');
		
		const intervalId = setInterval(async () => {
			try {
				await callback();
			} catch (error) {
				console.warn(`Interval ${id} callback error:`, error.message);
			}
		}, delay);

		const resource: ManagedResource = {
			id,
			type: 'interval',
			resource: intervalId,
			cleanup: () => clearInterval(intervalId),
			createdAt: Date.now(),
			description
		};

		this.resources.set(id, resource);
		this.enforceResourceLimits();

		return { id, intervalId };
	}

	/**
	 * Create and register a managed timeout
	 */
	public createTimeout(
		callback: () => void | Promise<void>,
		delay: number,
		description?: string
	): { id: string; timeoutId: NodeJS.Timeout } {
		const id = this.generateId('timeout');
		
		const timeoutId = setTimeout(async () => {
			try {
				await callback();
			} catch (error) {
				console.warn(`Timeout ${id} callback error:`, error.message);
			} finally {
				// Auto-remove after execution
				this.cleanup(id);
			}
		}, delay);

		const resource: ManagedResource = {
			id,
			type: 'timeout',
			resource: timeoutId,
			cleanup: () => clearTimeout(timeoutId),
			createdAt: Date.now(),
			description
		};

		this.resources.set(id, resource);
		this.enforceResourceLimits();

		return { id, timeoutId };
	}

	/**
	 * Register a custom resource for cleanup
	 */
	public registerResource(
		type: 'process' | 'custom',
		resource: any,
		cleanup: () => void | Promise<void>,
		description?: string
	): string {
		const id = this.generateId(type);

		const managedResource: ManagedResource = {
			id,
			type,
			resource,
			cleanup,
			createdAt: Date.now(),
			description
		};

		this.resources.set(id, managedResource);
		this.enforceResourceLimits();

		return id;
	}

	/**
	 * Clean up a specific resource
	 */
	public async cleanup(id: string): Promise<boolean> {
		const resource = this.resources.get(id);
		if (!resource) {
			return false;
		}

		try {
			await resource.cleanup();
			this.resources.delete(id);
			return true;
		} catch (error) {
			console.warn(`Failed to cleanup resource ${id}:`, error.message);
			// Remove from tracking even if cleanup failed
			this.resources.delete(id);
			return false;
		}
	}

	/**
	 * Clean up all resources of a specific type
	 */
	public async cleanupByType(type: ManagedResource['type']): Promise<number> {
		const resourcesOfType = Array.from(this.resources.values())
			.filter(resource => resource.type === type);

		let cleanedUp = 0;
		for (const resource of resourcesOfType) {
			const success = await this.cleanup(resource.id);
			if (success) cleanedUp++;
		}

		return cleanedUp;
	}

	/**
	 * Clean up all resources
	 */
	public async cleanupAll(): Promise<number> {
		const resourceIds = Array.from(this.resources.keys());
		let cleanedUp = 0;

		for (const id of resourceIds) {
			const success = await this.cleanup(id);
			if (success) cleanedUp++;
		}

		// Also cleanup the periodic cleanup interval
		if (this.cleanupInterval) {
			clearInterval(this.cleanupInterval);
			this.cleanupInterval = undefined;
		}

		return cleanedUp;
	}

	/**
	 * Get statistics about managed resources
	 */
	public getStats(): {
		total: number;
		byType: Record<string, number>;
		oldestAge: number;
		averageAge: number;
	} {
		const now = Date.now();
		const resources = Array.from(this.resources.values());
		
		const byType: Record<string, number> = {};
		let totalAge = 0;
		let oldestAge = 0;

		for (const resource of resources) {
			byType[resource.type] = (byType[resource.type] || 0) + 1;
			const age = now - resource.createdAt;
			totalAge += age;
			oldestAge = Math.max(oldestAge, age);
		}

		return {
			total: resources.length,
			byType,
			oldestAge,
			averageAge: resources.length > 0 ? totalAge / resources.length : 0
		};
	}

	/**
	 * List active resources (for debugging)
	 */
	public listResources(): ManagedResource[] {
		return Array.from(this.resources.values()).map(resource => ({
			...resource,
			resource: `[${typeof resource.resource}]` // Don't expose actual resource
		}));
	}

	/**
	 * Create a scoped resource manager for a specific context
	 */
	public createScope(description?: string): ResourceScope {
		return new ResourceScope(this, description);
	}

	private generateId(type: string): string {
		const timestamp = Date.now();
		const random = Math.random().toString(36).substring(2, 8);
		return `${type}_${timestamp}_${random}`;
	}

	private setupExitHandlers(): void {
		const cleanup = async () => {
			console.log('Cleaning up resources on exit...');
			const cleaned = await this.cleanupAll();
			console.log(`Cleaned up ${cleaned} resources`);
		};

		process.on('exit', () => {
			// Synchronous cleanup for exit
			for (const resource of this.resources.values()) {
				try {
					if (typeof resource.cleanup === 'function') {
						resource.cleanup();
					}
				} catch (error) {
					// Ignore errors during exit cleanup
				}
			}
		});

		process.on('SIGTERM', async () => {
			await cleanup();
			process.exit(0);
		});

		process.on('SIGINT', async () => {
			await cleanup();
			process.exit(0);
		});

		process.on('uncaughtException', async (error) => {
			console.error('Uncaught exception:', error);
			await cleanup();
			process.exit(1);
		});

		process.on('unhandledRejection', async (reason) => {
			console.error('Unhandled rejection:', reason);
			await cleanup();
			process.exit(1);
		});
	}

	private performPeriodicCleanup(): void {
		const now = Date.now();
		const toCleanup: string[] = [];

		// Find resources that are too old
		for (const [id, resource] of this.resources.entries()) {
			const age = now - resource.createdAt;
			if (this.options.maxAge && age > this.options.maxAge) {
				toCleanup.push(id);
			}
		}

		// Clean up old resources
		for (const id of toCleanup) {
			this.cleanup(id).catch(error => {
				console.warn(`Failed to cleanup old resource ${id}:`, error.message);
			});
		}
	}

	private enforceResourceLimits(): void {
		if (!this.options.maxResources) return;

		const resourceCount = this.resources.size;
		if (resourceCount <= this.options.maxResources) return;

		// Remove oldest resources to stay within limit
		const resources = Array.from(this.resources.entries())
			.sort((a, b) => a[1].createdAt - b[1].createdAt);

		const toRemove = resources.slice(0, resourceCount - this.options.maxResources);
		
		for (const [id] of toRemove) {
			this.cleanup(id).catch(error => {
				console.warn(`Failed to cleanup resource during limit enforcement ${id}:`, error.message);
			});
		}
	}
}

/**
 * Scoped resource manager for automatic cleanup of resources within a specific context
 */
export class ResourceScope {
	private resourceIds: string[] = [];
	private manager: ResourceManager;
	private description?: string;

	constructor(manager: ResourceManager, description?: string) {
		this.manager = manager;
		this.description = description;
	}

	/**
	 * Create an interval within this scope
	 */
	public createInterval(
		callback: () => void | Promise<void>,
		delay: number,
		description?: string
	): { id: string; intervalId: NodeJS.Timeout } {
		const result = this.manager.createInterval(callback, delay, description);
		this.resourceIds.push(result.id);
		return result;
	}

	/**
	 * Create a timeout within this scope
	 */
	public createTimeout(
		callback: () => void | Promise<void>,
		delay: number,
		description?: string
	): { id: string; timeoutId: NodeJS.Timeout } {
		const result = this.manager.createTimeout(callback, delay, description);
		this.resourceIds.push(result.id);
		return result;
	}

	/**
	 * Register a resource within this scope
	 */
	public registerResource(
		type: 'process' | 'custom',
		resource: any,
		cleanup: () => void | Promise<void>,
		description?: string
	): string {
		const id = this.manager.registerResource(type, resource, cleanup, description);
		this.resourceIds.push(id);
		return id;
	}

	/**
	 * Clean up all resources in this scope
	 */
	public async cleanup(): Promise<number> {
		let cleanedUp = 0;
		
		for (const id of this.resourceIds) {
			const success = await this.manager.cleanup(id);
			if (success) cleanedUp++;
		}

		this.resourceIds = [];
		return cleanedUp;
	}

	/**
	 * Get the number of resources in this scope
	 */
	public getResourceCount(): number {
		return this.resourceIds.length;
	}
}

// Global instance for convenience
export const resourceManager = ResourceManager.getInstance();