package com.atlassian.bitbucket.internal.branch;

import com.atlassian.bitbucket.branch.model.BranchModel;
import com.atlassian.bitbucket.branch.model.BranchModelService;
import com.atlassian.bitbucket.branch.model.BranchType;
import com.atlassian.bitbucket.internal.branch.automerge.AutoMerge;
import com.atlassian.bitbucket.internal.branch.automerge.AutoMergeService;
import com.atlassian.bitbucket.internal.branch.model.BranchTypes;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestMergeability;
import com.atlassian.bitbucket.pull.PullRequestMergeabilityPropertyProvider;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.user.SecurityService;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;

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

import static java.util.Optional.ofNullable;

public class MergeConflictPropertyProvider implements PullRequestMergeabilityPropertyProvider {

    // Visible for testing
    static final String PROP_PREFIX = "branch.";
    static final String PROP_AUTO_MERGE_CONFLICT = PROP_PREFIX + "auto-merge-conflict";
    static final String PROP_SOURCE_BRANCH_STABILITY = PROP_PREFIX + "from-ref-stability";

    private final AutoMergeService autoMergeService;
    private final BranchModelService branchModelService;
    private final SecurityService securityService;

    public MergeConflictPropertyProvider(AutoMergeService autoMergeService, BranchModelService branchModelService,
                                         SecurityService securityService) {
        this.autoMergeService = autoMergeService;
        this.branchModelService = branchModelService;
        this.securityService = securityService;
    }

    @Nonnull
    @Override
    public Map<String, Object> provideProperties(@Nonnull PullRequest pullRequest, @Nonnull PullRequestMergeability mergeability) {
        if (!mergeability.isConflicted()) {
            return Collections.emptyMap();
        }

        if (isAutoMergeConflict(pullRequest)) {
            return ImmutableMap.of(
                    PROP_AUTO_MERGE_CONFLICT, true,
                    PROP_SOURCE_BRANCH_STABILITY, "stable");
        }

        return provideSourceStabilityProperty(pullRequest);
    }

    private boolean isAutoMergeConflict(PullRequest pullRequest) {
        if (pullRequest.isCrossRepository()) {
            return false;
        }
        AutoMerge autoMerge = autoMergeService.getAutoMerge(pullRequest.getFromRef().getRepository(), pullRequest.getFromRef());
        Branch nextBranch = Iterables.get(autoMerge.getPath(), 1, null);
        return nextBranch != null && nextBranch.getId().equals(pullRequest.getToRef().getId());
    }

    private Map<String, Object> provideSourceStabilityProperty(PullRequest pullRequest) {
        Optional<BranchModel> branchModel = getModelForRepository(pullRequest.getFromRef().getRepository());
        if (branchModel.isPresent()) {
            BranchModel model = branchModel.get();
            if (isUnstable(model, BranchModelUtils.convertRefToBranch(pullRequest.getFromRef()))) {
                return ImmutableMap.of(PROP_SOURCE_BRANCH_STABILITY, "unstable");
            } else if (isStable(model, BranchModelUtils.convertRefToBranch(pullRequest.getFromRef()))) {
                return ImmutableMap.of(PROP_SOURCE_BRANCH_STABILITY, "stable");
            }
        }
        // can't tell whether it's stable or unstable or there is no branch model
        return Collections.emptyMap();
    }

    private Optional<BranchModel> getModelForRepository(Repository repository) {
        return securityService.withPermission(Permission.REPO_READ, repository, "using branch model to determine source branch stability")
                .call(() -> ofNullable(branchModelService.getModel(repository)));
    }

    private static boolean isUnstable(BranchModel model, Branch branch) {
        BranchType branchType = model.getClassifier().getType(branch);
        return BranchTypes.BUGFIX.matcher().test(branchType) ||
                BranchTypes.HOTFIX.matcher().test(branchType) ||
                BranchTypes.FEATURE.matcher().test(branchType);
    }

    private static boolean isStable(BranchModel model, Branch branch) {
        BranchType branchType = model.getClassifier().getType(branch);
        return BranchTypes.RELEASE.matcher().test(branchType) ||
                branch.equals(model.getDevelopment()) ||
                branch.equals(model.getProduction());
    }
}
