package com.atlassian.bitbucket.test;

import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.Response;
import net.sf.json.util.JSONStringer;

import javax.ws.rs.core.MediaType;
import java.io.File;
import java.util.LinkedHashSet;
import java.util.Set;

import static com.atlassian.bitbucket.test.RestTestHelper.hasValue;
import static javax.ws.rs.core.Response.Status.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class UserTestHelper {

    public static final String SEND_MODE_IMMEDIATE = "IMMEDIATE";
    public static final String SEND_MODE_BATCHED = "BATCHED";

    static final String CONTENT_TYPE = MediaType.APPLICATION_JSON + ";charset=UTF-8";

    public static int countUsers() {
        return getAllUsers().getInt("size");
    }

    public static Set<String> allUsernames() {
        JsonPath path = getAllUsers();
        int size = path.getInt("size");

        Set<String> usernames = new LinkedHashSet<>(size, 1.0f);
        for (int i = 0; i < size; i++) {
            usernames.add(path.getString("values[" + i + "].name"));
        }

        return usernames;
    }

    private static JsonPath getAllUsers() {
        Response get = RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .given()
                .queryParam("start", 0)
                .queryParam("limit", Short.MAX_VALUE) //We assume there are less users than Short.MAX_VALUE
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(CONTENT_TYPE)
                .when()
                .get(DefaultFuncTestData.getRestURL() + "/admin/users");

        return get.getBody().jsonPath();
    }

    public static void createUsers(boolean addToDefaultGroup, String... usernames) {
        for (String username : usernames) {
            createUser(username, username, username, username + "@example.com", addToDefaultGroup);
        }
    }

    public static void createUser(String username, String displayName, String password, String emailAddress) {
        createUser(username, displayName, password, emailAddress, true);
    }

    public static void createUser(String username, String displayName, String password, String emailAddress, boolean addToDefaultGroup) {
        Response post = RestAssured.given()
                .queryParam("name", username)
                .queryParam("password", password)
                .queryParam("emailAddress", emailAddress)
                .queryParam("displayName", displayName)
                .queryParam("addToDefaultGroup", addToDefaultGroup)
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(CONTENT_TYPE)
                .when()
                .post(DefaultFuncTestData.getRestURL() + "/admin/users");

        assertEquals("The user " + username + " could not be created\n" + dumpResponse(post),
                     NO_CONTENT.getStatusCode(), post.getStatusCode());
    }

    public static void deleteUsers(String... usernames) {
        deleteUsers(false, usernames);
    }

    /**
     * Delete the specified users. Note that associated user data is not immediately cleaned up. So depending on the
     * test setup, user data has to be deleted separately.
     */
    public static void deleteUsers(boolean ignoreIfNotPresent, String... usernames) {
        for (String username : usernames) {
            // Explicitly revoke permission because of delayed user cleanup
            PermissionsTestHelper.revokeGlobalPermissionsForUser(username);
            Response response = RestAssured.given()
                    .queryParam("name", username)
                    .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType(CONTENT_TYPE)
                    .when()
                    .delete(DefaultFuncTestData.getRestURL() + "/admin/users");

            int status = response.getStatusCode();
            if (ignoreIfNotPresent) {
                assertTrue("The user " + username + " could not be deleted\n" + dumpResponse(response),
                        OK.getStatusCode() == status || NOT_FOUND.getStatusCode() == status);
            } else {
                assertEquals("The user " + username + " could not be deleted\n" + dumpResponse(response),
                        OK.getStatusCode(), status);
            }
        }
    }

    public static boolean userExists(String user) {
        Response response = RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .given()
                .queryParam("filter", user)
                .parameter("start", "0")
                .parameter("limit", Short.MAX_VALUE) //Ensure we don't return false because the user is on another page
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(CONTENT_TYPE)
                .when()
                .get(DefaultFuncTestData.getRestURL() + "/admin/users");

        return hasUser(response, user);
    }

    public static void uploadAvatar(String username, File avatar) {
        RestAssured
                .given()
                    .auth().preemptive().basic(username, username)
                    .multiPart("avatar", avatar, "image/png") // assume the avatar is always a PNG image
                    .header(DefaultFuncTestData.HDR_XSRF_NO_CHECK)
                .expect()
                    .statusCode(CREATED.getStatusCode())
                    .log().ifError()
                .when()
                    .post(DefaultFuncTestData.getRestURL() + "/users/" + username + "/avatar.png");
    }

    public static void deleteAvatar(String username) {
        RestAssured
                .given()
                    .auth().preemptive().basic(username, username)
                    .header(DefaultFuncTestData.HDR_XSRF_NO_CHECK)
                .expect()
                    .statusCode(OK.getStatusCode())
                    .log().ifError()
                .when()
                    .delete(DefaultFuncTestData.getRestURL() + "/users/" + username + "/avatar.png");
    }



    private static String dumpResponse(Response response) {
        StringBuilder sb = new StringBuilder();
        sb.append("------------------")
                .append("\n")
                .append("Response from the server:")
                .append("\n")
                .append(response.getStatusLine())
                .append("\n")
                .append(response.getBody().asString())
                .append("\n")
                .append("------------------")
                .append("\n");
        return sb.toString();
    }

    public static Response doInvalidLogin(String user, int times) {
        //Hit a resource with an invalid login
        Response response = null;

        for (int i = 0; i < times; i++) {
            response = RestAssured.given()
                    .auth().preemptive().basic(user, "wrong")
                    .contentType("application/json")
                    .when()
                    .get(DefaultFuncTestData.getRestURL() + "/projects");
        }

        return response;
    }

    public static boolean hasUser(Response response, String username) {
        return hasValue(response, "name", username, true);
    }

    public static void setNotificationSendMode(String user, String sendMode) {
        setNotificationSendMode(user, user, sendMode);
    }

    public static void setNotificationSendMode(String user, String password, String sendMode) {
        String jsonBody = new JSONStringer().object().key("sendMode").value(sendMode).endObject().toString();
        RestAssured
                .given()
                    .auth().preemptive().basic(user, password)
                    .contentType(ContentType.JSON)
                    .body(jsonBody)
                .when()
                    .put(DefaultFuncTestData.getRestURL("notification", "latest") + "/settings")
                .then()
                    .statusCode(NO_CONTENT.getStatusCode())
                    .log().ifValidationFails();
    }
}
