import { Dispatch } from "redux";

import { handleRequest, isReqOk, compressImage, getNewBreadcrumb } from "@ax/helpers";
import { images, sites, folders } from "@ax/api";
import {
	IFolder,
	IFolderTree,
	IGetFolderParams,
	IGetSiteImages,
	IImage,
	IImageFolder,
	IImageForm,
	IRootState,
} from "@ax/types";
import { appActions } from "@ax/containers/App";

import {
	SET_DATA,
	SET_IS_LOADING,
	SET_IS_SAVING,
	SET_IS_UPLOADING,
	SET_UPLOAD_SUCCESS,
	SET_UPLOAD_ERROR,
	SET_DISPLAY_MODE,
	SET_SELECTED_TAB,
	SET_CURRENT_FOLDER_CONTENT,
	SET_CURRENT_FOLDER,
	SET_BREADCRUMB,
	SET_FOLDERS_TREE,
} from "./constants";
import {
	ISetData,
	ISetIsLoading,
	ISetIsSaving,
	ISetIsUploading,
	ISetUploadSuccess,
	ISetUploadError,
	ISetDisplayMode,
	ISetSelectedTab,
	ISetCurrentFolderContent,
	ISetCurrentFolder,
	ISetBreadcrumb,
	ISetFoldersTree,
} from "./interfaces";
import { IData, IIsLoading, IIsSaving } from "./reducer";

const thumbWidth = 192;
const thumbHeight = 144;
const maxSize = 50; // MB

function setData(data: IData): ISetData {
	return { type: SET_DATA, payload: { data } };
}

function setIsLoading(isLoading: IIsLoading): ISetIsLoading {
	return { type: SET_IS_LOADING, payload: { isLoading } };
}

function setIsSaving(isSaving: IIsSaving): ISetIsSaving {
	return { type: SET_IS_SAVING, payload: { isSaving } };
}

function setIsUploading(isUploading: boolean): ISetIsUploading {
	return { type: SET_IS_UPLOADING, payload: { isUploading } };
}

function setUploadSuccess(isSuccess: boolean): ISetUploadSuccess {
	return { type: SET_UPLOAD_SUCCESS, payload: { isUploading: false, isSuccess } };
}

function setUploadError(isError: boolean, errorMsg = ""): ISetUploadError {
	return { type: SET_UPLOAD_ERROR, payload: { isUploading: false, isError, errorMsg } };
}

function setCurrentFolderContent(currentFolderContent: IImageFolder | null): ISetCurrentFolderContent {
	return { type: SET_CURRENT_FOLDER_CONTENT, payload: { currentFolderContent } };
}

function setCurrentFolder(currentFolderID: number | null): ISetCurrentFolder {
	return { type: SET_CURRENT_FOLDER, payload: { currentFolderID } };
}

function setBreadcrumb(breadcrumb: IFolderTree[]): ISetBreadcrumb {
	return { type: SET_BREADCRUMB, payload: { breadcrumb } };
}

function setFoldersTree(foldersTree: IFolderTree[]): ISetFoldersTree {
	return { type: SET_FOLDERS_TREE, payload: { foldersTree } };
}

function setDisplayMode(displayMode: "grid" | "list"): ISetDisplayMode {
	return { type: SET_DISPLAY_MODE, payload: { displayMode } };
}

function setSelectedTab(selectedTab: "site" | "global"): ISetSelectedTab {
	return { type: SET_SELECTED_TAB, payload: { selectedTab } };
}

function getSiteImages(props: IGetSiteImages): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		const {
			gallery: { data },
		} = getState();
		const { site, page = 1, items, query, search, more = false } = props;
		try {
			dispatch(setIsLoading({ init: !more, more }));
			const result = await sites.getSiteImages(site, page, items, thumbWidth, thumbHeight, query, search);
			if (isReqOk(result.status)) {
				const images = [...result.data.items];
				if (more) images.unshift(...data.items);
				dispatch(
					setData({
						items: images,
						page,
						isFinished: result.data.items.length < items,
					}),
				);
				dispatch(setIsLoading({ init: false, more: false }));
				dispatch(setUploadSuccess(false));
			}
		} catch (e) {
			console.log(e);
		}
	};
}

