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


import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestSearchRequest;
import com.atlassian.bitbucket.pull.PullRequestService;
import com.atlassian.bitbucket.repository.MinimalRef;
import com.atlassian.bitbucket.repository.Ref;
import com.atlassian.bitbucket.repository.RefMetadataContext;
import com.atlassian.bitbucket.repository.RefMetadataProvider;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageProvider;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PagedIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

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

import static com.atlassian.bitbucket.util.Chainable.chain;

public class PullRequestMetadataProvider implements RefMetadataProvider<Map<String, Object>> {

    private final PermissionService permissionService;
    private final PullRequestService service;

    public PullRequestMetadataProvider(PermissionService permissionService, PullRequestService service) {
        this.permissionService = permissionService;
        this.service = service;
    }

    @Nonnull
    @Override
    public Map<Ref, Map<String, Object>> getMetadata(@Nonnull final RefMetadataContext context) {
        if (!permissionService.hasGlobalPermission(Permission.LICENSED_USER)) {
            return Maps.newHashMap();
        }

        PagedIterable<PullRequest> pullRequests = new PagedIterable<>(new PageProvider<PullRequest>() {
            @Override
            public Page<PullRequest> get(PageRequest request) {
                return service.search(new PullRequestSearchRequest.Builder()
                        .fromRepositoryId(context.getRepository().getId())
                        .fromRefIds(chain(context.getRefs()).transform(MinimalRef.TO_ID))
                        .build(), request);
            }
        }, 100);

        Map<String, PullRequestSummary> summaries = Maps.newHashMap();

        for (PullRequest pr : pullRequests) {
            String refId = pr.getFromRef().getId();
            if (!summaries.containsKey(refId)) {
                summaries.put(refId, new PullRequestSummary());
            }
            summaries.get(refId).add(pr);
        }

        Map<Ref, Map<String, Object>> result = Maps.newHashMapWithExpectedSize(context.getRefs().size());
        for (Ref ref : context.getRefs()) {
            if (summaries.containsKey(ref.getId())) {
                result.put(ref, summaries.get(ref.getId()).toMap());
            }
        }

        return result;
    }

    public static class PullRequestSummary {

        Set<Long> open = Sets.newHashSet();
        Set<Long> merged = Sets.newHashSet();
        Set<Long> declined = Sets.newHashSet();
        PullRequest pullRequest;

        public void add(PullRequest pr) {
            pullRequest = pr;
            switch (pr.getState()) {
                case OPEN:
                    open.add(pr.getId());
                    break;
                case DECLINED:
                    declined.add(pr.getId());
                    break;
                case MERGED:
                    merged.add(pr.getId());
                    break;
            }
        }

        public Map<String, Object> toMap() {
            ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
            if ((open.size() + merged.size() + declined.size()) == 1) {
                return builder.put("pullRequest", pullRequest).build();
            } else {
                return builder
                        .put("open", open.size())
                        .put("merged", merged.size())
                        .put("declined", declined.size())
                        .build();
            }

        }
    }
}
