package com.r35157.cryptowallet.solana;

import com.r35157.libs.solana.SolanaSignedTransaction;
import com.r35157.libs.solana.SolanaUnsignedTransaction;
import com.r35157.nenjim.component.NenjimComponent;
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.math.BigDecimal;

/**
 * A Solana wallet capable of identifying itself, signing transactions and
 * submitting them to the Solana blockchain.
 *
 * <p>Signing and submission can be performed separately or as one complete
 * operation.</p>
 */
public interface SolanaWallet extends NenjimComponent {
    /**
     * Returns the public Solana address controlled by this wallet.
     *
     * @return the wallet's public address
     */
    @NotNull
    ΩSolanaWalletIdΩ getAddress();

    /**
     * Returns the native SOL balance of this wallet.
     *
     * @return the wallet's native SOL balance
     * @throws IOException if the balance cannot be fetched or decoded
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaAmountΩ getSolanaBalance()
            throws IOException, InterruptedException;

    /**
     * Returns the balance of a specific SPL token owned by this wallet.
     *
     * @param mintAddress the SPL token mint address
     * @param splProgram the SPL token program containing the token
     * @return the token balance, or {@code null} if no token account exists
     * @throws IOException if the balance cannot be fetched or decoded
     * @throws InterruptedException if the calling thread is interrupted
     * @throws IllegalStateException if multiple accounts exist for the mint
     */
    @Nullable
    ΩAmountΩ getSPLTokenBalance(
            @NotNull ΩSPLMintAddressΩ mintAddress,
            @NotNull SolanaSPLTokenProgram splProgram
    ) throws IOException, InterruptedException;

    /**
     * Sends a specific amount of SOL to another wallet.
     *
     * <p>The wallet builds, signs and submits the required transaction.</p>
     *
     * @param recipient the wallet that should receive the SOL
     * @param amount the amount of SOL to send
     * @return the transaction signature returned by Solana RPC
     * @throws IllegalArgumentException if the recipient or amount is invalid
     * @throws IOException if the transaction cannot be built, signed or sent
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaTransactionSignatureΩ sendSolana(
            @NotNull ΩSolanaWalletIdΩ recipient,
            @NotNull ΩSolanaAmountΩ amount
    ) throws IOException, InterruptedException;

    /**
     * Sends the wallet's complete native SOL balance to another wallet after
     * paying the exact transaction fee.
     *
     * <p>The wallet builds, signs and submits the required transaction.</p>
     *
     * @param recipient the wallet that should receive the SOL
     * @return the transaction signature returned by Solana RPC
     * @throws IllegalArgumentException if the recipient is invalid
     * @throws IllegalStateException if the balance cannot cover a transfer
     * @throws IOException if the transaction cannot be built, signed or sent
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaTransactionSignatureΩ sendAllSolana(
            @NotNull ΩSolanaWalletIdΩ recipient
    ) throws IOException, InterruptedException;

    /**
     * Sends an SPL token to another wallet.
     *
     * <p>The token program is detected from the mint account. The wallet
     * builds, signs and submits the complete transaction internally.</p>
     *
     * @param mintAddress the mint of the token to send
     * @param recipient the wallet that should receive the token
     * @param amount the token amount in its human-readable decimal unit
     * @param missingRecipientTokenAccountPolicy how to handle a missing
     *        recipient Associated Token Account
     * @return the transaction signature returned by Solana RPC
     * @throws IllegalArgumentException if an argument or amount is invalid
     * @throws IllegalStateException if required accounts or balances are
     *         missing or invalid
     * @throws IOException if blockchain data cannot be read or the transaction
     *         cannot be built, signed or sent
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaTransactionSignatureΩ sendSPLToken(
            @NotNull ΩSPLMintAddressΩ mintAddress,
            @NotNull ΩSolanaWalletIdΩ recipient,
            @NotNull ΩAmountΩ amount,
            @NotNull MissingRecipientTokenAccountPolicy
                    missingRecipientTokenAccountPolicy
    ) throws IOException, InterruptedException;

    /**
     * Burns an amount of an SPL token owned by this wallet.
     *
     * <p>The token program is detected from the mint account. The amount uses
     * the token's human-readable decimal unit and must convert exactly to raw
     * token units. The wallet validates its holding, builds one checked burn,
     * signs it, and submits it once. Normal return means submission only; this
     * method does not await confirmation, retry, or close the token account.</p>
     *
     * @param mintAddress mint of the token to burn
     * @param amount amount to burn in the token's human-readable decimal unit
     * @return transaction signature returned by Solana RPC
     * @throws IllegalArgumentException if the mint or amount is invalid or the
     *                                  amount has excessive decimal precision
     * @throws IllegalStateException if the mint or wallet holding is missing,
     *                               unsupported, inconsistent, or insufficient
     * @throws IOException if blockchain data cannot be read or the transaction
     *                     cannot be built, signed, or submitted
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaTransactionSignatureΩ burnSPLToken(
            @NotNull ΩSPLMintAddressΩ mintAddress,
            @NotNull ΩAmountΩ amount
    ) throws IOException, InterruptedException;

    /**
     * Signs an unsigned Solana transaction.
     *
     * <p>The configured wallet must be a required signer for the supplied
     * transaction. This method does not submit the resulting transaction
     * to the Solana blockchain.</p>
     *
     * @param transaction the unsigned transaction to sign
     * @return the transaction containing the wallet's required signature
     * @throws IllegalArgumentException if the transaction is missing or blank
     * @throws IOException if the transaction cannot be signed or the signer
     *                     returns an invalid response
     * @throws InterruptedException if the signing process is interrupted
     */
    @NotNull
    SolanaSignedTransaction signTransaction(
            @NotNull SolanaUnsignedTransaction transaction
    ) throws IOException, InterruptedException;

    /**
     * Submits a signed Solana transaction to the Solana blockchain.
     *
     * @param transaction the signed transaction to submit
     * @return the transaction signature returned by Solana RPC
     * @throws IllegalArgumentException if the transaction is missing or blank
     * @throws IOException if the transaction cannot be sent
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaTransactionSignatureΩ sendTransaction(
            @NotNull SolanaSignedTransaction transaction
    ) throws IOException, InterruptedException;

    /**
     * Signs and submits an unsigned Solana transaction.
     *
     * @param transaction the unsigned transaction to sign and submit
     * @return the transaction signature returned by Solana RPC
     * @throws IllegalArgumentException if the transaction is missing or blank
     * @throws IOException if the transaction cannot be signed or sent
     * @throws InterruptedException if the signing process or calling thread
     *                               is interrupted
     */
    @NotNull
    default ΩSolanaTransactionSignatureΩ signAndSendTransaction(
            @NotNull SolanaUnsignedTransaction transaction
    ) throws IOException, InterruptedException {
        return sendTransaction(signTransaction(transaction));
    }
}
