import { IExecuteFunctions, IDataObject } from 'n8n-workflow';
import { apiRequest, apiRequestAllItems } from '../helpers/apiRequest';
import { handleProxMoxError, validateParameters } from '../helpers/errorHandler';
import { logger } from '../helpers/logger';

export async function executeContainerOperation(
	this: IExecuteFunctions,
	operation: string,
	i: number,
): Promise<IDataObject | IDataObject[]> {
	try {
		logger.operation('container', operation, 'start', { operation });

		let response: IDataObject | IDataObject[];

		switch (operation) {
			case 'create':
				response = await createContainer.call(this, i);
				break;

			case 'delete':
				response = await deleteContainer.call(this, i);
				break;

			case 'get':
				response = await getContainer.call(this, i);
				break;

			case 'getAll':
				response = await getAllContainers.call(this, i);
				break;

			case 'start':
				response = await startContainer.call(this, i);
				break;

			case 'stop':
				response = await stopContainer.call(this, i);
				break;

			case 'shutdown':
				response = await shutdownContainer.call(this, i);
				break;

			case 'reboot':
				response = await rebootContainer.call(this, i);
				break;

			case 'suspend':
				response = await suspendContainer.call(this, i);
				break;

			case 'resume':
				response = await resumeContainer.call(this, i);
				break;

			case 'clone':
				response = await cloneContainer.call(this, i);
				break;

			case 'migrate':
				response = await migrateContainer.call(this, i);
				break;

			case 'snapshot':
				response = await snapshotContainer.call(this, i);
				break;

			case 'getStatus':
				response = await getContainerStatus.call(this, i);
				break;

			default:
				throw new Error(`Unknown container operation: ${operation}`);
		}

		logger.operation('container', operation, 'success', { operation });
		return response;
	} catch (error) {
		logger.operation('container', operation, 'error', { operation, error: error.message });
		return handleProxMoxError.call(this, error, operation, 'container');
	}
}

async function waitForTask(
	this: IExecuteFunctions,
	nodeName: string,
	taskId: string,
	timeout: number = 300,
): Promise<IDataObject> {
	const startTime = Date.now();
	const timeoutMs = timeout * 1000;

	logger.debug('task', `Waiting for task ${taskId} on node ${nodeName}`, { taskId, timeout });

	while (Date.now() - startTime < timeoutMs) {
		try {
			const taskStatus = await apiRequest.call(
				this,
				'GET',
				`/nodes/${nodeName}/tasks/${taskId}/status`,
			);

			if (taskStatus.status === 'stopped') {
				if (taskStatus.exitstatus === 'OK') {
					logger.debug('task', `Task ${taskId} completed successfully`);
					return taskStatus;
				} else {
					throw new Error(`Task failed: ${taskStatus.exitstatus}`);
				}
			}

			// Wait 2 seconds before checking again
			await new Promise((resolve) => setTimeout(resolve, 2000));
		} catch (error) {
			logger.error('task', `Error checking task status: ${error.message}`);
			throw error;
		}
	}

	throw new Error(`Task ${taskId} timed out after ${timeout} seconds`);
}

async function createContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const newVmId = this.getNodeParameter('newVmId', i) as number;
	const ostemplate = this.getNodeParameter('ostemplate', i) as string;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;
	const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject;

	validateParameters.call(
		this,
		{ nodeName, newVmId, ostemplate },
		['nodeName', 'newVmId', 'ostemplate'],
		'create container',
	);

	const body: IDataObject = {
		vmid: newVmId,
		ostemplate,
		...additionalOptions,
	};

	// Set defaults for container creation
	if (!body.memory) body.memory = 512;
	if (!body.swap) body.swap = 512;
	if (!body.cores) body.cores = 1;
	if (!body.storage) body.storage = 'local-lvm';
	if (!body.rootfs) body.rootfs = `${body.storage}:8`;
	if (!body.net0) body.net0 = 'name=eth0,bridge=vmbr0,firewall=1,ip=dhcp';
	if (body.unprivileged === undefined) body.unprivileged = 1;

	logger.debug('container', 'Creating container', { nodeName, vmId: newVmId, ostemplate });

	const response = await apiRequest.call(this, 'POST', `/nodes/${nodeName}/lxc`, body);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);

		// Start container if requested
		if (additionalOptions.startAfterCreate) {
			logger.debug('container', 'Starting container after creation');
			await apiRequest.call(this, 'POST', `/nodes/${nodeName}/lxc/${newVmId}/status/start`);
		}

		return { ...response, taskResult };
	}

	return response;
}

