import {
	IExecuteFunctions,
	IHttpRequestOptions,
	NodeApiError,
	NodeOperationError,
	IDataObject,
	IHttpRequestMethods,
	ICredentialDataDecryptedObject,
} from 'n8n-workflow';

interface TicketCacheEntry {
	ticket: string;
	csrfToken: string;
	expiresAt: number;
}

const ticketCache: Record<string, TicketCacheEntry> = {};

/**
 * Check if a ticket is expired
 */
function isTicketExpired(expiresAt: number): boolean {
	return Date.now() > expiresAt - 60000; // Consider expired 60 seconds before actual expiration
}

/**
 * Get authentication ticket for username/password auth
 */
async function getAuthTicket(
	this: IExecuteFunctions,
	credentials: ICredentialDataDecryptedObject,
): Promise<{ ticket: string; csrfToken: string }> {
	const cacheKey = `${credentials.host}_${credentials.port}_${credentials.username}`;

	// Check cache first
	if (ticketCache[cacheKey] && !isTicketExpired(ticketCache[cacheKey].expiresAt)) {
		return {
			ticket: ticketCache[cacheKey].ticket,
			csrfToken: ticketCache[cacheKey].csrfToken,
		};
	}

	// Get new ticket
	const protocol = credentials.skipSslVerify ? 'http' : 'https';
	const baseUrl = `${protocol}://${credentials.host}:${credentials.port}`;

	try {
		const response = await this.helpers.request({
			method: 'POST',
			uri: `${baseUrl}/api2/json/access/ticket`,
			body: {
				username: credentials.username,
				password: credentials.password,
			},
			json: true,
			rejectUnauthorized: !credentials.skipSslVerify,
			timeout: credentials.timeout || 30000,
		});

		if (!response?.data?.ticket || !response?.data?.CSRFPreventionToken) {
			throw new Error('Invalid authentication response from ProxMox');
		}

		// Cache ticket (ProxMox tickets typically expire after 2 hours)
		const expiresAt = Date.now() + 2 * 60 * 60 * 1000; // 2 hours
		ticketCache[cacheKey] = {
			ticket: response.data.ticket,
			csrfToken: response.data.CSRFPreventionToken,
			expiresAt,
		};

		return {
			ticket: response.data.ticket,
			csrfToken: response.data.CSRFPreventionToken,
		};
	} catch (error) {
		throw new NodeOperationError(this.getNode(), `ProxMox authentication failed: ${error.message}`);
	}
}

/**
 * Make an API request to ProxMox VE
 */
export async function apiRequest(
	this: IExecuteFunctions,
	method: IHttpRequestMethods,
	endpoint: string,
	body: IDataObject = {},
	qs: IDataObject = {},
): Promise<any> {
	const credentials = await this.getCredentials('proxMoxApi');

	if (!credentials) {
		throw new NodeOperationError(this.getNode(), 'No credentials provided');
	}

	const protocol = credentials.skipSslVerify ? 'http' : 'https';
	const baseUrl = `${protocol}://${credentials.host}:${credentials.port}`;
	const fullUrl = `${baseUrl}/api2/json${endpoint}`;

	let headers: IDataObject = {
		'Content-Type': 'application/json',
	};

	// Handle authentication
	if (credentials.authMethod === 'apiToken') {
		// API Token authentication - ProxMox expects 'PVEAPIToken=TOKEN' format
		const tokenValue = credentials.tokenId as string;
		// Check if token already has PVEAPIToken prefix
		if (tokenValue.startsWith('PVEAPIToken=')) {
			headers.Authorization = tokenValue;
		} else {
			headers.Authorization = `PVEAPIToken=${tokenValue}`;
		}
	} else {
		// Username/Password with ticket authentication
		const auth = await getAuthTicket.call(this, credentials);
		headers.Cookie = `PVEAuthCookie=${auth.ticket}`;

		// Add CSRF token for write operations
		if (method !== 'GET') {
			headers.CSRFPreventionToken = auth.csrfToken;
		}
	}

	const requestOptions: IHttpRequestOptions = {
		method,
		url: fullUrl,
		headers,
		body: Object.keys(body).length > 0 ? body : undefined,
		qs: Object.keys(qs).length > 0 ? qs : undefined,
		json: true,
		rejectUnauthorized: !credentials.skipSslVerify,
		timeout: (credentials.timeout as number) || 30000,
		resolveWithFullResponse: true,
	};

	try {
		const response = await this.helpers.httpRequest(requestOptions);

		// ProxMox API returns data in response.data
		if (response.body?.data !== undefined) {
			return response.body.data;
		}

		return response.body || response;
	} catch (error) {
		if (error.response) {
			let message = 'ProxMox API error';
			const errorBody = error.error || error.response?.body;

			if (errorBody) {
				if (typeof errorBody === 'object') {
					if (errorBody.errors) {
						// ProxMox validation errors
						const errors = Array.isArray(errorBody.errors)
							? errorBody.errors.join(', ')
							: JSON.stringify(errorBody.errors);
						message = `Validation error: ${errors}`;
					} else if (errorBody.message) {
						message = errorBody.message;
					} else if (errorBody.reason) {
						message = errorBody.reason;
					}
				} else if (typeof errorBody === 'string') {
					message = errorBody;
				}
			}

			// Handle specific HTTP status codes
			if (error.statusCode === 401) {
				// Clear cached ticket if authentication fails
				const cacheKey = `${credentials.host}_${credentials.port}_${credentials.username}`;
				delete ticketCache[cacheKey];
				message = 'Authentication failed. Please check your credentials.';
			} else if (error.statusCode === 403) {
				// Permission denied - include the status text for more detail
				const statusText = error.response?.statusText || 'Permission denied';
				message = `Permission denied: ${statusText}`;
			}

			throw new NodeApiError(this.getNode(), error, {
				message: `${message} (Status: ${error.statusCode})`,
			});
		}

		throw new NodeOperationError(this.getNode(), `ProxMox API request failed: ${error.message}`);
	}
}

/**
 * Make paginated requests to ProxMox API
 */
export async function apiRequestAllItems(
	this: IExecuteFunctions,
	method: IHttpRequestMethods,
	endpoint: string,
	body: IDataObject = {},
	qs: IDataObject = {},
): Promise<any[]> {
	// ProxMox API doesn't support standard pagination, just make a single request
	const response = await apiRequest.call(this, method, endpoint, body, qs);

	if (Array.isArray(response)) {
		return response;
	} else {
		// Single item response
		return [response];
	}
}
