package com.atlassian.bitbucket.mesh.transaction;

import com.atlassian.bitbucket.mesh.AbstractStatusException;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitError;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitErrorUtils;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcIllegalTransactionState;
import io.grpc.Metadata;
import io.grpc.Status;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static java.util.Objects.requireNonNull;

/**
 * Exception that is thrown when a {@link Transaction} cannot be {@link TransactionManager#prepared(PreparedAction)
 * prepared}, {@link Transaction#setRollbackOnly() rolled back} or committed because that transition is not supported
 * from the current transaction state (e.g. committing a transaction that has already been rolled back).
 */
public class IllegalTransactionStateException extends AbstractStatusException {

    private final TransactionState state;
    private final TransactionState targetState;
    private final String transactionId;

    public IllegalTransactionStateException(@Nullable String message, @Nonnull String transactionId,
                                            @Nonnull TransactionState state,
                                            @Nonnull TransactionState targetState) {
        super(message, Status.FAILED_PRECONDITION);

        this.state = state;
        this.targetState = targetState;
        this.transactionId = transactionId;
    }

    public IllegalTransactionStateException(@Nonnull String transactionId,
                                            @Nonnull TransactionState state,
                                            @Nonnull TransactionState targetState) {
        this("Transaction " + requireNonNull(transactionId, "transactionId") + " cannot transition from " +
                requireNonNull(state, "state") + " to " + requireNonNull(targetState, "targetState"),
                transactionId, state, targetState);
    }

    /**
     * @return the transaction's current state
     */
    @Nonnull
    public TransactionState getState() {
        return state;
    }

    /**
     * @return the transaction's requested state
     */
    @Nonnull
    public TransactionState getTargetState() {
        return targetState;
    }

    /**
     * @return the transaction ID
     */
    @Nonnull
    public String getTransactionId() {
        return transactionId;
    }

    @Override
    protected Metadata getTrailers() {
        return RpcGitErrorUtils.toMetadata(RpcGitError.newBuilder()
                .setIllegalTransactionState(RpcIllegalTransactionState.newBuilder()
                        .setTransactionId(transactionId)
                        .setState(state.name())
                        .setTargetState(targetState.name())
                        .build())
                .build());
    }
}