function uploadImage(
	imageFiles: File | File[],
	site: number | string,
	setProgress?: (progress: number) => void,
	visible = true,
	folderID?: number | null,
): (dispatch: Dispatch) => Promise<IImage | null> {
	return async (dispatch) => {
		try {
			dispatch(setIsUploading(true));

			const imageFilesArr = Array.isArray(imageFiles) ? imageFiles : [imageFiles];
			const uploadPromises = imageFilesArr.map(async (imageFile: File) => {
				const imageSize = imageFile.size / (1024 * 1024);
				const image = imageSize >= maxSize ? await compressImage(imageFile, 9999, 9999, maxSize, false) : imageFile;
				const form = new FormData();
				form.append("file", image);
				site && form.append("site", JSON.stringify(site));
				visible === false && form.append("active", "false");
				folderID && form.append("folder", JSON.stringify(folderID));
				const createdImage = await images.createImage(form, setProgress);
				return { ...createdImage, image };
			});
			const results = await Promise.all(uploadPromises);
			const lastUploadedImage = results.reduce((lastResult, result) =>
				result.data.id > lastResult.data.id ? result : lastResult,
			);

			if (isReqOk(lastUploadedImage?.status)) {
				dispatch(setIsUploading(false));
				dispatch(setUploadSuccess(true));
				return { ...lastUploadedImage.data, file: lastUploadedImage.image };
			} else {
				dispatch(setUploadError(true, "Error uploading image"));
				return null;
			}
		} catch (e) {
			console.log(e);
			return null;
		}
	};
}

function uploadError(error: boolean, msg?: string): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setUploadError(error, msg));
	};
}

function updateImage(
	imageID: number,
	imageData: IImageForm,
	params: IGetFolderParams,
	add?: boolean,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
	return async (dispatch, getState) => {
		const {
			gallery: { isSaving },
		} = getState();
		dispatch(setIsSaving({ ...isSaving, save: true, saveAdd: !!add }));
		try {
			const result = await images.updateImage(imageID, imageData);
			if (isReqOk(result.status)) {
				dispatch(setData({ ...result.data, isImageSelected: false, imageSelected: null }));
				dispatch(setIsSaving({ ...isSaving, save: false, saveAdd: false }));
				await getFolderContent(params)(dispatch, getState);
				return true;
			}
		} catch (e) {
			console.log(e);
		}
		dispatch(setIsSaving({ ...isSaving, save: false, saveAdd: false }));
		return false;
	};
}

function deleteImage(
	imageID: number | number[],
	params: IGetFolderParams,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
	return async (dispatch, getState) => {
		try {
			const isBulk = Array.isArray(imageID);

			const responseActions = {
				handleSuccess: async () => {
					await getFolderContent(params)(dispatch, getState);
				},
				handleError: async (response: any) => {
					const {
						data: { message },
					} = response;

					if (isBulk) {
						await getFolderContent(params)(dispatch, getState);
					}

					const isMultiple = Array.isArray(message) && message.length > 1;
					const msg = isMultiple ? `The delete action failed due to ${message.length} errors.` : undefined;
					appActions.handleError(response, isMultiple, msg)(dispatch);
				},
			};

			const callback = async () => (isBulk ? images.deleteImageBulk(imageID) : images.deleteImage(imageID));

			return await handleRequest(callback, responseActions, [])(dispatch);
		} catch (e) {
			console.log(e);
			return false;
		}
	};
}

function resetGalleryState(): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setUploadSuccess(false));
	};
}

function getFolderContent(params: IGetFolderParams): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		try {
			const {
				gallery: { currentFolderContent },
			} = getState();

			const { folderID, loading, infinite = false } = params;
			const responseActions = {
				handleSuccess: (data: IImageFolder) => {
					const imagesData =
						infinite && currentFolderContent
							? [...currentFolderContent.images.items, ...data.images.items]
							: [...data.images.items];

					dispatch(setCurrentFolderContent({ ...data, images: { ...data.images, items: imagesData } }));
					updateBreadcrumb(folderID)(dispatch, getState);
					dispatch(setUploadSuccess(false));
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};
			const callback = async () => await images.getImagesFolderContent(params);

			const setLoading = loading ? [appActions.setIsLoading] : [];

			await handleRequest(callback, responseActions, setLoading)(dispatch);
		} catch (e) {
			console.log(e);
		}
	};
}

function getFoldersTree(siteID: number | "global"): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		try {
			const responseActions = {
				handleSuccess: (data: IFolderTree[]) => dispatch(setFoldersTree(data)),
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};
			const callback = async () => images.getFoldersTree(siteID);

			await handleRequest(callback, responseActions, [])(dispatch);
		} catch (e) {
			console.log(e);
		}
	};
}

function updateCurrentFolder(currentFolderID: number | null): (dispatch: Dispatch) => void {
	return async (dispatch) => {
		try {
			dispatch(setCurrentFolder(currentFolderID));
		} catch (e) {
			console.log("Error", e);
		}
	};
}

