import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash, derivePubkeyFrom, ss58 } from "../src/utils";

const PRESET: Presets = Presets.RealS;

test(
	`slashing spam test on ${PRESET}`,
	async () => {
		const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
		const apis = await getApis();
		// total number of offences to send.
		const target = 1000;
		// the size of each batch.
		const batchSize = 100;
		const numBatches = Math.ceil(target / batchSize);
		let sent = 0;
		let received = 0;
		// onchain-page-size for offence queueing in RC is 50, so we expect 20 pages for 1000 offences.

		const steps = [
			// first relay session change at block 11, just a sanity check
			Observe.on(Chain.Relay, "Session", "NewSession")
				.byBlock(11)
				.onPass(async () => {
					logger.info(`Submitting ${target} offences in batches of ${batchSize}`);

					// Calculate number of batches needed

					let nonce = await apis.rcApi.apis.AccountNonceApi.account_nonce(ss58(alice.publicKey));
					logger.info(`Alice nonce at start: ${nonce}`);

					for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
						const start = batchIndex * batchSize;
						const end = Math.min(start + batchSize, target);
						const currentBatchSize = end - start;

						logger.info(`Processing batch ${batchIndex + 1}/${numBatches}: offences ${start} to ${end - 1}`);

						// Create batch of offence calls
						const offenceCalls = Array.from({ length: currentBatchSize }, (_, i) => {
							const offenceIndex = start + i;
							logger.debug(`Preparing offence ${offenceIndex}: ${derivePubkeyFrom(`//${offenceIndex}`)}`);

							return apis.rcApi.tx.RootOffences.report_offence({
								offences: [[
									[derivePubkeyFrom(`//${offenceIndex}`), { total: BigInt(0), own: BigInt(0), others: [] }],
									0, // session index
									BigInt(offenceIndex), // time slot, each being unique
									100000000 // slash ppm
								]]
							}).decodedCall;
						});

						// Submit this batch as a single transaction
						try {
							const batchCall = apis.rcApi.tx.Utility.force_batch({ calls: offenceCalls }).decodedCall;
							const result = apis.rcApi.tx.Sudo.sudo({ call: batchCall })
								.signAndSubmit(alice, { at: "best", nonce: nonce });

							logger.info(`Batch ${batchIndex + 1} submitted`);
							nonce += 1;
							sent += currentBatchSize;

						} catch (error) {
							logger.error(`Batch ${batchIndex + 1} failed:`, error);
							// Continue with next batch even if this one fails
						}
					}
				}),

			// in the meantime, we expect to see on the AH side:
			...Array.from({ length: 20 }, (_, __) =>
				Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck((x) => {
					received += x.offences_count;
					return true
				}),
			),
		];

		const testCase = new TestCase(
			steps.map((s) => s.build()),
			true,
			() => {
				logger.info(`Test completed. Created ${sent} offences, processed ${received} in teyrchain`);
				killZn();
			}
		);

		const outcome = await runTest(testCase, apis, paraLog);
		expect(outcome).toEqual(EventOutcome.Done);
		expect(sent).toEqual(received);
	},
	{ timeout: GlobalTimeout * 2 } // Double timeout for this complex test
);
