package com.atlassian.bitbucket.user;


import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionRequest;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Set;

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

/**
 * Request for searching users. The purpose and behaviour of each field is described on its accessor.
 */
public class UserSearchRequest {

    private final String filter;
    private final String group;
    private final Set<PermissionRequest> permissions;

    private UserSearchRequest(Builder builder) {
        filter = builder.filter;
        group = builder.group;
        permissions = ImmutableSet.copyOf(builder.permissions);
    }

    /**
     * When set, limits returned {@link ApplicationUser users} to only those whose
     * {@link ApplicationUser#getDisplayName() display names}, {@link ApplicationUser#getEmailAddress() e-mail addresses}
     * or {@link ApplicationUser#getName() usernames} <i>contain</i> the provided filter.
     * <p>
     * If this field is not set, users shall be returned regardless of their
     * {@link ApplicationUser#getDisplayName() display names} or {@link ApplicationUser#getEmailAddress() e-mail addresses}
     * or {@link ApplicationUser#getName() usernames}.
     *
     * @return filter text to match against display names or e-mail addresses or usernames,
     * or {@code null} to return users regardless of display names or e-mail addresses or usernames.
     */
    @Nullable
    public String getFilter() {
        return filter;
    }

    /**
     * When set, limits returned {@link ApplicationUser users} to only those who are members of the specified group.
     * <p>
     * If this field is not set, users shall be returned regardless of their group membership.
     *
     * @return group name to match against user's group membership,
     * or {@code null} to return users regardless of their group membership.
     * @since 4.4
     */
    @Nullable
    public String getGroup() {
        return group;
    }

    /**
     * When set, limits returned {@link ApplicationUser users} to only those who have <i>all</i> permissions as specified by
     * each contained {@link PermissionRequest permission request}.
     * <p>
     * An empty set indicates that users shall be returned regardless of their {@link Permission}.
     *
     * @return the required permission(s), or an empty set to return all users regardless of permission.
     */
    @Nonnull
    public Set<PermissionRequest> getPermissions() {
        return permissions;
    }

    public static class Builder {

        private String filter;
        private String group;
        private final Set<PermissionRequest> permissions = new HashSet<>();

        public Builder() {
        }

        public Builder(@Nonnull UserSearchRequest request) {
            this.filter = request.filter;
            this.group = request.group;
            this.permissions.addAll(request.getPermissions());
        }

        /**
         * Assembles a new {@link UserSearchRequest} from the provided values.
         *
         * @return a new request
         */
        @Nonnull
        public UserSearchRequest build() {
            return new UserSearchRequest(this);
        }

        /**
         * Set the filter for the resulting {@link UserSearchRequest request} instance.
         *
         * @param filter filter text for the user search; may be blank, which will be treated
         *              as an empty filter
         * @return this builder instance
         * @see UserSearchRequest#getFilter()
         */
        @Nonnull
        public Builder filter(@Nullable String filter) {
            this.filter = StringUtils.trimToNull(filter);

            return this;
        }

        /**
         * Set a group filter for the resulting {@link UserSearchRequest request} instance.
         *
         * @param group the group name for the user search; may be blank
         * @return this builder instance
         * @see UserSearchRequest#getGroup()
         * @since 4.4
         */
        @Nonnull
        public Builder group(@Nullable String group) {
            this.group = StringUtils.trimToNull(group);

            return this;
        }

        /**
         * Set the global permission filter for the resulting {@link UserSearchRequest request}.
         *
         * @param permission filter permission for the user search
         * @return this builder instance
         */
        @Nonnull
        public Builder permission(@Nonnull Permission permission) {
            permissions.add(new PermissionRequest.Builder(permission).build());

            return this;
        }

        /**
         * Set the project permission filter for the resulting {@link UserSearchRequest request}.
         *
         * @param project    filter project for the user search
         * @param permission filter project permission for the user search
         * @return this builder instance
         */
        @Nonnull
        public Builder projectPermission(@Nonnull Project project, @Nonnull Permission permission) {
            checkNotNull(project, "project");
            permissions.add(new PermissionRequest.Builder(permission).resource(project).build());

            return this;
        }

        /**
         * Set the project permission filter for the resulting {@link UserSearchRequest request}.
         * <p>
         * NOTE: passing in an ID of a non-existing project will <i>not</i> filter out any users.
         *
         * @param projectId ID of the filter project for the user search
         * @param permission filter project permission for the user search
         * @return this builder instance
         */
        @Nonnull
        public Builder projectPermission(int projectId, @Nonnull Permission permission) {
            permissions.add(new PermissionRequest.Builder(permission).resource(projectId).build());

            return this;
        }

        /**
         * Set the repository permission filter for the resulting {@link UserSearchRequest request}.
         *
         * @param repository filter repository for the user search
         * @param permission filter repository permission for the user search
         * @return this builder instance
         */
        @Nonnull
        public Builder repositoryPermission(@Nonnull Repository repository, @Nonnull Permission permission) {
            checkNotNull(repository, "repository");
            permissions.add(new PermissionRequest.Builder(permission).resource(repository).build());

            return this;
        }

        /**
         * Set the repository permission filter for the resulting {@link UserSearchRequest request}.
         * <p>
         * NOTE: passing in an ID of a non-existing repository will <i>not</i> filter out any users.
         *
         * @param repositoryId ID of the filter repository for the user search
         * @param permission filter repository permission for the user search
         * @return this builder instance
         */
        @Nonnull
        public Builder repositoryPermission(int repositoryId, @Nonnull Permission permission) {
            permissions.add(new PermissionRequest.Builder(permission).resource(repositoryId).build());

            return this;
        }
    }
}