function updateBreadcrumb(folderID: number | null): (dispatch: Dispatch, getState: () => IRootState) => void {
	return (dispatch, getState) => {
		const {
			gallery: { foldersTree },
		} = getState();

		const newBreadcrumb = getNewBreadcrumb(foldersTree, folderID);
		dispatch(setBreadcrumb(newBreadcrumb));
	};
}

function updateDisplayMode(displayMode: "grid" | "list"): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setDisplayMode(displayMode));
	};
}

function updateTab(tab: "site" | "global"): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setSelectedTab(tab));
	};
}

function resetError(): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setUploadError(false, ""));
	};
}

function createNewFolder(
	data: {
		folderName: string;
		site: number | null;
		parentId: number | null;
	},
	params: IGetFolderParams,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
	return async (dispatch, getState) => {
		try {
			const responseActions = {
				handleSuccess: async () => {
					await getFoldersTree(params.siteID)(dispatch);
					await getFolderContent(params)(dispatch, getState);
					dispatch(setCurrentFolder(data.parentId));
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};
			const callback = async () => folders.createFolder({ ...data, type: "images" });

			return await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
		} catch (e) {
			console.log(e);
			return false;
		}
	};
}

function updateFolder(
	folderID: number,
	data: { parentId: number; folderName: string },
	params: IGetFolderParams,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
	return async (dispatch, getState) => {
		try {
			const responseActions = {
				handleSuccess: async () => {
					await getFolderContent(params)(dispatch, getState);
					await getFoldersTree(params.siteID)(dispatch);
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};
			const callback = async () => folders.updateFolder(folderID, data);

			return await handleRequest(callback, responseActions, [])(dispatch);
		} catch (e) {
			console.log(e);
			return false;
		}
	};
}

function deleteFolder(
	folder: IFolder,
	params: IGetFolderParams,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
	return async (dispatch, getState) => {
		try {
			const responseActions = {
				handleSuccess: async () => {
					await getFoldersTree(folder.site || "global")(dispatch);
					await getFolderContent(params)(dispatch, getState);
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};
			const callback = async () => folders.deleteFolder(folder.id, "images");

			return await handleRequest(callback, responseActions, [])(dispatch);
		} catch (e) {
			console.log(e);
			return false;
		}
	};
}

function moveImage(
	imageID: number | number[],
	folderID: number | "root",
	params: IGetFolderParams,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
	return async (dispatch, getState) => {
		try {
			const responseActions = {
				handleSuccess: async () => {
					await getFolderContent(params)(dispatch, getState);
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};

			const callback = async () =>
				Array.isArray(imageID) ? images.moveImageBulk(imageID, folderID) : images.moveImage(imageID, folderID);

			return await handleRequest(callback, responseActions, [])(dispatch);
		} catch (e) {
			console.log(e);
			return false;
		}
	};
}

function downloadImages(
	imageID: number | number[],
	zip: boolean,
	fileName?: string,
): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		try {
			const responseActions = {
				handleSuccess: (response: any) => {
					const url = window.URL.createObjectURL(new Blob([response]));
					const a = document.createElement("a");
					a.href = url;
					a.download = fileName ? fileName : "download.zip";
					document.body.appendChild(a);
					a.click();
					window.URL.revokeObjectURL(url);
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};

			const callback = async () => images.downloadImages(imageID, zip);

			await handleRequest(callback, responseActions, [])(dispatch);
		} catch (e) {
			console.log(e);
		}
	};
}

function replaceImage(
	imageFile: File,
	imageID: number,
	params: IGetFolderParams,
	setProgress?: (progress: number) => void,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<IImage | null> {
	return async (dispatch, getState) => {
		try {
			dispatch(setIsUploading(true));

			const {
				sites: { currentSiteInfo },
			} = getState();

			const siteID = currentSiteInfo ? currentSiteInfo.id : "global";
			const form = new FormData();
			form.append("file", imageFile);
			form.append("site", JSON.stringify(siteID));

			const result = await images.replaceImage(form, imageID, setProgress);
			if (isReqOk(result.status)) {
				dispatch(setIsUploading(false));
				dispatch(setUploadSuccess(true));
				await getFolderContent(params)(dispatch, getState);
				return result.data;
			} else {
				dispatch(setUploadError(true, "Error replacing image"));
				return null;
			}
		} catch (e) {
			console.log(e);
			return null;
		}
	};
}

export {
	setIsLoading,
	getSiteImages,
	uploadImage,
	updateImage,
	deleteImage,
	uploadError,
	resetGalleryState,
	updateDisplayMode,
	updateTab,
	updateCurrentFolder,
	updateBreadcrumb,
	getFolderContent,
	getFoldersTree,
	resetError,
	createNewFolder,
	updateFolder,
	deleteFolder,
	moveImage,
	downloadImages,
	replaceImage,
	setUploadSuccess,
};
