import type { TxData } from "@hooks/apis/common/quote/types";
import type { SendTransactionOptions } from "@solana/wallet-adapter-base";
import {
	Connection,
	Keypair,
	PublicKey,
	type Transaction,
	TransactionInstruction,
	TransactionMessage,
	type TransactionSignature,
	VersionedTransaction,
} from "@solana/web3.js";
import type { SOLANA_CHAIN_ID } from "./constants";
import { retry } from "./retry";

export const signAndSendTransaction = async ({
	address,
	txData,
	sendTransaction,
	rpc,
}: {
	txData: TxData<typeof SOLANA_CHAIN_ID>;
	address: string;
	sendTransaction: (
		transaction: VersionedTransaction | Transaction,
		connection: Connection,
		options?: SendTransactionOptions,
	) => Promise<TransactionSignature>;
	rpc: string | undefined;
}) => {
	if (!rpc) {
		throw new Error("RPC is required");
	}

	const connection = new Connection(rpc);

	const clientLookupTables = await Promise.all(
		txData.data.lookupTables.map(async (lookupTableAccount) => {
			return (
				await connection.getAddressLookupTable(new PublicKey(lookupTableAccount))
			).value;
		}),
	);

	const lookupTables = clientLookupTables.filter(
		(lookupTable) => lookupTable !== null,
	);

	const txInstructions = txData.data.instructions.map((instruction, index) => {
		return new TransactionInstruction({
			programId: new PublicKey(instruction.programId),
			keys: instruction.keys.map((key) => ({
				pubkey: new PublicKey(key.pubkey),
				isSigner: key.isSigner,
				isWritable: key.isWritable,
			})),
			data: Buffer.from(instruction.data),
		});
	});

	const messageV0 = new TransactionMessage({
		payerKey: new PublicKey(address),
		recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
		instructions: txInstructions,
	}).compileToV0Message(lookupTables);

	const transaction = new VersionedTransaction(messageV0);

	const txId = await sendTransaction(transaction, connection, {
		skipPreflight: true,
		signers: txData.data.signers.map(toSolanaSigners),
	});

	await retry(
		async () => {
			const tx = await connection
				.getTransaction(txId, {
					commitment: "confirmed",
					maxSupportedTransactionVersion: 0,
				})
				.catch(() => null);
			return tx;
		},
		(tx) => !!tx,
		3,
		1000,
	);

	return txId;
};

const toSolanaSigners = (signer: number[]) => {
	return Keypair.fromSecretKey(Uint8Array.from(signer));
};
