package com.atlassian.bitbucket.test.rest;

import com.atlassian.bitbucket.util.BuilderSupport;
import com.google.common.collect.Maps;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.specification.ResponseSpecification;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.ws.rs.core.Response;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;

public abstract class AbstractRestRequest implements RestRequest {

    private final RestAuthentication authentication;
    private final ResponseSpecification responseSpecification;
    private final Response.Status status;

    protected AbstractRestRequest(AbstractBuilder<?, ?> builder) {
        authentication = builder.authentication;
        responseSpecification = builder.buildSpecification();
        status = builder.status;
    }

    @Nullable
    public RestAuthentication getAuthentication() {
        return authentication;
    }

    @Nonnull
    public ResponseSpecification getResponseSpecification() {
        return responseSpecification;
    }

    @Nonnull
    @Override
    public Response.Status getStatus() {
        return status;
    }

    @Nonnull
    @Override
    public Map<String, Object> toQueryParams() {
        return Maps.newLinkedHashMap();
    }

    protected abstract static class AbstractBuilder<B extends AbstractBuilder<B, R>, R extends AbstractRestRequest>
            extends BuilderSupport {

        private final ResponseSpecification specifications = RestAssured.expect(); // empty response specification

        private RestAuthentication authentication;
        private Response.Status status;

        protected AbstractBuilder() {
            status = Response.Status.OK;
        }

        protected AbstractBuilder(@Nonnull R request) {
            status = checkNotNull(request, "request").getStatus();
        }

        @Nonnull
        public B authentication(@Nullable RestAuthentication value) {
            authentication = value;

            return self();
        }

        @Nonnull
        public abstract R build();

        /**
         * Add extra {@link ResponseSpecification response specifications} to this request. New instances of those can
         * be easily obtained using {@code RestAssured.expect()}. Specifications will be added cumulatively, with some
         * fields overridden and some fields merged, as per {@link ResponseSpecification#spec(ResponseSpecification)}.
         * <p>
         * Clients can also use {@link ResponseSpecBuilder} to build more complex specifications before passing on to
         * this method. Some built-in specifications are also provided in the
         * {@code com.atlasstian.stash.test.rest.specification} package.
         * <p>
         * NOTE: Expectations about the response status code provided using this method will <i>overridden</i> by any
         * value provided directly using {@link #status(Response.Status)}.
         *
         * @param specification new specification to add
         * @return this builder instance
         * @see ResponseSpecification
         * @see ResponseSpecBuilder
         * @see com.atlassian.bitbucket.test.rest.specification
         */
        @Nonnull
        public B specification(@Nullable ResponseSpecification specification) {
            if (specification != null) {
                specifications.spec(specification);
            }

            return self();
        }

        @Nonnull
        public B status(@Nonnull Response.Status value) {
            status = checkNotNull(value, "status");

            return self();
        }

        private ResponseSpecification buildSpecification() {
            return new ResponseSpecBuilder()
                    .addResponseSpecification(specifications)
                    .expectStatusCode(status.getStatusCode()) //Replace any status code in the provided specification
                    .build();
        }

        @Nonnull
        protected abstract B self();
    }
}
