import axios from 'axios';

import type { key_val } from '$lib/stores/ae_stores';

export let temp_get_blob_percent_completed = 0;
// export let get_blob_percent_completed = readable(temp_get_blob_percent_completed);
export const get_blob_percent_completed = temp_get_blob_percent_completed;

export let temp_get_object_percent_completed = 0;
// export let get_object_percent_completed = readable(temp_get_object_percent_completed);
export const get_object_percent_completed = temp_get_object_percent_completed;

// Updated 2024-05-23
export const get_object = async function get_object({
	api_cfg = null,
	endpoint = '',
	headers = {},
	params = {},
	data = {},
	timeout = 60000,
	return_meta = false,
	return_blob = false,
	filename = '',
	auto_download = false,
	as_list = false,
	// The task_id value should be a random string that is unique to the task. This is used to identify the task in the message event.
	task_id = crypto.randomUUID(),
	log_lvl = 0
}: {
	api_cfg: any;
	endpoint: string;
	headers?: any;
	params?: any;
	data?: any;
	timeout?: number;
	return_meta?: boolean;
	return_blob?: boolean;
	filename?: null | string;
	auto_download?: boolean;
	as_list?: boolean;
	task_id?: string;
	log_lvl?: number;
}) {
	if (log_lvl) {
		console.log(`*** get_object() *** Endpoint: ${endpoint} AE Task ID: ${task_id}`);
		console.log('Params:', params);
		if (log_lvl > 1) {
			console.log('Data:', data);
			console.log(`Base URL: ${api_cfg['base_url']}; Timeout: ${timeout}`);
			console.log('API Config:', api_cfg);
		}
		if (log_lvl > 2) {
			console.log(
				`Return Meta: ${return_meta}; Return Blob: ${return_blob}; Filename: ${filename}; Auto Download: ${auto_download}`
			);
		}
	}

	if (!api_cfg) {
		console.log('No API Config was provided. Returning false.');
		return false;
	}

	const axios_api = axios.create({
		baseURL: api_cfg['base_url'],
		timeout: timeout // in milliseconds; 60000 = 60 seconds
		/* other custom settings */
	});
	axios_api.defaults.headers = api_cfg['headers'];
	if (log_lvl) {
		console.log('axios_api.defaults.headers:', axios_api.defaults.headers);
		console.log('Additional headers:', headers);
	}

	// console.log('Clean the headers. No _underscores_!')
	const headers_cleaned: key_val = {};
	for (const prop in headers) {
		// No underscores allowed in the header parameters!
		const prop_cleaned = prop.replaceAll('_', '-');

		// The value must be a string for the header!
		if (typeof headers[prop] != 'string') {
			headers[prop] = JSON.stringify(headers[prop]);
		}

		headers_cleaned[prop_cleaned] = headers[prop];

		if (log_lvl) {
			console.log(`${prop_cleaned}: ${headers_cleaned[prop_cleaned]}`);
		}
	}
	headers = headers_cleaned;
	if (log_lvl) {
		console.log('All headers cleaned:', headers);
	}

	if (log_lvl) {
		console.log('URL params:');
	}
	for (const prop in params) {
		if (log_lvl > 1) {
			console.log(`URL param: ${prop}: ${params[prop]}`);
		}
		if (params[prop] === null) {
			params[prop] = 'null';
		}
	}

	// Handle the case where there is no Blob expected to be returned. Mainly JSON and text data.
	if (!return_blob) {
		const response_data_promise = await axios_api
			.get(endpoint, {
				headers: headers,
				params: params,
				onDownloadProgress: (progressEvent) => {
					const percent_completed = Math.round((progressEvent.loaded * 100) / progressEvent.total);
					if (log_lvl > 1) {
						console.log(
							'GET Data Progress:',
							progressEvent.progress,
							'Total:',
							progressEvent.total,
							'Loaded:',
							progressEvent.loaded,
							'Percent Completed',
							percent_completed
						);
					}

					temp_get_object_percent_completed = percent_completed;

					// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
					try {
						// Check if window is defined. This is to prevent errors in SvelteKit.
						if (typeof window !== 'undefined') {
							window.postMessage(
								{
									type: 'api_download_data',
									status: 'downloading',
									task_id: task_id,
									endpoint: endpoint,
									filename: filename,
									size_total: progressEvent.total,
									size_loaded: progressEvent.loaded,
									percent_completed: percent_completed
								},
								'*'
							);
						}
					} catch (error) {
						console.log('Error posting message to window:', error);
					}
				}
			})
			.then(function (response) {
				if (log_lvl) {
					console.log(
						`GET Response: status=${response.status} statusText=${response.statusText} baseURL=${response.config.baseURL} url=${response.config.url} method=${response.config.method} headers=${response.config.headers} params=${JSON.stringify(response.config.params)}`
					);
				}
				if (log_lvl > 1) {
					console.log('GET Response:', response);
				}

				// Post file download message
				try {
					if (typeof window !== 'undefined') {
						window.postMessage(
							{
								type: 'api_download_data',
								status: 'complete',
								task_id: task_id,
								endpoint: endpoint,
								filename: filename,
								size_total: 0,
								size_loaded: 0,
								percent_completed: 100
							},
							'*'
						);
					}
				} catch (error) {
					console.log('Error posting message to window:', error);
				}

				if (!Array.isArray(response.data['data']) && as_list) {
					if (log_lvl) {
						console.log(
							'Data result is a dictionary/object, not an array/list. Forcing return as an array/list'
						);
					}
					const return_data = [];
					return_data.push(response.data['data']);
					return return_data;
				} else if (response.data['data']) {
					const return_data = response.data['data'];
					if (log_lvl) {
						if (Array.isArray(return_data)) {
							console.log(`Data result is an array/list. Array length: ${return_data.length}`);
						} else {
							console.log(`Data result is a dictionary/object, not an array/list.`);
						}
					}
					return return_data;
				} else {
					const return_data = response.data;
					if (log_lvl) {
						if (Array.isArray(return_data)) {
							console.log(
								`Not a standard response from Aether's API. Data result is an array/list. Array length: ${return_data.length}`
							);
						} else {
							console.log(
								`Not a standard response from Aether's API. Data result is a dictionary/object, not an array/list.`
							);
						}
					}
					return return_data;
				}
			})
			.catch(function (error: any) {
				// Handle the common and expected 404 "error" first
				if (error.response && error.response.status === 404) {
					if (log_lvl) {
						console.log('The response was a 404 not found "error". Returning null.');
					}
					if (log_lvl > 1) {
						console.log(error.response);
					}
					if (log_lvl > 2) {
						console.log(error);
					}

					// Post file download message
					try {
						if (typeof window !== 'undefined') {
							window.postMessage(
								{
									type: 'api_download_data',
									status: 'complete',
									task_id: task_id,
									endpoint: endpoint,
									filename: filename,
									size_total: 0,
									size_loaded: 0,
									percent_completed: 0
								},
								'*'
							);
						}
					} catch (error) {
						console.log('Error posting message to window:', error);
					}
					return null; // Returning null since there were no results
				}

				if (log_lvl) {
					console.log(`Base URL: ${api_cfg['base_url']} | Endpoint: ${endpoint}`);
					console.log('Error Message:', error.message); // Is this needed here or below in the in the else portion???

					if (error.response) {
						// The request was made and the server responded with a status code that falls out of the range of 2xx
						console.log('Error Response Data', error.response.data);
						console.log('Error Response Status', error.response.status);
						console.log('Error Response Headers', error.response.headers);
					} else if (error.request) {
						// The request was made but no response was received `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js
						if (log_lvl > 1) {
							console.log('Error Request', error.request);
						}
					} else {
						// Something happened in setting up the request that triggered an Error
						console.log('Error Message', error.message);
					}
				}
				if (log_lvl > 2) {
					console.log('Error:', error);
					console.log(error.config);
				}

				if (error.code === 'ECONNABORTED') {
					// Timeout Error (You can implement retry here where suitable)
					console.log('Timeout Error: ', error.message);
				}
				if (log_lvl) {
					console.log('The response was an error. Returning false.');
				}

				return false; // Returning false since something may have gone wrong. This includes timeouts. Also more in line with what the API returns.
				// return error;
			});

		if (log_lvl > 1) {
			// console.log(`Response Data: ${response_data_promise}`);
			console.log(`Response Data:`, response_data_promise);
			// console.log(response_data_promise);
		}
		if (response_data_promise) {
			// The most common and expected response.
			// console.log('Returning result. This is generally expected.');
			return response_data_promise;
		} else if (response_data_promise === null) {
			// Less common, but expected response if no results were returned.
			if (log_lvl) {
				console.log('Returning null. This is expected if no results were found. (404)');
			}
			return response_data_promise;
		} else if (response_data_promise === false) {
			// Not common, but expected response if the request to the API had an issue.
			console.log('Returning false. There may have been an issue with this request.');
			return response_data_promise;
		} else {
			// This generally should not happen. It likely means the query was bad or an API issue.
			console.log('Returning (JSON/text) unknown. This should not happen in most cases.');
			Promise.reject(new Error('fail')).then(resolved, rejected);
		}

		// Handle the case where a Blob is expected to be returned.
	} else {
		// console.log('Expecting a Blob to be returned...');

		const response_data_promise = await axios_api
			.get(endpoint, {
				params: params,
				responseType: 'blob',
				onDownloadProgress: (progressEvent) => {
					const percent_completed = Math.round((progressEvent.loaded * 100) / progressEvent.total);
					console.log(
						'GET Blob Progress:',
						progressEvent.progress,
						'Total:',
						progressEvent.total,
						'Loaded:',
						progressEvent.loaded,
						'Percent Completed',
						percent_completed
					);

					temp_get_blob_percent_completed = percent_completed;

					// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
					try {
						if (typeof window !== 'undefined') {
							window.postMessage(
								{
									type: 'api_download_blob',
									status: 'downloading',
									task_id: task_id,
									endpoint: endpoint,
									filename: filename,
									size_total: progressEvent.total,
									size_loaded: progressEvent.loaded,
									percent_completed: percent_completed
								},
								'*'
							);
						}
					} catch (error) {
						console.log('Error posting message to window:', error);
					}
				}
			})
			.then(function (response) {
				if (log_lvl) {
					console.log(
						`GET (blob) Response: status=${response.status} statusText=${response.statusText} baseURL=${response.config.baseURL} url=${response.config.url} method=${response.config.method} headers=${response.config.headers} params=${response.config.params}`
					);
				}
				if (log_lvl > 1) {
					console.log('GET (blob) Response:', response);
				}

				const { data, headers } = response;

				// Careful if this download filename needs to be changed to a different file extension. The browser/client may not know how to handle it.
				if (filename) {
				} else if (headers['content-disposition']) {
					filename = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1');
				} else {
					filename = 'unknown_file.ext';
				}

				// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
				try {
					if (typeof window !== 'undefined') {
						window.postMessage(
							{
								type: 'api_download_blob',
								status: 'complete',
								task_id: task_id,
								endpoint: endpoint,
								filename: filename,
								size_total: 0,
								size_loaded: 0,
								percent_completed: 100
							},
							'*'
						);
					}
				} catch (error) {
					console.log('Error posting message to window:', error);
				}

				if (auto_download) {
					if (log_lvl) {
						console.log(`Auto Download: ${filename}`);
					}
					const url = window.URL.createObjectURL(new Blob([response.data]));
					const link = document.createElement('a');
					link.href = url;
					link.setAttribute('download', filename);
					document.body.appendChild(link);
					link.click();
					return true;
				} else {
					return response;
				}
			})
			.catch(function (error: any) {
				// Handle the common and expected 404 "error" first
				if (error.response && error.response.status === 404) {
					if (log_lvl) {
						console.log('The response was a 404 not found "error". Returning null.');
					}
					if (log_lvl > 1) {
						console.log(error.response);
					}
					if (log_lvl > 2) {
						console.log(error);
					}

					// Post file download message
					try {
						if (typeof window !== 'undefined') {
							window.postMessage(
								{
									type: 'api_download_blob',
									status: 'complete',
									task_id: task_id,
									endpoint: endpoint,
									filename: filename,
									size_total: 0,
									size_loaded: 0,
									percent_completed: 0
								},
								'*'
							);
						}
					} catch (error) {
						console.log('Error posting message to window:', error);
					}
					return null; // Returning null since there were no results
				}

				if (log_lvl) {
					console.log(`Base URL: ${api_cfg['base_url']} | Endpoint: ${endpoint}`);
					console.log('Error Message:', error.message); // Is this needed here or below in the in the else portion???

					if (error.response) {
						// The request was made and the server responded with a status code that falls out of the range of 2xx
						console.log('Error Response Data', error.response.data);
						console.log('Error Response Status', error.response.status);
						console.log('Error Response Headers', error.response.headers);
					} else if (error.request) {
						// The request was made but no response was received `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js
						if (log_lvl > 1) {
							console.log('Error Request', error.request);
						}
					} else {
						// Something happened in setting up the request that triggered an Error
						console.log('Error Message', error.message);
					}
				}

				if (error.code === 'ECONNABORTED') {
					// Timeout Error (You can implement retry here where suitable)
					console.log('Timeout Error: ', error.message);
				}

				if (log_lvl) {
					console.log('The response was an error. Returning false.');
				}

				return false; // Returning false since something may have gone wrong. This includes timeouts. Also more in line with what the API returns.
				// return error;
			});

		if (response_data_promise) {
			// The most common and expected response.
			// console.log('Returning result. This is generally expected.');
			// let test_blob = new Blob([response_data_promise.data]);
			// console.log(test_blob);
			// return test_blob;
			// console.log(response_data_promise.blob());
			return response_data_promise;
		} else if (response_data_promise === null) {
			// Less common, but expected response if no results were returned.
			if (log_lvl) {
				console.log('Returning null. This is expected if no results were found. (404)');
			}
			return response_data_promise;
		} else if (response_data_promise === false) {
			// Not common, but expected response if the request to the API had an issue.
			console.log('Returning false. There may have been an issue with this request.');
			return response_data_promise;
		} else {
			// This generally should not happen. It likely means the query was bad or an API issue.
			console.log('Returning (blob) unknown. This should not happen in most cases.');
			Promise.reject(new Error('fail')).then(resolved, rejected);
		}
	}
};

function resolved(result: any) {
	console.log('Resolved');
}

function rejected(result: any) {
	console.error(result);
}
