import { ITradeIdea } from 'contract/interfaces'
import serialize from 'serialize-javascript'
import { IBasicNFTInflatable, MyIdeaKeys } from '../../interfaces'

// in this case when the idea is closed we don't need cache then we update the expiration time to 100 years :)
export const skipExpirationInSeconds = 60 * 60 * 24 * 365 * 100

export const randomNumberBetween = (minimum: number, limit: number): number => {
	return Math.floor(Math.random() * (limit - minimum) + minimum)
}

export const rest = async (delay: number) => {
	await new Promise((resolve) => setTimeout(resolve, delay))
}

export const wait = (time = 1000): Promise<any> => {
	return new Promise<void>((resolve, reject) => {
		try {
			const interval = setInterval(() => {
				clearInterval(interval)
				resolve()
			}, time)
		} catch (err) {
			reject()
		}
	})
}

export const generateUUID = () => {
	let d = new Date().getTime()
	let d2 =
		(typeof performance !== 'undefined' &&
			performance.now &&
			performance.now() * 1000) ||
		0
	return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
		/[xy]/g,
		function (c) {
			let r = Math.random() * 16
			if (d > 0) {
				r = (d + r) % 16 | 0
				d = Math.floor(d / 16)
			} else {
				r = (d2 + r) % 16 | 0
				d2 = Math.floor(d2 / 16)
			}
			return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
		},
	)
}

export const isUrl = (url: string) => {
	return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
		url,
	)
}

export const isNullOrUndefined = (value: any): boolean => {
	const checkValue = ['undefined', 'null', undefined, null]?.includes(value)
		? undefined
		: value
	const check = checkValue === undefined || null ? true : false
	return check
}

export const isNullOrWhiteSpace = (value: any): boolean => {
	return isNullOrUndefined(value) || value?.trim()?.length === 0
}

export const getBoolean = (value: any, defaultValue = false): boolean => {
	const data: any = {
		true: true,
		false: false,
		1: true,
		0: false,
		undefined: false,
		null: false,
	}

	const response = data[value] || defaultValue

	return response
}

export const loop = (
	next: () => Promise<void>,
	validator: () => Promise<boolean>,
	settings?: {
		loopTimeInMs: number
		limitTimeSecond: number
	},
	errorCallback?: (error: string) => Promise<void>,
) =>
	new Promise<void>(async (resolve, reject) => {
		try {
			// default settings
			if (!settings?.loopTimeInMs || settings?.loopTimeInMs <= 0) {
				//@ts-ignore
				settings.loopTimeInMs = 5000
			}

			if (!settings?.limitTimeSecond || settings?.limitTimeSecond <= 0) {
				//@ts-ignore
				settings.limitTimeSecond = 60
			}

			const loopTimeInMs: any = settings?.loopTimeInMs
			const limitTimeSecond: any = settings?.limitTimeSecond

			//@ts-ignore
			const loopTimeInMsToSecond = Math.floor((loopTimeInMs / 1000) % 60)

			// check
			//@ts-ignore
			if (loopTimeInMsToSecond >= limitTimeSecond) {
				throw new Error('The loop can not be greater than limit.')
			}

			const startDate = new Date()

			let interval: any = null

			interval = setInterval(async () => {
				try {
					const status = await validator()
					const endDate = new Date()
					const seconds =
						(endDate.getTime() - startDate.getTime()) / 1000

					// If we exceed the standby limit, we cut the process
					if (seconds >= limitTimeSecond && !status) {
						clearInterval(interval)

						if (errorCallback) {
							await errorCallback('Time limit exceeded')
						}

						throw new Error('Time limit exceeded')
					}

					// if validator is completed!
					if (status) {
						clearInterval(interval)
						interval = null
						await next()
						resolve()
					}
				} catch (err) {
					reject(err)
				}
			}, loopTimeInMs)
		} catch (err: any) {
			if (errorCallback) {
				await errorCallback(err?.message)
			}
			reject(err)
		}
	})

export const retryFunctionHelper = async <T>(payload: {
	maxRetries: number
	retryCallback: (retryIndex: number) => Promise<any>
	notificationCallback?: (errMsg: string, retryCount: number) => Promise<any>
	rejectOnMaxRetries?: boolean
}) => {
	let retryCount = 1

	// this is the function that will be called to notify the caller of the error
	// using slack or email or whatever instead of throwing an error
	const notify = async (errMsg: string, retryCount: number) => {
		try {
			if (payload?.notificationCallback) {
				await payload.notificationCallback(errMsg, retryCount)
			}
		} catch (err: any) {
			console.log(
				'retryFunctionHelper [Error notifying]',
				`${err?.message}. Retry #${retryCount}`,
			)
		}
	}

	try {
		const { maxRetries, retryCallback } = payload

		while (retryCount <= maxRetries) {
			try {
				const data = await retryCallback(retryCount)

				if (data) {
					return data as T
				} else {
					await notify('No data returned', retryCount)
				}
			} catch (err: any) {
				await notify(err?.message || 'Unknown error', retryCount)

				// optionally reject on max retries
				if (
					payload?.rejectOnMaxRetries &&
					retryCount === payload.maxRetries
				) {
					throw new Error(err?.message || 'Unknown error')
				}
			}

			if (retryCount < maxRetries) {
				// await 5 second before retrying
				await wait(5000)
			}

			retryCount++
		}

		return null
	} catch (err: any) {
		await notify(err?.message || 'Unknown error', retryCount)

		// optionally reject on max retries
		if (payload?.rejectOnMaxRetries && retryCount === payload.maxRetries) {
			throw new Error(err?.message || 'Unknown error')
		}
	}
}

