const BufferB = require('buffer/').Buffer
import { ethers } from 'ethers'
import { ILitProvider, LIT_ACTIONS, LogModule as log } from '../activ-v4'
import { isNullOrUndefined } from '../activ-v4/tools/chunks/helpers.tool'

// import * as fs from 'fs' // debug code
// import path from 'path' // debug code

/*
 NOTE: to use activ from backend the client need install 2 external packages:
	* npm install lit-js-sdk@1.2.57 --force
	* npm install siwe@1.1.6 --force
*/
import { AccessControlConditions, AuthSig } from '@lit-protocol/types'

let LitJsSdk: any
let Siwe: any
let backendWalletPrivateKey: string | undefined

const checks = {
	LitJsSdkCheck: () => {
		if (isNullOrUndefined(LitJsSdk)) {
			throw new Error('LitJsSdk is not initialized')
		}
	},
	SiweCheck: () => {
		if (isNullOrUndefined(Siwe)) {
			throw new Error('Siwe is not initialized')
		}
	},
}

const globalCheck = () => {
	// NOTE: automate this in a future
	checks.LitJsSdkCheck()
	checks.SiweCheck()
}

const init = async (externalPackages: {
	LitJsSdkInstance: any
	SiweInstance?: any
	backendWalletPrivateKey?: string
}) => {
	LitJsSdk = externalPackages?.LitJsSdkInstance
	Siwe = externalPackages?.SiweInstance
	backendWalletPrivateKey =
		externalPackages?.backendWalletPrivateKey || undefined
}

const getEncryptionClient = async () => {
	globalCheck()
	const client = new (LitJsSdk as any).LitNodeClientNodeJs({
		alertWhenUnauthorized: true,
		debug: false,
	})
	await client.connect()
	return client
}

const getActionsClient = async (nodes: number = 10) => {
	globalCheck()

	// Dev mark nodes
	if (LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE) {
		nodes = LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE_NODES
	}

	const client = new LitJsSdk.LitNodeClientNodeJs({
		alertWhenUnauthorized: true,
		minNodeCount: nodes,
		litNetwork: 'serrano',
		// debug: true,
		debug: LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE,
	})

	// console.log('connectin lit network')

	await client.connect()
	return client
}

// const base64StringToBlob = async (base64: string): Promise<Blob> => {
// 	const response = await fetch(base64)
// 	console.log(response)
// 	return (await response.blob()) as Blob
// }

const base64StringToBlob = (base64Data: string): Blob => {
	// console.log('base64Data:')
	// console.log(base64Data)
	const contentType = 'application/octet-stream;base64'
	const begin = 'data:' + contentType + ','
	const base64DataNoBegin = base64Data.replace(begin, '')
	// console.log('base64DataNoBegin:')
	// console.log(base64DataNoBegin)

	const sliceSize = 1024
	const byteCharacters = BufferB.from(base64DataNoBegin, 'base64').toString(
		'latin1',
	)
	const bytesLength = byteCharacters.length
	const slicesCount = Math.ceil(bytesLength / sliceSize)
	const byteArrays = new Array(slicesCount)

	for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
		const begin = sliceIndex * sliceSize
		const end = Math.min(begin + sliceSize, bytesLength)

		const bytes = new Array(end - begin)
		for (let offset = begin, i = 0; offset < end; ++i, ++offset) {
			bytes[i] = byteCharacters[offset].charCodeAt(0)
		}
		byteArrays[sliceIndex] = new Uint8Array(bytes)
	}
	const blob = new Blob(byteArrays, { type: contentType })
	// console.log('blob:')
	// console.log(await blobToBase64String(blob))
	return blob
}

const blobToBase64String = async (blob: Blob): Promise<string> => {
	const buffer = await blob.arrayBuffer()
	const base64 = BufferB.from(buffer).toString('base64')
	return 'data:application/octet-stream;base64,' + base64
}

