import axios, { AxiosRequestConfig, AxiosResponse, Method, ResponseType } from "axios";
import { SET_ERROR } from "./../containers/App/constants";

export interface IServiceConfig {
	host: string | undefined;
	endpoint: string | string[];
	method: Method;
	headers: { "Content-type": string; accept: string; site?: number | null; lang?: number };
	hasToken: boolean;
	dynamicUrl?: string;
	responseType?: ResponseType;
	onUploadProgress?: any;
}

const getToken = (): Record<string, unknown> | null => {
	const app = localStorage.getItem("persist:app");
	if (!app) return null;

	let { token } = JSON.parse(app);
	token = JSON.parse(token);
	const authorization = { Authorization: `bearer ${token}` };

	return authorization;
};

const getLang = (): Record<string, unknown> => {
	const lang = JSON.parse(localStorage.getItem("langID") || "{}");

	return { lang };
};

const getSite = (): Record<string, unknown> => {
	const site = JSON.parse(localStorage.getItem("siteID") || "{}");

	return { site };
};

const getHeaders = (headers: Record<string, unknown>, hasToken: boolean) => {
	// Check if we're in preview mode and add preview share headers
	const previewPageId = sessionStorage.getItem("previewPageId");
	const previewEntity = sessionStorage.getItem("previewEntity");
	const previewHeaders =
		previewPageId && previewEntity
			? {
					"x-preview-share-page-id": previewPageId,
					"x-preview-share-entity": previewEntity,
				}
			: {};

	return hasToken
		? {
				...headers,
				...getToken(),
				...getLang(),
				...getSite(),
				...previewHeaders,
			}
		: {
				...headers,
				...getLang(),
				...getSite(),
				...previewHeaders,
			};
};

const wrapConfig = (serviceConfig: IServiceConfig): AxiosRequestConfig => {
	const { host, endpoint, method, headers: configHeaders, hasToken, dynamicUrl, responseType } = serviceConfig;

	const headers = getHeaders(configHeaders, hasToken);

	return {
		url: dynamicUrl ? dynamicUrl : `${host}${endpoint}`,
		method,
		headers,
		responseType,
	};
};

/*class RequestCache {
  private _cacheTime = 1; // SECONDS
  private _cache: { [key: string]: { timestamp: number; value: string } } = {};

  private _getKey(key: AxiosRequestConfig): string {
    return JSON.stringify(key);
  }

  save(key: AxiosRequestConfig, value: AxiosResponse): void {
    this._cache[this._getKey(key)] = { timestamp: new Date().getTime(), value: JSON.stringify(value) };
  }

  get(key: AxiosRequestConfig): AxiosResponse | null {
    const res = this._cache[this._getKey(key)];

    if (!res || new Date(res.timestamp).getSeconds() - new Date().getSeconds() > this._cacheTime) {
      return null;
    }

    return JSON.parse(res.value);
  }
}

const requestCache = new RequestCache();*/

export const sendInitialRequest = async (
	serviceConfig: IServiceConfig,
	token: string,
	data?: Record<string, unknown>,
): Promise<AxiosResponse> => {
	const { headers: configHeaders, method, host, endpoint, dynamicUrl } = serviceConfig;

	const config = {
		url: dynamicUrl ? dynamicUrl : `${host}${endpoint}`,
		method,
		headers: {
			...configHeaders,
			Authorization: "bearer " + token,
		},
	};

	try {
		const isPost = method === "PUT" || method === "POST";
		if (isPost) {
			const postConfig = { ...config, data };
			const postResponse = await axios(postConfig);
			return postResponse;
		}
		const response = await axios(config);
		return response;
	} catch (e) {
		if (e.message !== "Network Error") {
			return (
				e.response || { status: e.status, data: { code: e.code || "unknown", message: e.message || "Platform error" } }
			);
		}
		return e.response;
	}
};

export const sendRequest = async (
	serviceConfig: IServiceConfig,
	data?: Record<string, unknown> | null | FormData,
	dataHeader?: Record<string, unknown>,
): Promise<AxiosResponse> => {
	let config = wrapConfig(serviceConfig);
	const headers = { ...config.headers, ...dataHeader };

	if (dataHeader) {
		config = { ...config, headers };
	}

	try {
		const { method } = config;
		let requestConfig = config;

		const isGet = method === "GET";
		if (!isGet) {
			requestConfig = { ...config, data };
		}

		// TODO: Try to cache only on GETs
		// let response = requestCache.get(requestConfig);

		// if (response) {
		//   return response;
		// }

		// response = await axios(requestConfig);
		// requestCache.save(requestConfig, response);

		// return response;
		return await axios(requestConfig);
	} catch (e) {
		if (e.message !== "Network Error") {
			return (
				e.response || { status: e.status, data: { code: e.code || "unknown", message: e.message || "Platform error" } }
			);
		}
		return e.response;
	}
};

export const sendUploadRequest = async (
	serviceConfig: IServiceConfig,
	data: FormData,
	setProgress?: (progress: number) => void,
): Promise<AxiosResponse> => {
	try {
		const config = wrapConfig(serviceConfig);
		let requestConfig = {
			...config,
			data,
		};

		if (setProgress) {
			requestConfig = {
				...requestConfig,
				onUploadProgress: (progressEvent: ProgressEvent) => {
					const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
					setProgress(percentCompleted);
				},
			};
		}

		return await axios(requestConfig);
	} catch (e) {
		if (e.message !== "Network Error") {
			return (
				e.response || { status: e.status, data: { code: e.code || "unknown", message: e.message || "Platform error" } }
			);
		}
		return e.response;
	}
};

function defaultErrorHandler(errorType: number) {
	// FIXME create DEFAULT ERROR
	return { type: SET_ERROR, payload: { errorType, errorText: "This is a generic error" } };
}

const isSuccessful = (status: number): boolean => status >= 200 && status < 400;

export function handleResponse(
	response: AxiosResponse,
	successHandler: (data: Record<string, unknown>) => Record<string, unknown>,
	customErrorHandler?: (status: number) => Record<string, unknown>,
): Record<string, unknown> {
	const { status, data } = response;
	const errorHandler = customErrorHandler || defaultErrorHandler;

	if (isSuccessful(status)) {
		return successHandler(data);
	} else {
		return errorHandler(status);
	}
}

export default {
	getToken,
	getHeaders,
	wrapConfig,
	sendInitialRequest,
	sendRequest,
	isSuccessful,
	handleResponse,
};