export const placeholderError = <T>(
	errorMessage: string,
	placeholderReturned?: T,
): T => {
	throw new Error(errorMessage)
	return placeholderReturned as unknown as T
}

// export const IdeaStatusByKind: {
// 	[key in CONTRACT_INTERFACES.ITradeIdeaIdeaKind]: IdeaStatus
// } = {
// 	open: 1,
// 	adjust: 2,
// 	close: 3,
// }

// export const IdeaKindByStatus: {
// 	[key in number]: CONTRACT_INTERFACES.ITradeIdeaIdeaKind
// } = {
// 	1: 'open',
// 	2: 'adjust',
// 	3: 'close',
// }

// export const kindTypeToIdeaStatus = (
// 	kindType: CONTRACT_INTERFACES.ITradeIdeaIdeaKind[],
// ): IdeaStatus[] => {
// 	return kindType.map((kind) => IdeaStatusByKind[kind])
// }

// export const IdeaStatusToKindType = (
// 	status: IdeaStatus[],
// ): CONTRACT_INTERFACES.ITradeIdeaIdeaKind[] => {
// 	return status.map((s) => IdeaKindByStatus[s])
// }

// export const getBasicNFTByNftId = (res: number) => {
// 	if(typeof res !== 'number') {
// 		throw new Error('Check contract return format here')
// 	}
// 	let result: IBasicNFT = {
// 		id: res,
// 		strategyReference: res[1],
// 		status: res[2].toNumber(),
// 		isPublic: res[3],
// 	}
// 	return result
// }

export const getBasicNFTInflatableByMyIdeaKeys = (idKeys: MyIdeaKeys) => {
	let result: IBasicNFTInflatable = {
		id: idKeys.nftId,
		strategyReference: idKeys.strategyKey,
		strategyUniqueReference: idKeys.strategyUniqueKey,
	}
	return result
}

// export const getBasicNFTsByNftIds = (data: number[]) => {
// 	let result: IBasicNFT[] = data.map((res: number) => {
// 		return getBasicNFTByNftId(res) as IBasicNFT
// 	})
// 	return result
// }

export const getBasicNFTsInflatableByMyIdeaKeys = (data: MyIdeaKeys[]) => {
	let result: IBasicNFTInflatable[] = data.map((idKeys: MyIdeaKeys) => {
		return getBasicNFTInflatableByMyIdeaKeys(idKeys)
	})
	return result
}

export const serializeDataTool = (obj: any): string => {
	return serialize(obj)
}
export const deserializeDataTool = (serializedJavascript: string): any => {
	return eval('(' + serializedJavascript + ')')
}

export const isDecryptableIdea = (
	idea: ITradeIdea,
	myWallet: string,
): boolean => {
	if (typeof idea.idea === 'string') {
		if (idea.creator.walletAddress === myWallet) {
			return true
		}
		if (idea.access !== undefined) {
			if (idea.access.wallets.includes(myWallet)) {
				return true
			}
		}
	}
	return false
}

export const copyObj = <T>(obj: T): T => {
	return deserializeDataTool(serializeDataTool(obj)) as T
}

/*

export const floodProtectiveWrapper = async <T>(
	_f: () => Promise<T>,
	_fName: string,
	times: number = 10,
): Promise<T> => {
	let attempts = 0
	let value: T | 'null' | undefined = 'null'
	const randomBetweenOneAnd20 = Math.floor(Math.random() * 20) + 1
	await new Promise((resolve) => setTimeout(resolve, randomBetweenOneAnd20))
	while (value === 'null') {
		try {
			value = await _f()
		} catch (err) {
			if (attempts >= times) {
				const errorParsed =
					(err as unknown as any).message || JSON.stringify(err)
				console.error(errorParsed)
				throw new Error(
					`Error in ${_fName} ` + times + ` times as logged.`,
				)
			}
			const randomBetweenOneAnd20 = Math.floor(Math.random() * 20) + 1
			await new Promise((resolve) =>
				setTimeout(resolve, randomBetweenOneAnd20 * 10),
			)
		}
		attempts++
	}
	return value
}
*/
