package com.atlassian.stash.internal.user;

import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.permission.Permission;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;

/**
 * Spring security Authentication token that holds the ApplicationUser for the logged in user, and a collection of
 * global {@link Permission}s that have been granted to the current user session. This mechanism is used to
 * temporally run with elevated permissions.
 */
public class StashUserAuthenticationToken extends AbstractAuthenticationToken implements PermissionGraph {

    private final PermissionGraph elevatedPermissions;
    private final ApplicationUser user;

    private PermissionGraph permissions;

    private StashUserAuthenticationToken(Builder builder) {
        super(Collections.<GrantedAuthority>emptySet());
        this.user = builder.user;
        this.elevatedPermissions = builder.elevatedPermissions;
        this.permissions = builder.permissions;

        setAuthenticated(true);
    }

    public static StashUserAuthenticationToken forUser(ApplicationUser user) {
        return new Builder().user(user).build();
    }

    @Nonnull
    public StashUserAuthenticationToken copyWithUser(@Nullable ApplicationUser user) {
        return new StashUserAuthenticationToken(new Builder(this).user(user));
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    public PermissionGraph getElevatedPermissions() {
        return elevatedPermissions;
    }

    @Override
    public String getName() {
        return user == null ? "<anonymous>" : user.getName();
    }

    public PermissionGraph getPermissions() {
        return permissions;
    }

    @Override
    public ApplicationUser getPrincipal() {
        return user;
    }

    @Override
    public boolean isGranted(Permission permission, @Nullable Object resource) {
        return isGranted(elevatedPermissions, permission, resource) ||
                isGranted(permissions, permission, resource);
    }

    public void setPermissions(PermissionGraph permissions) {
        this.permissions = permissions;
    }

    @Override
    public String toString() {
        return "StashUserAuthenticationToken{user=" + user + ", elevatedPermissions=" + elevatedPermissions + "}";
    }

    private boolean isGranted(PermissionGraph graph, Permission permission, Object resource) {
        return graph != null && permission != null && graph.isGranted(permission, resource);
    }

    public static class Builder {
        private PermissionGraph permissions;
        private PermissionGraph elevatedPermissions;
        private ApplicationUser user;

        public Builder() {
        }

        public Builder(StashUserAuthenticationToken token) {
            elevatedPermissions = token.elevatedPermissions;
            permissions = token.permissions;
            user = token.user;
        }

        public StashUserAuthenticationToken build() {
            return new StashUserAuthenticationToken(this);
        }

        public Builder elevatedPermissions(PermissionGraph value) {
            elevatedPermissions = value;
            return this;
        }

        public Builder permissions(PermissionGraph value) {
            permissions = value;
            return this;
        }

        public Builder user(ApplicationUser value) {
            user = value;
            return this;
        }
    }
}
