package com.atlassian.stash.internal.rest.user;

import com.atlassian.bitbucket.rest.BadRequestException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.stash.internal.rest.util.RestPermissionUtils;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.project.ProjectService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.user.UserSearchRequest;

import javax.annotation.Nonnull;
import javax.ws.rs.core.MultivaluedMap;

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

/**
 * Parses REST request to "/users" and translates into {@link UserSearchRequest}.
 */
public class RestUserSearchRequestParser {

    private static final int MAX_PERMISSION_FILTERS = 50;

    private static final String PARAM_FILTER = "filter";
    private static final String PARAM_GROUP = "group";
    private static final String PARAM_PERMISSION = "permission";

    private static final String SUFFIX_PROJECT_ID = ".projectId";
    private static final String SUFFIX_PROJECT_KEY = ".projectKey";
    private static final String SUFFIX_REPOSITORY_ID = ".repositoryId";
    private static final String SUFFIX_REPOSITORY_SLUG = ".repositorySlug";

    private final I18nService i18nService;
    private final ProjectService projectService;
    private final RepositoryService repositoryService;

    public RestUserSearchRequestParser(I18nService i18nService, ProjectService projectService,
                                RepositoryService repositoryService) {
        this.i18nService = i18nService;
        this.projectService = projectService;
        this.repositoryService = repositoryService;
    }

    @Nonnull
    public UserSearchRequest parse(@Nonnull MultivaluedMap<String, String> request) {
        checkNotNull(request, "request");
        UserSearchRequest.Builder requestBuilder = new UserSearchRequest.Builder()
                .filter(request.getFirst(PARAM_FILTER))
                .group(request.getFirst(PARAM_GROUP));

        int maxFilters = MAX_PERMISSION_FILTERS;
        // parse "permission"
        if (maybeSetPermission(requestBuilder, request, PARAM_PERMISSION)) {
            maxFilters--;
        }

        // keep parsing "permission.1", "permission.2" etc. as long as present, or a hard limit of filters is reached
        for (int i = 1; i <= maxFilters; i++) {
            if (!maybeSetPermission(requestBuilder, request, PARAM_PERMISSION + "." + i)) {
                break;
            }
        }

        return requestBuilder.build();
    }

    private Project getProjectOrFail(String projectKey, String context) {
        Project project = projectService.getByKey(projectKey);
        if (project == null) {
            throw new BadRequestException(context, i18nService.getMessage("bitbucket.rest.nosuchproject", projectKey));
        }

        return project;
    }

    private Project getProjectOrFail(int projectId, String context) {
        Project project = projectService.getById(projectId);
        if (project == null) {
            throw new BadRequestException(context,
                    i18nService.getMessage("bitbucket.rest.nosuchproject.byid", Integer.toString(projectId)));
        }

        return project;
    }

    private Repository getRepositoryOrFail(String projectKey, String repositorySlug, String context) {
        Repository repository = repositoryService.getBySlug(projectKey, repositorySlug);
        if (repository == null) {
            throw new BadRequestException(context,
                    i18nService.getMessage("bitbucket.rest.nosuchrepo", projectKey, repositorySlug));
        }

        return repository;
    }

    private Repository getRepositoryOrFail(int repositoryId, String context) {
        Repository repository = repositoryService.getById(repositoryId);
        if (repository == null) {
            throw new BadRequestException(context,
                    i18nService.getMessage("bitbucket.rest.nosuchrepo.byid", Integer.toString(repositoryId)));
        }

        return repository;
    }

    private boolean maybeSetPermission(UserSearchRequest.Builder builder, MultivaluedMap<String, String> request,
                                       String permissionParam) {
        Permission permission = RestPermissionUtils.parsePermission(i18nService, request.getFirst(permissionParam),
                null, null, permissionParam);
        if (permission == null) {
            return false;
        }
        if (permission.isGlobal()) {
            builder.permission(permission);

            return true;
        }
        if (permission.isResource(Repository.class)) {
            return maybeSetRepositoryPermission(builder, request, permissionParam, permission);
        }
        if (permission.isResource(Project.class)) {
            return maybeSetProjectPermission(builder, request, permissionParam, permission);
        }

        throw new BadRequestException(permissionParam, i18nService.getMessage("bitbucket.rest.permission.unsupported",
                permission));
    }

    private boolean maybeSetRepositoryPermission(UserSearchRequest.Builder builder, MultivaluedMap<String, String> request,
                                                 String permissionParam, Permission permission) {
        String repositoryIdParam = permissionParam + SUFFIX_REPOSITORY_ID;
        if (request.containsKey(repositoryIdParam)) {
            int repositoryId = parseInt(repositoryIdParam, request.getFirst(repositoryIdParam));
            builder.repositoryPermission(getRepositoryOrFail(repositoryId, repositoryIdParam), permission);

            return true;
        }

        String projectKey = request.getFirst(permissionParam + SUFFIX_PROJECT_KEY);
        String repositorySlug = request.getFirst(permissionParam + SUFFIX_REPOSITORY_SLUG);
        if (projectKey != null && repositorySlug != null) {
            builder.repositoryPermission(getRepositoryOrFail(projectKey, repositorySlug,
                    permissionParam + SUFFIX_REPOSITORY_SLUG), permission);

            return true;
        }

        throw new BadRequestException(permissionParam,
                i18nService.getMessage("bitbucket.rest.permission.invalidrepositoryrequest", permissionParam));
    }

    private boolean maybeSetProjectPermission(UserSearchRequest.Builder builder, MultivaluedMap<String, String> request,
                                              String permissionParam, Permission permission) {
        String projectIdParam = permissionParam + SUFFIX_PROJECT_ID;
        if (request.containsKey(projectIdParam)) {
            int projectId = parseInt(projectIdParam, request.getFirst(projectIdParam));
            builder.projectPermission(getProjectOrFail(projectId, projectIdParam), permission);

            return true;
        }

        String projectKey = request.getFirst(permissionParam + SUFFIX_PROJECT_KEY);
        if (projectKey != null) {
            builder.projectPermission(getProjectOrFail(projectKey, permissionParam + SUFFIX_PROJECT_KEY), permission);

            return true;
        }

        throw new BadRequestException(permissionParam, i18nService.getMessage(
                "bitbucket.rest.permission.invalidprojectrequest", permissionParam));
    }

    private int parseInt(String context, String intValue) {
        try {
            return Integer.parseInt(intValue);
        } catch (NumberFormatException e) {
            throw new BadRequestException(context, i18nService.getMessage(
                    "bitbucket.rest.nan", intValue));
        }
    }
}
