/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.atlas.authorize.simple;

import org.apache.atlas.AtlasClient;
import org.apache.atlas.authorize.AtlasActionTypes;
import org.apache.atlas.authorize.AtlasResourceTypes;
import org.apache.atlas.authorize.AtlasAuthorizationException;
import org.apache.atlas.authorize.AtlasAuthorizer;
import org.apache.atlas.authorize.AtlasAccessRequest;
import org.apache.atlas.authorize.AtlasAuthorizerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

public class AtlasAuthorizationUtils {
    private static final Logger LOG = LoggerFactory.getLogger(AtlasAuthorizationUtils.class);
    private static boolean isDebugEnabled = LOG.isDebugEnabled();
    private static final String BASE_URL = "/" + AtlasClient.BASE_URI;

    public static String getApi(String contextPath) {
        if (isDebugEnabled) {
            LOG.debug("==> getApi({})", contextPath);
        }
        if (contextPath.startsWith(BASE_URL)) {
            contextPath = contextPath.substring(BASE_URL.length());
        } else {
            // strip of leading '/'
            if (contextPath.startsWith("/")) {
                contextPath = contextPath.substring(1);
            }
        }
        String[] split = contextPath.split("/", 3);

        String api = split[0];
        if(Pattern.matches("v\\d", api)) {
            api = split[1];
        }

        if (isDebugEnabled) {
            LOG.debug("<== getApi({}): {}", contextPath, api);
        }

        return api;
    }

    public static AtlasActionTypes getAtlasAction(String method) {
        AtlasActionTypes action = null;

        switch (method.toUpperCase()) {
            case "POST":
                action = AtlasActionTypes.CREATE;
                break;
            case "GET":
                action = AtlasActionTypes.READ;
                break;
            case "PUT":
                action = AtlasActionTypes.UPDATE;
                break;
            case "DELETE":
                action = AtlasActionTypes.DELETE;
                break;
            default:
                if (isDebugEnabled) {
                    LOG.debug("getAtlasAction(): Invalid HTTP method '{}", method);
                }
                break;
        }

        if (isDebugEnabled) {
            LOG.debug("<== AtlasAuthorizationFilter getAtlasAction HTTP Method {} mapped to AtlasAction : {}",
                    method, action);
        }
        return action;
    }

    /**
     * @param contextPath
     * @return set of AtlasResourceTypes types api mapped with AtlasResourceTypes.TYPE eg :- /api/atlas/types/*
     *
     *         gremlin discovery,admin,graph apis are mapped with AtlasResourceTypes.OPERATION eg :-/api/atlas/admin/*
     *         /api/atlas/discovery/search/gremlin /api/atlas/graph/*
     *
     *         entities,lineage and discovery apis are mapped with AtlasResourceTypes.ENTITY eg :- /api/atlas/lineage/hive/table/*
     *         /api/atlas/entities/{guid}* /api/atlas/discovery/*
     * 
     *         taxonomy API are also mapped to AtlasResourceTypes.TAXONOMY & AtlasResourceTypes.ENTITY and its terms APIs have
     *         added AtlasResourceTypes.TERM associations.
     *
     *         unprotected types are mapped with AtlasResourceTypes.UNKNOWN, access to these are allowed.
     */
    public static Set<AtlasResourceTypes> getAtlasResourceType(String contextPath) {
        Set<AtlasResourceTypes> resourceTypes = new HashSet<>();
        if (isDebugEnabled) {
            LOG.debug("==> getAtlasResourceType  for {}", contextPath);
        }
        String api = getApi(contextPath);
        if (api.startsWith("types")) {
            resourceTypes.add(AtlasResourceTypes.TYPE);
        } else if (api.startsWith("admin") && (contextPath.contains("/session") || contextPath.contains("/version"))) {
            resourceTypes.add(AtlasResourceTypes.UNKNOWN);
        } else if ((api.startsWith("discovery") && contextPath.contains("/gremlin")) || api.startsWith("admin")
                || api.startsWith("graph")) {
            resourceTypes.add(AtlasResourceTypes.OPERATION);
        } else if (api.startsWith("entities") || api.startsWith("lineage") ||
                api.startsWith("discovery") || api.startsWith("entity")  || api.startsWith("search")) {
            resourceTypes.add(AtlasResourceTypes.ENTITY);
        } else if (api.startsWith("taxonomies")) {
            resourceTypes.add(AtlasResourceTypes.TAXONOMY);
            // taxonomies are modeled as entities
            resourceTypes.add(AtlasResourceTypes.ENTITY);
            if (contextPath.contains("/terms")) {
                resourceTypes.add(AtlasResourceTypes.TERM);
            }
        } else {
            LOG.error("Unable to find Atlas Resource corresponding to : {}\nSetting {}"
                , api, AtlasResourceTypes.UNKNOWN.name());
            resourceTypes.add(AtlasResourceTypes.UNKNOWN);
        }

        if (isDebugEnabled) {
            LOG.debug("<== Returning AtlasResources {} for api {}", resourceTypes, api);
        }
        return resourceTypes;
    }

    public static boolean isAccessAllowed(AtlasResourceTypes resourcetype, AtlasActionTypes actionType, String userName, Set<String> groups) {
        AtlasAuthorizer authorizer = null;
        boolean isaccessAllowed = false;

        Set<AtlasResourceTypes> resourceTypes = new HashSet<>();
        resourceTypes.add(resourcetype);
        AtlasAccessRequest atlasRequest = new AtlasAccessRequest(resourceTypes, "*", actionType, userName, groups);
        try {
            authorizer = AtlasAuthorizerFactory.getAtlasAuthorizer();
            if (authorizer != null) {
                isaccessAllowed = authorizer.isAccessAllowed(atlasRequest);
            }
        } catch (AtlasAuthorizationException e) {
            LOG.error("Unable to obtain AtlasAuthorizer. ", e);
        }

        return isaccessAllowed;
    }
}
