package com.atlassian.stash.internal.web.repos;

import com.atlassian.event.api.EventPublisher;
import com.atlassian.bitbucket.event.repository.RepositoryAccessedEvent;
import com.atlassian.bitbucket.AuthorisationException;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Intercepts handler requests and, if a repository is being accessed e.g. accessing /projects/KEY/repo/SLUG/...,
 * fires a {@link RepositoryAccessedEvent}.
 *
 * @since 1.1
 */
public class RepositoryAccessInterceptor extends HandlerInterceptorAdapter {

    //Note the leading ~? before the project key's expression; that allows the expression to match accesses to
    //repositories in personal projects
    private static final Pattern REPO_PATH_PATTERN = Pattern.compile(String.format("/projects/(~?%s)/repos/(%s)/.*",
            Project.KEY_REGEXP, Repository.SLUG_REGEXP));

    private static final Logger log = LoggerFactory.getLogger(RepositoryAccessInterceptor.class);

    private final EventPublisher eventPublisher;
    private final RepositoryService repositoryService;

    public RepositoryAccessInterceptor(EventPublisher eventPublisher, RepositoryService repositoryService) {
        this.eventPublisher = eventPublisher;
        this.repositoryService = repositoryService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String pathInfo = request.getPathInfo();
        if (pathInfo != null) {
            Matcher matcher = REPO_PATH_PATTERN.matcher(pathInfo);
            if (matcher.matches()) {
                maybeFireRepositoryAccessed(matcher.group(1), matcher.group(2));
            }
        }

        return true;
    }

    private void maybeFireRepositoryAccessed(String projectKey, String repositorySlug) {
        //One approach to handling this is to look for personal projects/repositories directly, but that may conflict
        //with repository permissions. A better approach is probably to just handle the authorisation exception, the
        //approach taken here.
        try {
            Repository repository = repositoryService.getBySlug(projectKey, repositorySlug);
            if (repository != null) {
                eventPublisher.publish(new RepositoryAccessedEvent(this, repository));
            }
        } catch (AuthorisationException e) {
            //The current user is not actually allowed to access _either_ the project or the repository. Regardless
            //of which is off limits, there shouldn't be an access raised for this
        } catch (Exception e) {
            log.warn("Failed to fire event for repository access", e);
        }
    }
}