async function deleteContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'delete container');

	logger.debug('container', 'Deleting container', { nodeName, vmId });

	const response = await apiRequest.call(this, 'DELETE', `/nodes/${nodeName}/lxc/${vmId}`);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function getContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'get container');

	logger.debug('container', 'Getting container details', { nodeName, vmId });

	return await apiRequest.call(this, 'GET', `/nodes/${nodeName}/lxc/${vmId}/config`);
}

async function getAllContainers(this: IExecuteFunctions, i: number): Promise<IDataObject[]> {
	const returnAll = this.getNodeParameter('returnAll', i) as boolean;

	logger.debug('container', 'Getting all containers');
	console.log('=== getAllContainers Debug ===');
	console.log('returnAll parameter:', returnAll);

	if (returnAll) {
		console.log('Making API request for all VMs (includes containers)...');
		// Get all VMs and filter for LXC containers (type: 'lxc' in response data)
		const response = await apiRequestAllItems.call(
			this,
			'GET',
			'/cluster/resources',
			{},
			{ type: 'vm' },
		);
		console.log(
			'API response received, response type:',
			typeof response,
			'isArray:',
			Array.isArray(response),
		);
		console.log(
			'Sample raw response data:',
			JSON.stringify(Array.isArray(response) ? response.slice(0, 3) : response, null, 2),
		);

		// The ProxMox API should return an array directly for apiRequestAllItems
		let allResources: any[];
		if (Array.isArray(response)) {
			allResources = response;
			console.log('Response is direct array with', allResources.length, 'resources');
		} else if (response && response.data && Array.isArray(response.data)) {
			allResources = response.data;
			console.log('Found data array with', allResources.length, 'resources');
		} else {
			allResources = [response];
			console.log('Response is single object, converting to array');
		}

		// Filter for LXC containers
		const containers = allResources.filter((resource: any) => {
			console.log(
				'Checking resource:',
				JSON.stringify({ id: resource.id, type: resource.type, vmid: resource.vmid }, null, 2),
			);
			return resource.type === 'lxc';
		});
		console.log('Filtered containers count:', containers.length);
		console.log('Filtered containers:', JSON.stringify(containers.slice(0, 2), null, 2));
		return containers;
	} else {
		const limit = this.getNodeParameter('limit', i) as number;
		console.log('Making limited API request for VMs (includes containers), limit:', limit);

		// Get all VMs and filter for LXC containers
		const response = await apiRequest.call(this, 'GET', '/cluster/resources', {}, { type: 'vm' });
		console.log('Limited API response type:', typeof response, 'isArray:', Array.isArray(response));
		console.log(
			'Limited API response received:',
			JSON.stringify(Array.isArray(response) ? response.slice(0, 3) : response, null, 2),
		);

		// The ProxMox API wraps the data in a 'data' property
		let allResources: any[];
		if (response && response.data && Array.isArray(response.data)) {
			allResources = response.data;
			console.log('Found data array with', allResources.length, 'resources');
		} else if (Array.isArray(response)) {
			allResources = response;
			console.log('Response is direct array with', allResources.length, 'resources');
		} else {
			allResources = [response];
			console.log('Response is single object, converting to array');
		}

		// Filter for LXC containers and then apply limit
		const containers = allResources.filter((resource: any) => {
			console.log(
				'Checking resource:',
				JSON.stringify({ id: resource.id, type: resource.type, vmid: resource.vmid }, null, 2),
			);
			return resource.type === 'lxc';
		});
		console.log('Filtered containers count before limit:', containers.length);
		return containers.slice(0, limit);
	}
}

