package com.atlassian.bitbucket.web.conditions;

import com.atlassian.plugin.PluginParseException;
import com.atlassian.plugin.web.Condition;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.user.Person;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

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

/**
 * An abstract base class for creating permission-based conditions. The permission will be parsed from the
 * {@link #init(Map) initialisation context} and validated by this base class. Derived classes then implement
 * {@link #shouldDisplay(Map)} to make the desired check using the {@link PermissionService}.
 */
public abstract class AbstractPermissionCondition implements Condition {

    /**
     * Defines the key from the {@link #init(Map) initialisation context} under which the permission name is expected
     * to be set.
     */
    public static final String PERMISSION = "permission";
    public static final String CONTEXT_PARAM = "contextParam";

    /**
     * Allows derived implementations to access the {@link PermissionService}.
     */
    protected final PermissionService permissionService;

    private final boolean global;

    private Permission permission;
    private String contextParam;

    /**
     * Constructs a new {@code AbstractPermissionCondition} which will perform checks using the specified
     * {@link PermissionService}. The provided {@code global} flag is used during {@link #init(Map) initialisation},
     * to validate the {@link Permission#isGlobal() global} flag of the parsed permission.
     *
     * @param permissionService the service to use when performing the permission check
     * @param global            {@code true} if the initialised permission is required to be global; otherwise,
     *                          {@code false} to required a non-global permission
     */
    protected AbstractPermissionCondition(PermissionService permissionService, boolean global) {
        this.global = global;
        this.permissionService = permissionService;
    }

    /**
     * Retrieves the {@link #init(Map) initialised} permission.
     *
     * @return the initialised permission
     */
    public Permission getPermission() {
        return permission;
    }

    /**
     * Retrieves the {@link #PERMISSION permission} from the context and {@link #getPermission() stores it}. The
     * {@link Permission#isGlobal() global} flag of the parsed permission is validated against the flag set when
     * the instance was constructed.
     *
     * @param params the initialisation context
     * @throws PluginParseException Thrown if no {@link #PERMISSION permission} is set, or if the set value is not a
     *                              valid {@link Permission}, or the permission's {@link Permission#isGlobal() global}
     *                              flag does not match the required value.
     */
    @Override
    public void init(Map<String, String> params) throws PluginParseException {
        String permissionName = params.get(PERMISSION);
        if (StringUtils.isBlank(permissionName)) {
            throw new PluginParseException(getClass().getSimpleName() + ": The [" + PERMISSION +
                    "] param is required, and must define a " + getType() + " permission");
        }

        try {
            permission = Permission.valueOf(permissionName);
            checkArgument(global == permission.isGlobal(), "%s requires a %s permission",
                    getClass().getSimpleName(), getType());
        } catch (IllegalArgumentException e) {
            throw new PluginParseException(e.getMessage());
        }
        contextParam = params.get(CONTEXT_PARAM);

    }

    @Override
    public boolean shouldDisplay(Map<String, Object> context) {
        if (contextParam != null && context.containsKey(contextParam)) {
            Object userObject = context.get(contextParam);
            if (userObject instanceof ApplicationUser) {
                return hasPermission(context, (ApplicationUser) userObject);
            } else if (userObject instanceof String) {
                return hasPermission(context, (String) userObject);
            } else if (userObject instanceof Person) {
                return hasPermission(context, ((Person) userObject).getName());
            } else {
                return hasPermission(context, (ApplicationUser) null);
            }
        } else {
            return hasPermission(context);
        }
    }

    protected boolean hasPermission(Map<String, Object> context) {
        return false;
    }

    protected boolean hasPermission(Map<String, Object> context, ApplicationUser user) {
        return false;
    }

    protected boolean hasPermission(Map<String, Object> context, String userName) {
        return false;
    }

    /**
     * Retrieves a string describing the required permission type.
     *
     * @return {@code "global"} or {@code "non-global} depending on the flag set during construction
     */
    protected String getType() {
        return global ? "global" : "non-global";
    }
}
