package com.atlassian.stash.internal.pull;

import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.spring.AvailableToPlugins;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestMergeVeto;
import com.atlassian.bitbucket.pull.PullRequestMergeability;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheck;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheckModuleDescriptor;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheckService;
import com.atlassian.bitbucket.util.Timer;
import com.atlassian.bitbucket.util.TimerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Nonnull;
import java.util.Collection;

import static com.atlassian.stash.internal.InternalConverter.convertToInternalPullRequest;

@AvailableToPlugins(MergeRequestCheckService.class)
@Service("mergeRequestCheckService")
@Transactional(readOnly = true)
public class DefaultMergeRequestCheckService implements InternalMergeRequestCheckService {

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

    private final PullRequestEnricher enricher;
    private final I18nService i18nService;
    private final PluginAccessor pluginAccessor;
    private final ScmService scmService;

    @Autowired
    public DefaultMergeRequestCheckService(PullRequestEnricher enricher, I18nService i18nService,
                                           PluginAccessor pluginAccessor, ScmService scmService) {
        this.enricher = enricher;
        this.i18nService = i18nService;
        this.pluginAccessor = pluginAccessor;
        this.scmService = scmService;
    }

    @Nonnull
    @Override
    public Collection<PullRequestMergeVeto> check(@Nonnull PullRequest pullRequest) {
        return doCheck(new SimpleMergeRequest.Builder(pullRequest).build());
    }

    @Nonnull
    @Override
    public PullRequestMergeability checkMergeability(@Nonnull InternalMergeRequest mergeRequest) {
        Collection<PullRequestMergeVeto> vetoes = doCheck(mergeRequest);
        boolean conflicted = isConflicted(mergeRequest.getPullRequest());

        return enricher.enrich(convertToInternalPullRequest(mergeRequest.getPullRequest()),
                new SimplePullRequestMergeability(vetoes.isEmpty() && !conflicted, conflicted, vetoes));
    }

    private static Timer timer(MergeRequestCheckModuleDescriptor descriptor, MergeRequestCheck module) {
        return TimerUtils.start(String.format("Calling merge request check %s of type %s",
                descriptor.getCompleteKey(), module.getClass().getName()));
    }

    private Collection<PullRequestMergeVeto> doCheck(InternalMergeRequest mergeRequest) {
        for (MergeRequestCheckModuleDescriptor descriptor : getDescriptors()) {
            MergeRequestCheck module = descriptor.getModule();
            try (Timer ignored = timer(descriptor, module)) {
                module.check(mergeRequest);
            } catch (Exception e) {
                log.warn("Merge request check {} of type {} failed.",
                        descriptor.getCompleteKey(), module.getClass().getName(), e);

                // Automatically veto exceptions but still call other check requests
                mergeRequest.veto(i18nService.getMessage("bitbucket.service.pullrequest.merge.exception.summary"),
                        i18nService.getMessage("bitbucket.service.pullrequest.merge.exception.description", descriptor.getPluginKey()));
            }
        }

        return mergeRequest.getVetoes();
    }

    private Iterable<MergeRequestCheckModuleDescriptor> getDescriptors() {
        return pluginAccessor.getEnabledModuleDescriptorsByClass(MergeRequestCheckModuleDescriptor.class);
    }

    private boolean isConflicted(PullRequest pullRequest) {
        Boolean canMerge = scmService.getPullRequestCommandFactory(pullRequest)
                .canMerge()
                .call();

        //If the command returns null (which it never will, but IDEA doesn't know that), assume it can be
        //merged. This check is always only going to be an indicator, never ironclad, because the pull
        //request can be updated after this method is called but before the user refreshes their screen.
        return !(canMerge == null || canMerge);
    }
}
