package com.atlassian.bitbucket.test.rest.specification;

import com.atlassian.bitbucket.test.rest.JsonMatchers;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.specification.ResponseSpecification;
import org.hamcrest.Matcher;

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

import static com.atlassian.bitbucket.test.rest.JsonMatchers.isRestErrorWith;
import static com.atlassian.bitbucket.test.rest.JsonMatchers.isRestErrorWithMessage;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.iterableWithSize;

/**
 * Reusable {@link ResponseSpecification response specifications} that can be used to make expectations that REST
 * response contain specific error(s).
 * @see ResponseSpecification
 * @see com.atlassian.bitbucket.test.rest.AbstractRestRequest.AbstractBuilder#specification(ResponseSpecification)
 */
public class RestErrorSpecifications {

    private static final String ERRORS_FIELD = "errors";
    private static final String ERROR_FORMAT = ERRORS_FIELD + "[%d]";
    private static final String ERROR_MESSAGE_FORMAT = ERROR_FORMAT + ".message";

    private RestErrorSpecifications() {
        throw new UnsupportedOperationException("Do not instantiate " + getClass().getSimpleName());
    }

    /**
     * Expect exactly one error that matches the {@code errorMatcher}.
     * {@link JsonMatchers} provides common REST error matchers to use as {@code errorMatcher}.
     *
     * @param errorMatcher error matcher
     * @return response specification to use in your REST request
     */
    @Nonnull
    public static ResponseSpecification expectError(@Nonnull Matcher<?> errorMatcher) {
        ResponseSpecification fullSpec = RestAssured.expect();
        fullSpec.body(ERRORS_FIELD, iterableWithSize(1));
        fullSpec.spec(expectErrorAt(0, errorMatcher));
        return fullSpec;
    }

    /**
     * Expect exactly one error with given {@code context} and {@code errorMessage}.
     *
     * @param context expected context
     * @param errorMessage expected error message
     * @return response specification to use in your REST request
     */
    @Nonnull
    public static ResponseSpecification expectError(@Nullable String context, @Nonnull String errorMessage) {
        return expectError(isRestErrorWith(context, errorMessage));
    }

    /**
     * Expect error matching {@code errorMatcher} at {@code index}.
     * {@link JsonMatchers} provides common REST error matchers to use as {@code errorMatcher}.
     *
     * @param index index of the expected error
     * @param errorMatcher error matcher
     * @return response specification to use in your REST request
     */
    @Nonnull
    public static ResponseSpecification expectErrorAt(int index, @Nonnull Matcher<?> errorMatcher) {
        checkNotNull(errorMatcher, "errorMatcher");
        return RestAssured.expect().body(errorAt(index), errorMatcher);
    }

    /**
     * Expect exactly one error with given {@code errorMessage}.
     *
     * @param errorMessage expected error message
     * @return response specification to use in your REST request
     */
    @Nonnull
    public static ResponseSpecification expectErrorMessage(@Nonnull String errorMessage) {
        return expectError(isRestErrorWithMessage(errorMessage));
    }

    @Nonnull
    public static ResponseSpecification expectErrorMessageAt(int index, @Nonnull String errorMessage) {
        checkNotNull(errorMessage, "errorMessage");
        return expectErrorMessageAt(index, equalTo(errorMessage));
    }

    @Nonnull
    public static ResponseSpecification expectErrorMessageAt(int index, @Nonnull Matcher<?> errorMessageMatcher) {
        checkNotNull(errorMessageMatcher, "errorMessageMatcher");
        return RestAssured.expect().body(errorMessageAt(index), errorMessageMatcher);
    }

    @Nonnull
    public static ResponseSpecification expectErrorMessages(@Nonnull String... errorMessages) {
        ResponseSpecification fullSpec = RestAssured.expect();
        for (int i = 0; i < errorMessages.length; i++) {
            fullSpec.spec(expectErrorMessageAt(i, errorMessages[i]));
        }

        return fullSpec;
    }

    private static String errorAt(int index) {
        return String.format(ERROR_FORMAT, index);
    }

    private static String errorMessageAt(int index) {
        return String.format(ERROR_MESSAGE_FORMAT, index);
    }
}