const getAuthSig = async (): Promise<AuthSig> => {
	globalCheck()

	// console.log('backendWalletPrivateKey')
	// console.log(backendWalletPrivateKey)
	const signer = new ethers.Wallet(backendWalletPrivateKey as string)
	// console.log('signer')
	// console.log(signer)

	let address: string | undefined
	if (signer.address !== undefined) {
		if (typeof signer.address == 'string') {
			if (signer.address.length > 0) {
				address = signer.address
			}
		}
	}
	if (address === undefined) {
		address = await signer.getAddress()
	}

	// console.log('address')
	// console.log(address)

	const siweMessage = new Siwe.SiweMessage({
		domain: 'ixily.io',
		address: address!,
		statement:
			'I do agree share my wallet authority with Lit Protocol nodes for the purposes of proofing ownership and encryption of my trade ideas.',
		uri: 'https://ixily.io',
		version: '1',
		chainId: 1,
	})

	const messageToSign = siweMessage.prepareMessage()

	const sig = await signer?.signMessage(messageToSign)

	const authSig: AuthSig = {
		// messageToSign,
		sig,
		derivedVia: 'web3.eth.personal.sign',
		signedMessage: messageToSign,
		address,
	}

	return authSig
}

const encryptString = async (
	message: string,
): Promise<{
	encryptedString: string
	symmetricKey: string
	authSig: AuthSig
}> => {
	globalCheck()
	const authSig = await getAuthSig()

	const { symmetricKey, encryptedString } = await LitJsSdk.encryptString(
		message,
	)

	// console.log(
	// 	'[lit-node-provider] encryptString (encryptedString)',
	// 	encryptedString,
	// )
	const symmetricKeyStr = LitJsSdk.uint8arrayToString(symmetricKey, 'base16')

	// console.log(
	// 	'[lit-node-provider] encryptString (symmetricKey)',
	// 	symmetricKeyStr,
	// )
	// console.log('typeof symmetricKeyStr:')
	// console.log(typeof symmetricKeyStr)
	// console.log('symmetricKeyStr:')
	// console.log(symmetricKeyStr)

	const base64: string = await blobToBase64String(encryptedString)
	// console.log('base64')
	// console.log(base64)
	// console.log('[lit-node-provider] encryptString (base64)', base64)

	return {
		encryptedString: base64,
		symmetricKey: symmetricKeyStr,
		authSig,
	}
}

const saveKey = async (
	client: any,
	symmetricKey: string,
	accessControlConditions: AccessControlConditions,
	authSig: AuthSig,
	chain: string,
	permanent: boolean = true,
): Promise<{
	encryptedSymmetricKey: any
	encryptedSymmetricKeyString: string
}> => {
	globalCheck()

	const encryptedSymmetricKey = await client!.saveEncryptionKey({
		accessControlConditions,
		symmetricKey: LitJsSdk.uint8arrayFromString(symmetricKey, 'base16'),
		authSig,
		chain,
		permanent: permanent === true ? 1 : undefined,
	})

	// console.log('encryptedSymmetricKey')
	// console.log(encryptedSymmetricKey)
	const encryptedSymmetricKeyString = LitJsSdk.uint8arrayToString(
		encryptedSymmetricKey,
		'base16',
	)
	// console.log('encryptedSymmetricKeyString')
	// console.log(encryptedSymmetricKeyString)

	return {
		encryptedSymmetricKey,
		encryptedSymmetricKeyString,
	}
}

const updateConditionsKey = async (
	client: any,
	encryptedSymmetricKey: any,
	symmetricKey: string,
	accessControlConditions: AccessControlConditions,
	authSig: AuthSig,
	chain: string,
	permanent: boolean = true,
): Promise<void> => {
	globalCheck()

	await client!.saveEncryptionKey({
		encryptedSymmetricKey,
		accessControlConditions,
		symmetricKey: LitJsSdk.uint8arrayFromString(symmetricKey, 'base16'),
		authSig,
		chain,
		permanent: permanent === true ? 1 : undefined,
	})
}

