package fr.auchan.nexus3.gitlabauth.plugin.api;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import fr.auchan.nexus3.gitlabauth.plugin.GitlabAuthenticationException;
import fr.auchan.nexus3.gitlabauth.plugin.GitlabPrincipal;
import fr.auchan.nexus3.gitlabauth.plugin.config.GitlabAuthConfiguration;
import org.gitlab.api.GitlabAPI;
import org.gitlab.api.Pagination;
import org.gitlab.api.models.GitlabGroup;
import org.gitlab.api.models.GitlabUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Singleton
@Named("GitlabApiClient")
public class GitlabApiClient {
    private static final Logger LOGGER = LoggerFactory.getLogger(GitlabApiClient.class);

    private GitlabAPI client;
    private GitlabAuthConfiguration configuration;
    // Cache token lookups to reduce the load on Github's User API to prevent hitting the rate limit.
    private Cache<String, GitlabPrincipal> tokenToPrincipalCache;

    public GitlabApiClient() {
        //no args constructor is needed
    }

    public GitlabApiClient(GitlabAPI client, GitlabAuthConfiguration configuration) {
        this.client = client;
        this.configuration = configuration;
        initPrincipalCache();
    }

    @Inject
    public GitlabApiClient(GitlabAuthConfiguration configuration) {
        this.configuration = configuration;
    }

    @PostConstruct
    public void init() {
        client = GitlabAPI.connect(configuration.getGitlabApiUrl(), configuration.getGitlabApiKey());
        initPrincipalCache();
    }

    private void initPrincipalCache() {
        tokenToPrincipalCache = CacheBuilder.newBuilder()
                .expireAfterWrite(configuration.getPrincipalCacheTtl().toMillis(), TimeUnit.MILLISECONDS)
                .build();
    }

    public GitlabPrincipal authz(String login, char[] token) throws GitlabAuthenticationException {
        // Combine the login and the token as the cache key since they are both used to generate the principal. If either changes we should obtain a new
        // principal.
        String cacheKey = login + "|" + new String(token);
        GitlabPrincipal cached = tokenToPrincipalCache.getIfPresent(cacheKey);
        if (cached != null) {
            LOGGER.debug("Using cached principal for login: {}", login);
            return cached;
        } else {
            GitlabPrincipal principal = doAuthz(login, token);
            tokenToPrincipalCache.put(cacheKey, principal);
            return principal;
        }
    }

    private GitlabPrincipal doAuthz(String loginName, char[] token) throws GitlabAuthenticationException {
        GitlabUser gitlabUser;
        List<GitlabGroup> groups = null;
        try {
            GitlabAPI gitlabAPI = GitlabAPI.connect(configuration.getGitlabApiUrl(), String.valueOf(token));
            gitlabUser = gitlabAPI.getUser();
        } catch (Exception e) {
            throw new GitlabAuthenticationException(e);
        }

        if (gitlabUser==null || !loginName.equals(gitlabUser.getEmail())) {
            throw new GitlabAuthenticationException("Given username not found or does not match Github Username!");
        }

        GitlabPrincipal principal = new GitlabPrincipal();

        principal.setUsername(gitlabUser.getEmail());
        principal.setGroups(getGroups((gitlabUser.getUsername())));

        return principal;
    }

    private Set<String> getGroups(String username) throws GitlabAuthenticationException {
        List<GitlabGroup> groups;
        try {
            groups = client.getGroupsViaSudo(username,new Pagination().withPerPage(Pagination.MAX_ITEMS_PER_PAGE));
        } catch (IOException e) {
            throw new GitlabAuthenticationException("Could not fetch groups for given username");
        }
        return groups.stream().map(this::mapGitlabGroupToNexusRole).collect(Collectors.toSet());
    }

    private String mapGitlabGroupToNexusRole(GitlabGroup team) {
        return team.getPath();
    }


}