// Core dependencies
import { Program } from "@coral-xyz/anchor";
import { Connection, Keypair, PublicKey, TransactionSignature, VersionedTransaction } from "@solana/web3.js";
import { parse, stringify } from "uuid";

// Local imports
import { PRIORITY_FEE, USDC_MINT } from "./constants";
import { AgentsProgram } from "./idl/types";
import { createAgentIx } from "./instructions/createAgent";
import { topUpBalanceIx } from "./instructions/topUpBalance";
import { fetchAgentState, parseAgentState, ParsedAgentState } from "./state/agent";
import { getAgent, getAgentsByCreator, getAgentsProgram, getAgentStateAccount, getAllAgents, getCreateAgentEventFromTx, getRandomSeed, getTopUpBalanceEventFromTx } from "./utils/programAccounts";
import { prepareV0Transactions, sendV0Transactions, VersionedTxs } from "./utils/txUtils";

export class AgentsSDK {

    private sdkParams: {
        payer: PublicKey,
        connection: Connection,
        program: Program<AgentsProgram>,
        priorityFee: number,
    }

    constructor(params: {
        RPC_URL: string,
        payer?: PublicKey,
        priorityFee?: number,
    }) {
        this.sdkParams = {
            payer: params.payer ?? Keypair.generate().publicKey,
            connection: new Connection(params.RPC_URL, "confirmed"),
            program: getAgentsProgram(new Connection(params.RPC_URL, "confirmed")),
            priorityFee: params.priorityFee ?? PRIORITY_FEE,
        }
    }

    async setPayer(payer: PublicKey) {
        this.sdkParams.payer = payer;
    }

    async createAgent(params?: {}): Promise<{
        blockhash: string,
        lastValidBlockHeight: number,
        versionedTxs: VersionedTransaction[],
        batches: number[],
        agent: string,
        agentState: string,
        agentUuid: string,
    }> {
        let agentUuid = getRandomSeed();
        let agentAddress = getAgent(agentUuid);
        let agentState = getAgentStateAccount(agentAddress);
        const ix = await createAgentIx({
            payer: this.sdkParams.payer,
            program: this.sdkParams.program,
            agentUuid,
            agentAddress,
            agentState,
        });
        return {
            ...(await prepareV0Transactions({
                connection: this.sdkParams.connection,
                payer: this.sdkParams.payer,
                priorityFee: this.sdkParams.priorityFee,
                multipleIxs: [[ix]],
                multipleLookupTableAddresses: [[]],
                signers: [[]],
                batches: [1],
            })),
            agentUuid: stringify(Uint8Array.from(agentUuid)),
            agent: agentAddress.toBase58(),
            agentState: agentState.toBase58(),
        };
    }

    async topUpBalance(params: {
        amount: number,
        mint?: PublicKey,
    }): Promise<VersionedTxs> {
        const ix = await topUpBalanceIx({
            payer: this.sdkParams.payer,
            program: this.sdkParams.program,
            amount: params.amount,
            mint: params.mint ?? USDC_MINT,
        });
        return await prepareV0Transactions({
            connection: this.sdkParams.connection,
            payer: this.sdkParams.payer,
            priorityFee: this.sdkParams.priorityFee,
            multipleIxs: [[ix]],
            multipleLookupTableAddresses: [[]],
            signers: [[]],
            batches: [1],
        });
    }

    static async validateTopUpTx(params: {
        txId: TransactionSignature,
        RPC_URL: string,
    }): Promise<{
        user: string,
        mint: string,
        amount: number,
    }> {
        return await getTopUpBalanceEventFromTx(
            new Connection(params.RPC_URL, "confirmed"),
            getAgentsProgram(new Connection(params.RPC_URL, "confirmed")),
            params.txId
        );
    }

    async validateTopUpTx(txId: TransactionSignature): Promise<{
        user: string,
        mint: string,
        amount: number,
    }> {
        return await getTopUpBalanceEventFromTx(
            this.sdkParams.connection,
            this.sdkParams.program,
            txId
        );
    }

    static async validateCreateAgentTx(params: {
        txId: TransactionSignature,
        RPC_URL: string,
    }): Promise<{
        agent: string,
        agentState: string,
        agentUuid: string,
        creator: string,
    }> {
        return await getCreateAgentEventFromTx(
            new Connection(params.RPC_URL, "confirmed"),
            getAgentsProgram(new Connection(params.RPC_URL, "confirmed")),
            params.txId
        );
    }

    async validateCreateAgentTx(txId: TransactionSignature): Promise<{
        agent: string,
        agentState: string,
        agentUuid: string,
        creator: string,
    }> {
        return await getCreateAgentEventFromTx(
            this.sdkParams.connection,
            this.sdkParams.program,
            txId
        );
    }

    async getAgentState(uuid: string): Promise<ParsedAgentState> {
        let agentState = await fetchAgentState(
            this.sdkParams.program,
            getAgentStateAccount(getAgent(Array.from(parse(uuid))))
        );
        return parseAgentState(agentState);
    }

    async getAgentsByCreator(creator: string): Promise<ParsedAgentState[]> {
        return await getAgentsByCreator(
            this.sdkParams.program,
            new PublicKey(creator)
        );
    }

    async getAllAgents(): Promise<ParsedAgentState[]> {
        return await getAllAgents(
            this.sdkParams.program
        );
    }

    async sendSignedVersionedTxs(
        txs: VersionedTxs,
        simulateTransactions: boolean = false,
    ): Promise<TransactionSignature[]> {
        return await sendV0Transactions(
            this.sdkParams.connection,
            txs,
            simulateTransactions
        );
    }
}
