import { ApiPromise } from "@polkadot/api";
import {
	AddressOrPair,
	ApiTypes,
	SubmittableExtrinsic,
} from "@polkadot/api/types";
import { GenericExtrinsic } from "@polkadot/types";
import { AnyTuple } from "@polkadot/types/types";
import { Event } from "@polkadot/types/interfaces";
import { u8aToHex } from "@polkadot/util";
// import { DevTestContext } from "./setup-dev-tests";
const debug = require("debug")("test:substrateEvents");

// LAUNCH BASED NETWORK TESTING (PARA TESTS)

export async function waitOneBlock(
	api: ApiPromise,
	numberOfBlocks: number = 1
) {
	return new Promise<void>(async (res) => {
		let count = 0;
		let unsub = await api.derive.chain.subscribeNewHeads(async (header) => {
			console.log(
				`One block elapsed : #${header.number}: author : ${header.author}`
			);
			count += 1;
			if (count === 1 + numberOfBlocks) {
				unsub();
				res();
			}
		});
	});
}

// Log relay/parachain new blocks and events
export async function logEvents(api: ApiPromise, name: string) {
	api.derive.chain.subscribeNewHeads(async (header) => {
		debug(
			`------------- ${name} BLOCK#${header.number}: author ${header.author}, hash ${header.hash}`
		);
		(await api.query.system.events.at(header.hash)).forEach((e, i) => {
			debug(
				`${name} Event :`,
				i,
				header.hash.toHex(),
				(e.toHuman() as any).event.section,
				(e.toHuman() as any).event.method
			);
		});
	});
}

async function lookForExtrinsicAndEvents(
	api: ApiPromise,
	extrinsicHash: Uint8Array
) {
	// We retrieve the block (including the extrinsics)
	const signedBlock = await api.rpc.chain.getBlock();

	// We retrieve the events for that block
	const allRecords = await api.query.system.events.at(
		signedBlock.block.header.hash
	);

	const extrinsicIndex = signedBlock.block.extrinsics.findIndex((ext) => {
		return ext.hash.toHex() == u8aToHex(extrinsicHash);
	});
	if (extrinsicIndex < 0) {
		console.log(
			`Extrinsic ${extrinsicHash} is missing in the block ${signedBlock.block.header.hash}`
		);
	}
	const extrinsic = signedBlock.block.extrinsics[extrinsicIndex];

	// We retrieve the events associated with the extrinsic
	const events = allRecords
		.filter(
			({ phase }) =>
				phase.isApplyExtrinsic &&
				phase.asApplyExtrinsic.toNumber() == extrinsicIndex
		)
		.map(({ event }) => event);
	return { events, extrinsic };
}

async function tryLookingForEvents(
	api: ApiPromise,
	extrinsicHash: Uint8Array
): Promise<{ extrinsic: GenericExtrinsic<AnyTuple>; events: Event[] }> {
	await waitOneBlock(api);
	let { extrinsic, events } = await lookForExtrinsicAndEvents(
		api,
		extrinsicHash
	);
	if (events.length > 0) {
		return {
			extrinsic,
			events,
		};
	} else {
		return await tryLookingForEvents(api, extrinsicHash);
	}
}

export const createBlockWithExtrinsicParachain = async <
	Call extends SubmittableExtrinsic<ApiType>,
	ApiType extends ApiTypes
>(
	api: ApiPromise,
	sender: AddressOrPair,
	polkadotCall: Call
): Promise<{ extrinsic: GenericExtrinsic<AnyTuple>; events: Event[] }> => {
	console.log("-------------- EXTRINSIC CALL -------------------------------");
	// This should return a Uint8Array
	const extrinsicHash = (await polkadotCall.signAndSend(
		sender
	)) as unknown as Uint8Array;

	// We create the block which is containing the extrinsic
	//const blockResult = await context.createBlock();
	return await tryLookingForEvents(api, extrinsicHash);
};