package it.com.atlassian.stash.rest.mirror;

import com.atlassian.bitbucket.test.rest.mirror.MirroringInstallationHelper;
import com.atlassian.bitbucket.test.rest.server.stub.mirror.StubMirror;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.RequestSpecification;
import it.com.atlassian.stash.BaseRestTest;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.mutable.MutableObject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import javax.ws.rs.core.MediaType;
import java.util.ArrayList;

import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminPassword;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminUser;
import static com.atlassian.bitbucket.test.rest.mirror.MirroringRequestHelper.*;
import static com.atlassian.bitbucket.test.rest.server.stub.mirror.StubMirror.getDescriptorUrl;
import static com.jayway.restassured.RestAssured.given;
import static javax.ws.rs.core.Response.Status.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class MirroringRequestsResourceTest extends BaseRestTest {

    private static final String DEFAULT_BASEURL = "http://some.base.url.com:8888";

    private String mirrorId;

    @Rule
    public final TestName testName = new TestName();

    @Before
    public void setupKeys() {
        mirrorId = testName.getMethodName();
    }

    @Test
    public void testBadRequestIfMissingFields() throws Exception {
        given()
                .contentType(MediaType.APPLICATION_JSON)
                .body("{\"mirrorId\": \"mymirror\"}")
                .expect()
                .statusCode(BAD_REQUEST.getStatusCode())
                .when().post(getMirroringRequestsUrl());
    }

    @Test
    public void testCanAcceptRequest() throws Exception {
        // stub the add-on descriptor, so the installation of the mirror can succeed
        try (StubMirror mirror = newStubMirror(mirrorId)) {
            int requestId = makeRequestFor(mirror);
            acceptRequest(requestId);
            MirroringInstallationHelper.uninstall(mirror, NO_CONTENT);
        }
    }

    @Test
    public void testAcceptRequestFailsIfDescriptorNotAvailable() {
        int requestId = makeRequestFor(mirrorId, DEFAULT_BASEURL);
        acceptRequest(requestId, CONFLICT);
    }

    @Test
    public void testCanCreateRequest() throws Exception {
        MutableObject<Response> response = new MutableObject<>();
        testContext.mirroringRequest(mirrorId, DEFAULT_BASEURL, getDescriptorUrl(DEFAULT_BASEURL, mirrorId), response::setValue);
        verifyRequest(mirrorId, "pending", DEFAULT_BASEURL, response.getValue());
    }

    @Test
    public void testCanPageAllRequests() throws Exception {
        for (int i = 0; i < 5; i++) {
            makeRequestFor("mymirror-" + i, DEFAULT_BASEURL);
        }

        verifyPage(2, false, getRequestPage(0, 2));
        verifyPage(2, false, getRequestPage(2, 2));
        verifyPage(1, true, getRequestPage(4, 2));
    }

    @Test
    public void testCanRejectRequest() throws Exception {
        int requestId = makeRequestFor(mirrorId, DEFAULT_BASEURL);
        Response rejectedResponse = rejectRequest(requestId);
        verifyRequest(mirrorId, "rejected", DEFAULT_BASEURL, rejectedResponse);
    }

    @Test
    public void testCanRetrieveRequest() throws Exception {
        int requestId = makeRequestFor(mirrorId, DEFAULT_BASEURL);
        Response resolveResponse = retrieveRequest(requestId);
        verifyRequest(mirrorId, "pending", DEFAULT_BASEURL, resolveResponse);
    }

    @Test
    public void testCanFilterRequests() throws Exception {
        ArrayList<Integer> createRequestIds = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            createRequestIds.add(makeRequestFor("filteredmirror-" + i, DEFAULT_BASEURL));
        }

        rejectRequest(createRequestIds.get(0));
        rejectRequest(createRequestIds.get(1));
        Response pendingMirroringRequests = getRequestPage(0, 5, "pending");

        verifyPage(3, true, pendingMirroringRequests);
    }

    @Test
    public void testConflictIfRejectingAlreadyAcceptedRequest() throws Exception {
        try (StubMirror mirror = newStubMirror(mirrorId)) {
            int requestId = makeRequestFor(mirror);
            acceptRequest(requestId);
            try {
                rejectRequest(requestId, CONFLICT);
            } finally {
                MirroringInstallationHelper.uninstall(mirror, NO_CONTENT, NOT_FOUND);
            }
        }
    }

    @Test
    public void testConflictIfRegisteringForAlreadyAcceptedRequest() throws Exception {
        try (StubMirror mirror = newStubMirror(mirrorId)) {
            int requestId = makeRequestFor(mirror);
            acceptRequest(requestId);
            try {
                createMirroringRequest(mirror.getId(), mirror.getDescriptorUrl(), mirror.getBaseUrl(), CONFLICT);
            } finally {
                MirroringInstallationHelper.uninstall(mirror, NO_CONTENT, NOT_FOUND);
            }
        }
    }

    private StubMirror newStubMirror(String mirrorId) {
        StubMirror mirror = new StubMirror(mirrorId);
        mirror.configureDefaultResponders();
        return mirror;
    }

    private int makeRequestFor(String mirrorId, String baseUrl) {
        MutableInt requestId = new MutableInt();
        testContext.mirroringRequest(mirrorId, baseUrl, getDescriptorUrl(baseUrl, mirrorId),
                (response) -> requestId.setValue(getIdFromResponse(response)));
        return requestId.getValue();
    }

    private int makeRequestFor(StubMirror mirror) {
        MutableInt requestId = new MutableInt();
        testContext.mirroringRequest(mirror.getId(), mirror.getBaseUrl(), mirror.getDescriptorUrl(),
                (response) -> requestId.setValue(getIdFromResponse(response)));
        return requestId.getValue();
    }

    private Response getRequestPage(int start, int limit) {
        return getRequestPage(start, limit, null);
    }

    private Response getRequestPage(int start, int limit, String state) {
        RequestSpecification req = given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .queryParam("start", start)
                .queryParam("limit", limit);

        if (state != null) {
            req.queryParam("state", state);
        }

        return req.expect()
                .statusCode(OK.getStatusCode())
                .when().get(getMirroringRequestsUrl());
    }

    private void verifyPage(int size, boolean isLastPage, Response response) {
        JsonPath jsonPath = response.getBody().jsonPath();
        assertEquals(size, jsonPath.getInt("size"));
        assertEquals(isLastPage, jsonPath.getBoolean("isLastPage"));
    }

    private Response verifyRequest(String mirrorId, String state, String baseUrl, Response response) {
        JsonPath jsonPath = response.getBody().jsonPath();
        int id = jsonPath.getInt("id");
        assertNotNull(id);
        assertEquals(String.valueOf(mirrorId), jsonPath.getString("mirrorId"));
        assertEquals(mirrorId, jsonPath.getString("mirrorName"));
        assertEquals("test-mirror", jsonPath.getString("productType"));
        assertEquals("none", jsonPath.getString("productVersion"));
        assertEquals(getDescriptorUrl(DEFAULT_BASEURL, mirrorId), jsonPath.getString("addonDescriptorUri"));
        assertEquals(baseUrl, jsonPath.getString("mirrorBaseUrl"));
        assertEquals(state, jsonPath.getString("state"));

        return response;
    }
}