const recoverString = async (
	client: any,
	encryptedString: string,
	encryptedSymmetricKey: string,
	accessControlConditions: AccessControlConditions,
	chain: string,
): Promise<string> => {
	globalCheck()

	// console.log(
	// 	'lit-node-provider.module [recoverString] (encryptedString)',
	// 	encryptedString,
	// )
	// console.log(
	// 	'lit-node-provider.module [recoverString] (encryptedSymmetricKey)',
	// 	encryptedSymmetricKey,
	// )
	// console.log(
	// 	'lit-node-provider.module [recoverString] (accessControlConditions)',
	// 	accessControlConditions,
	// )
	// console.log(
	// 	'lit-node-provider.module [recoverString] (accessControlConditionsStr)',
	// 	JSON.stringify(accessControlConditions),
	// )

	const authSig = await getAuthSig().catch((err: any) => {
		console.error(
			'lit-node-provider.module [recoverString] (authSig) ERROR',
			err.message,
		)
		throw err
	})

	// console.log('lit-node-provider.module [recoverString] (authSig)', authSig)

	const encryptionKey = await client!.getEncryptionKey({
		accessControlConditions,
		toDecrypt: encryptedSymmetricKey,
		chain,
		authSig,
	})

	// console.log(
	// 	'lit-node-provider.module [recoverString] (encryptionKey)',
	// 	encryptionKey,
	// )

	const blob = await base64StringToBlob(encryptedString)

	// console.log('lit-node-provider.module [recoverString] (blob)', blob)

	const decryptedString = await LitJsSdk.decryptString(blob, encryptionKey)

	// console.log(
	// 	'lit-node-provider.module [recoverString] (decryptedString)',
	// 	decryptedString,
	// )

	return decryptedString
}

const runJSFallback = async <T>(
	javascriptCode: string,
	params: any,
): Promise<{
	response: T
}> => {
	globalCheck()

	// console.log('[lit-node-provider] pre runJSFallback')

	const authSig = await getAuthSig()

	// console.log('[lit-node-provider] runJSFallback (authSig)', authSig)

	const litNodeClient = new LitJsSdk.LitNodeClientNodeJs({
		alertWhenUnauthorized: false,
		// minNodeCount: 3,
		litNetwork: 'jalapeno',
		debug: false,
	})

	await litNodeClient.connect()

	const litActionCode = javascriptCode

	const litActionResponse = await litNodeClient.executeJs({
		code: litActionCode,
		jsParams: params,
		authSig,
	})

	// console.log('runJS (litActionResponse)', litActionResponse)

	const response = litActionResponse.response as unknown as T

	const finalResponse = {
		response,
	}

	// console.log('runJS (finalResponse)', finalResponse)

	return finalResponse
}

const runJS = async (
	client: any,
	ipfsId: string,
	publicKey: string,
	chain: string,
	params?: any,
	nodes: number = 10,
): Promise<any> => {
	globalCheck()

	const authSig = await getAuthSig()

	// Dev mark nodes
	if (LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE) {
		nodes = LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE_NODES
	}

	// delete params.executionNonce
	// delete params.commonCache

	// console.log('calling function')
	// console.log('params:')
	// console.log(params)
	// console.log('ipfsId:')
	// console.log(ipfsId)
	// console.log('publicKey:')
	// console.log(publicKey)
	// console.log('nodes:')
	// console.log(nodes)
	// const litActionResponse = await (
	// 	litNodeClient as auxiliarLit.LitNodeClientNodeJs
	// )
	// console.log('params')
	// console.log(params)
	// console.log('ipfsId')
	// console.log(ipfsId)
	// console.log('called lit action ipfsId: ' + ipfsId)
	// console.log('lit js called with params: ')
	// console.log(params)

	if (LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE) {
		console.log('LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE => true')
	}
	const jsParams = {
		ipfsId,
		publicKey,
		...params,
		debug: false,
		// debug: LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE,
		debugSkipSignature: false,
	}
	// console.log('jsParams:')
	// console.log(jsParams)

	const litActionResponse = await client.executeJs({
		code: LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE
			? LIT_ACTIONS.GET_PRICE_TESTCODE_V2_CODE
			: undefined,
		ipfsId: LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE ? undefined : ipfsId,
		jsParams,
		authSig,
		debug: true,
		targetNodeRange: nodes,
	})

	if (LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE) {
		console.log('runJS (litActionResponse)')
		console.log(litActionResponse)
	}

	const { response, logs } = litActionResponse

	// console.log('litActionResponse')
	// console.log(litActionResponse)

	log.dev('lit action logs:')
	log.dev(logs)

	if (LIT_ACTIONS.USE_LIT_ACTION_TEST_CODE) {
		console.log('runJS (finalResponse)', response)
		throw new Error('USE_LIT_ACTION_TEST_CODE check breakpoint')
	}

	return litActionResponse
}

export const LitNodeProviderModule: ILitProvider = {
	isBrowser: false,
	init,
	getEncryptionClient,
	getActionsClient,
	getAuthSig,
	saveKey,
	recoverString,
	encryptString,
	updateConditionsKey,
	runJSFallback,
	runJS,
}
