package com.r35157.libs.jupiter;

import com.r35157.libs.solana.SolanaCommitment;
import com.r35157.libs.solana.SolanaTransactionOutcome;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

/**
 * Reports a definitive on-chain failure or an unknown timeout for a Jupiter
 * transaction whose signature is already known.
 *
 * <p>This checked exception never represents a successful transaction. A
 * timed-out outcome is unknown and must not cause an equivalent transaction
 * to be automatically resubmitted.</p>
 */
public final class JupiterTransactionOutcomeException extends Exception {
    /**
     * Creates an exception for a failed or timed-out Jupiter transaction.
     *
     * @param transactionSignature known submitted transaction signature
     * @param requestedCommitment commitment requested from Solana
     * @param outcome complete failed or timed-out transaction outcome
     * @throws IllegalArgumentException if the signature is blank or the
     *                                  outcome is successful
     * @throws NullPointerException if any argument is {@code null}
     */
    public JupiterTransactionOutcomeException(
            @NotNull ΩSolanaTransactionSignatureΩ transactionSignature,
            @NotNull SolanaCommitment requestedCommitment,
            @NotNull SolanaTransactionOutcome outcome
    ) {
        super(buildMessage(
                requireSignature(transactionSignature),
                Objects.requireNonNull(
                        requestedCommitment,
                        "requestedCommitment"
                ),
                requireExceptionalOutcome(outcome)
        ));
        this.transactionSignature = transactionSignature;
        this.requestedCommitment = requestedCommitment;
        this.outcome = outcome;
    }

    /**
     * Returns the known submitted transaction signature.
     *
     * @return non-blank Solana transaction signature
     */
    public @NotNull ΩSolanaTransactionSignatureΩ transactionSignature() {
        return transactionSignature;
    }

    /**
     * Returns the commitment that the Jupiter operation required.
     *
     * @return requested Solana commitment
     */
    public @NotNull SolanaCommitment requestedCommitment() {
        return requestedCommitment;
    }

    /**
     * Returns the complete failed or timed-out outcome.
     *
     * @return complete exceptional Solana transaction outcome
     */
    public @NotNull SolanaTransactionOutcome outcome() {
        return outcome;
    }

    private static ΩSolanaTransactionSignatureΩ requireSignature(
            ΩSolanaTransactionSignatureΩ transactionSignature
    ) {
        Objects.requireNonNull(
                transactionSignature,
                "transactionSignature"
        );
        if (transactionSignature.isBlank()) {
            throw new IllegalArgumentException(
                    "Transaction signature must not be blank"
            );
        }
        return transactionSignature;
    }

    private static SolanaTransactionOutcome requireExceptionalOutcome(
            SolanaTransactionOutcome outcome
    ) {
        Objects.requireNonNull(outcome, "outcome");
        if (outcome.status() == SolanaTransactionOutcome.Status.SUCCEEDED) {
            throw new IllegalArgumentException(
                    "A successful transaction outcome is not exceptional"
            );
        }
        return outcome;
    }

    private static String buildMessage(
            ΩSolanaTransactionSignatureΩ transactionSignature,
            SolanaCommitment requestedCommitment,
            SolanaTransactionOutcome outcome
    ) {
        return switch (outcome.status()) {
            case FAILED -> "Jupiter transaction "
                    + transactionSignature
                    + " definitively failed on-chain at "
                    + requestedCommitment
                    + " in slot "
                    + outcome.slot()
                    + ": "
                    + outcome.failureDetails();
            case TIMED_OUT -> "Jupiter transaction "
                    + transactionSignature
                    + " did not reach a definitive "
                    + requestedCommitment
                    + " outcome before timeout; the on-chain outcome is "
                    + "unknown and an equivalent transaction must not be "
                    + "automatically resubmitted";
            case SUCCEEDED -> throw new IllegalArgumentException(
                    "A successful transaction outcome is not exceptional"
            );
        };
    }

    private final ΩSolanaTransactionSignatureΩ transactionSignature;
    private final SolanaCommitment requestedCommitment;
    private final SolanaTransactionOutcome outcome;
}
