import {Prisma} from '@prisma/client'
import bcrypt from 'bcryptjs'
import {type ClassValue, clsx} from 'clsx'
import {getLocale} from 'next-intl/server'
import slugify from 'slugify'
import {twMerge} from 'tailwind-merge'

import {i18nDefaultLocale} from '@/i18n-config'

/**
 * Just output dump using pretty output
 *
 * @param variable
 */
export function dump(variable: any): [string, string] {
	return [
		(new Error().stack?.split('\n')[2]?.trim().split(' ')[1] as string) + ' ',
		JSON.stringify(variable, null, 2)
	]
}

export const toPrice = (price: any) => parseFloat(price).toFixed(2)

/**
 * Create fallback avatar for showing during login process or in case if empty
 *
 * @param name
 */
export const avatarFallback = (name: string): string =>
	name
		? name
				.split(' ')
				.slice(0, 2)
				.map(w => w[0])
				.join('')
				.toUpperCase()
		: 'U'

export function cn(...inputs: ClassValue[]) {
	return twMerge(clsx(inputs))
}

/**
 * Slugify the string based on locale using kebab case
 *
 * @param string
 * @param locale
 */
export function slug(string: string, locale: string = 'uk'): string {
	return slugify(string, {
		lower: true,
		strict: true,
		locale
	})
}

export const formatNumberWithDecimal = (num: number): string => {
	const [int, decimal] = num.toString().split('.')
	return decimal ? `$(num).${decimal.padEnd(2, '0')}` : int
}

export const toInt = (input: unknown): number | null => {
	let output = NaN

	if (typeof input === 'string' || typeof input === 'number') {
		output = parseInt(input as string, 10)
	}

	if (isNaN(output)) {
		console.log('input invalid type: ', typeof input)
		return null
	}

	return output
}

export const hashPassword = async (value: string): Promise<string> => {
	return bcrypt.hash(value, 10)
}

export const verifyHashedPassword = async (
	value: string,
	hashPassword: string
): Promise<boolean> => {
	return await bcrypt.compare(value, hashPassword)
}

/**
 * Remove empty properties from the object
 *
 * @param queryParams
 */
export const cleanEmptyParams = (queryParams: any) => {
	return Object.keys(queryParams)
		.filter(key => queryParams[key] != '')
		.reduce((acc, key) => Object.assign(acc, {[key]: queryParams[key]}), {})
}

/**
 * Replace null value with empty string for the object and array of them as well
 *
 * @param data
 */
export const toEmptyParams = (data: object | object[]) => {
	const result = []
	const isArray = Array.isArray(data)
	const toProcess: object[] = isArray ? data : [data]

	for (let x in toProcess) {
		const norm = Object.keys(toProcess[x]).reduce(
			(obj: {}, key: string) =>
				Object.assign(obj, {
					// @ts-ignore
					[key]: toProcess[x][key] === null ? '' : toProcess[x][key]
				}),
			{}
		)

		if (!isArray) return norm
		result.push(norm)
	}

	return result
}

export const thisLocales = async (locales: any) => {
	const loc = await getLocale()
	return locales.filter((locale: any) => locale.lang === loc)
}

export const thisLocale = async (locales: any) => {
	const loc = await getLocale()
	return locales.find((locale: any) => locale.lang === loc)
}

export const dbErrorHandling = (e: unknown, message?: string | null) => {
	if (e instanceof Prisma.PrismaClientKnownRequestError) {
		return {error: `${e.code}: ${e.message}`}
	} else if ((e as {code: string}).code === 'ETIMEDOUT') {
		return {
			error:
				'Неможливо підключитися до бази даних. Будь ласка, спробуйте згодом.'
		}
	} else if ((e as {code: string}).code === '503') {
		return {
			error: 'Сервіс тимчасово недоступний. Будь ласка, спробуйте згодом.'
		}
	} else {
		return {error: 'Сталася несподівана помилка. Будь ласка, спробуйте згодом.'}
	}
}

/**
 * Check if the object is empty
 *
 * @param obj
 */
export const isEmptyObj = (obj: object): boolean =>
	Object.keys(obj).length === 0
