package com.atlassian.bitbucket.test;

import com.atlassian.bitbucket.test.rest.AbstractRestHelper;
import com.atlassian.bitbucket.test.rest.RestAuthentication;
import com.google.common.collect.ImmutableMap;
import com.jayway.restassured.response.Response;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;

import javax.ws.rs.core.Response.Status;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.OK;

public class WebHookTestHelper extends AbstractRestHelper {

    public WebHookTestHelper(String username, String password) {
        super(RestAuthentication.authenticated(username, password));
    }

    public Response create(String name, String callbackUrl, List<String> eventIds, Map<String, String> filters, Status expectedStatus) {
        return request()
                .log().ifValidationFails()
                .body(createRegistrationBody(name, callbackUrl, filters, eventIds))
                .expect().statusCode(expectedStatus.getStatusCode())
                .log().ifValidationFails()
                .when().post(getUrl());
    }

    public Response delete(String selfUrl) {
        return request()
                .expect().statusCode(NO_CONTENT.getStatusCode())
                .when().delete(selfUrl);
    }

    public Response list() {
        return request()
                .expect().statusCode(OK.getStatusCode())
                .when().get(getUrl());
    }

    public String getUrl() {
        return DefaultFuncTestData.getRestURL("webhooks", "latest") + "/webhook";
    }

    public String getUrl(int id) {
        return DefaultFuncTestData.getRestURL("webhooks", "latest") + "/webhook/" + id;
    }

    public String createRegistrationBody(String name, String callbackUrl, Map<String, String> filters, List<String> events) {
        JSONObject json = new JSONObject();
        json.put("name", name);
        json.put("description", name);
        json.put("url", callbackUrl);
        JSONArray jsonEvents = new JSONArray();
        jsonEvents.addAll(events);
        json.put("events", jsonEvents);

        // In essence the properties under 'filters' in the JSON payload are opaque string value. In the case of
        // Bitbucket Server they should be stringified JSON object which specifies the project/repo id.
        // The problem with this net.sf.json is it is too eager to turn a string value which *looks* like a JSON object
        // *into* a JSON object and so undoes any escaping (and Connect throws a fit because the filter is no longer a
        // string but an object). So this code substitutes each filter value for "%s" and then the generated JSON entity
        // (with the %s in place) are replaced with escaped versions of the string values.
        // e.g.
        // If this is what we want:
        // ...
        //   'filters': {
        //     'resource' : '{ \'projectId\' : 1 }'
        //   }
        // ...
        // This is what net.sf.json insists on giving us without doing what we do:
        // ...
        //   'filters': {
        //     'resource' : { 'projectId' : 1 }
        //   }
        // ...
        JSONObject jsonParams = new JSONObject();
        // substitute each filter value for '%s'
        jsonParams.putAll(filters.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> "%s")));
        json.put("filters", jsonParams);
        // generate the JSON payload and then apply String.format to it passing in the filter values properly escaped
        // as JSON strings
        return String.format(json.toString(), filters.entrySet().stream()
                .map(Map.Entry::getValue)
                .map(JSONUtils::quote)
                .map(str -> str.substring(1, str.length() - 1))
                .toArray());
    }

    public static Map<String, String> filterOnProjectWithId(int id) {
        return ImmutableMap.of("resource", projectFilterEntry(id));
    }

    public static String projectFilterEntry(int id) {
        return "{\"projectId\":\"" + id + "\"}";
    }

    public static Map<String, String> filterOnRepositoryWithId(int id) {
        return ImmutableMap.of("resource", repositoryFilterEntry(id));
    }

    public static String repositoryFilterEntry(int id) {
        return "{\"repositoryId\":\"" + id + "\"}";
    }
}