async function startContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'start container');

	logger.debug('container', 'Starting container', { nodeName, vmId });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/status/start`,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function stopContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;
	const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'stop container');

	const body: IDataObject = {};
	if (additionalOptions.force) body.force = 1;
	if (additionalOptions.skipLock) body.skiplock = 1;

	logger.debug('container', 'Stopping container', { nodeName, vmId, options: additionalOptions });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/status/stop`,
		body,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function shutdownContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;
	const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'shutdown container');

	const body: IDataObject = {};
	if (additionalOptions.force) body.forceStop = 1;
	if (additionalOptions.skipLock) body.skiplock = 1;

	logger.debug('container', 'Shutting down container', {
		nodeName,
		vmId,
		options: additionalOptions,
	});

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/status/shutdown`,
		body,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function rebootContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'reboot container');

	logger.debug('container', 'Rebooting container', { nodeName, vmId });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/status/reboot`,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function suspendContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'suspend container');

	logger.debug('container', 'Suspending container', { nodeName, vmId });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/status/suspend`,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function resumeContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'resume container');

	logger.debug('container', 'Resuming container', { nodeName, vmId });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/status/resume`,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function cloneContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const newVmId = this.getNodeParameter('newVmId', i) as number;
	const cloneName = this.getNodeParameter('cloneName', i, '') as string;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;
	const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject;

	validateParameters.call(
		this,
		{ nodeName, vmId, newVmId },
		['nodeName', 'vmId', 'newVmId'],
		'clone container',
	);

	const body: IDataObject = {
		newid: newVmId,
		...additionalOptions,
	};

	if (cloneName) {
		body.hostname = cloneName;
	}

	logger.debug('container', 'Cloning container', { nodeName, vmId, newVmId, cloneName });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/clone`,
		body,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function migrateContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const targetNode = this.getNodeParameter('targetNode', i) as string;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;
	const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject;

	validateParameters.call(
		this,
		{ nodeName, vmId, targetNode },
		['nodeName', 'vmId', 'targetNode'],
		'migrate container',
	);

	const body: IDataObject = {
		target: targetNode,
		...additionalOptions,
	};

	// Set default migration options
	if (additionalOptions.online !== false) {
		body.online = 1;
	}
	if (additionalOptions.restart) {
		body.restart = 1;
	}

	logger.debug('container', 'Migrating container', {
		nodeName,
		vmId,
		targetNode,
		options: additionalOptions,
	});

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/migrate`,
		body,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function snapshotContainer(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;
	const snapshotName = this.getNodeParameter('snapshotName', i) as string;
	const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean;
	const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number;

	validateParameters.call(
		this,
		{ nodeName, vmId, snapshotName },
		['nodeName', 'vmId', 'snapshotName'],
		'snapshot container',
	);

	const body: IDataObject = {
		snapname: snapshotName,
	};

	logger.debug('container', 'Creating container snapshot', { nodeName, vmId, snapshotName });

	const response = await apiRequest.call(
		this,
		'POST',
		`/nodes/${nodeName}/lxc/${vmId}/snapshot`,
		body,
	);

	if (waitForTask && response.data) {
		const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout);
		return { ...response, taskResult };
	}

	return response;
}

async function getContainerStatus(this: IExecuteFunctions, i: number): Promise<IDataObject> {
	const nodeName = this.getNodeParameter('nodeName', i) as string;
	const vmId = this.getNodeParameter('vmId', i) as number;

	validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'get container status');

	logger.debug('container', 'Getting container status', { nodeName, vmId });

	return await apiRequest.call(this, 'GET', `/nodes/${nodeName}/lxc/${vmId}/status/current`);
}
