package com.atlassian.bitbucket.internal.branch;

import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.repository.Ref;
import com.atlassian.bitbucket.repository.RefMetadataContext;
import com.atlassian.bitbucket.repository.RefMetadataProvider;
import com.atlassian.bitbucket.scm.CommitsCommandParameters;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * A metadata provider which associates the latest {@link Commit} with a collection of {@link Ref refs}
 */
public class LatestCommitMetadataProvider implements RefMetadataProvider<Commit> {

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

    private final ScmService scmService;

    public LatestCommitMetadataProvider(ScmService scmService) {
        this.scmService = scmService;
    }

    @Nonnull
    @Override
    public Map<Ref, Commit> getMetadata(@Nonnull final RefMetadataContext context) {
        Page<Commit> commits = scmService.getCommandFactory(context.getRepository())
                .commits(buildParameters(context), PageUtils.newRequest(0, context.getRefs().size()))
                .call();
        if (commits == null) {
            return Collections.emptyMap();
        }

        Map<String, Commit> commitById = Maps.uniqueIndex(commits.getValues(), Commit::getId);

        ImmutableMap.Builder<Ref, Commit> builder = ImmutableMap.builder();
        for (Ref ref : context.getRefs()) {
            Commit commit = commitById.get(ref.getLatestCommit());
            if (commit == null) {
                // This should never happen, but we will be defensive and log a warning instead of letting
                // ImmutableMap.Builder throw an exception
                log.warn("{}: Missing commit {} for ref {}",
                        context.getRepository(), ref.getLatestCommit(), ref.getId());
            } else {
                builder.put(ref, commit);
            }
        }
        return builder.build();
    }

    private CommitsCommandParameters buildParameters(RefMetadataContext metadataContext) {
        Map<String, Object> context = metadataContext.getContext();
        CommitsCommandParameters.Builder builder = new CommitsCommandParameters.Builder()
                .include(Collections2.transform(metadataContext.getRefs(), Ref::getLatestCommit))
                .traverse(false);

        if (context.containsKey("withMessages")) {
            builder.withMessages(Boolean.TRUE.equals(context.get("withMessages")));
        }
        return builder.build();
    }
}
