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

import com.atlassian.bitbucket.RequestCanceledException;
import com.atlassian.bitbucket.ServiceException;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.help.HelpPathService;
import com.atlassian.bitbucket.hook.repository.RepositoryHook;
import com.atlassian.bitbucket.hook.repository.RepositoryHookService;
import com.atlassian.bitbucket.hook.repository.RepositoryHookType;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.project.NoSuchProjectException;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.pull.*;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.scm.AvailableScm;
import com.atlassian.bitbucket.scm.ScmFeature;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.server.FeatureManager;
import com.atlassian.bitbucket.server.StandardFeature;
import com.atlassian.bitbucket.ui.PluginFormFragments;
import com.atlassian.bitbucket.ui.PluginFormFragmentsFactory;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.Chainable;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.bitbucket.validation.ArgumentValidationException;
import com.atlassian.soy.springmvc.errors.DetailedError;
import com.atlassian.stash.internal.attach.AttachmentService;
import com.atlassian.stash.internal.plugin.PluginValidationErrors;
import com.atlassian.stash.internal.project.InternalProjectService;
import com.atlassian.stash.internal.scm.InternalScmService;
import com.atlassian.stash.internal.web.repos.ViewRef;
import com.atlassian.stash.internal.web.repos.ViewRefType;
import com.atlassian.stash.internal.web.repos.ViewRefTypeHelper;
import com.atlassian.stash.internal.web.soy.StashSoyResponseBuilder;
import com.atlassian.stash.internal.web.util.FilteredErrors;
import com.atlassian.stash.internal.web.util.RepositoryControllerSupport;
import com.atlassian.stash.internal.web.util.StashWebErrorUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import java.util.*;
import java.util.function.Function;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableList;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@Controller
@RequestMapping("/projects/{projectKey}/repos")
public class RepositoryAdminController extends RepositoryControllerSupport {

    static final String FRAGS_FORK = "bitbucket.page.repository.fork";
    static final String FRAGS_GENERAL_SETTINGS = "bitbucket.page.repository.settings.general";
    static final String FRAGS_PULL_REQUEST_SETTINGS = "bitbucket.page.repository.settings.pullRequests";
    static final String MERGE_CONFIG_DEFAULT_STRATEGY_ID = "defaultId";
    static final String MERGE_CONFIG_DEFAULT_STRATEGY = "defaultStrategy";
    static final String MERGE_CONFIG_DEFAULT_STRATEGY_UNSET = "defaultUnset";
    static final String MERGE_CONFIG_ENABLED_STRATEGY_IDS = "enabledIds";
    static final String MERGE_CONFIG_NO_STRATEGY_ENABLED = "enabledUnset";
    static final String MERGE_CONFIG_ERROR = "invalidMergeConfig";
    static final String MERGE_CONFIG_STRATEGIES = "strategies";
    static final String MERGE_CONFIG_TYPE = "type";

    private static final String PAGE_CREATE = "bitbucket.internal.page.createRepo";
    private static final String PAGE_FORK = "bitbucket.internal.page.forkRepo";
    private static final String PAGE_GENERAL_SETTINGS = "bitbucket.internal.page.repositoryGeneralSettings";
    private static final String PAGE_HOOK_SETTINGS = "bitbucket.internal.page.repositoryHookSettings";
    private static final String PAGE_PULL_REQUEST_SETTINGS = "bitbucket.internal.page.repositoryPullRequestSettings";

    private final AttachmentService attachmentService;
    private final AuthenticationContext authenticationContext;
    private final FeatureManager featureManager;
    private final PluginFormFragmentsFactory fragmentsFactory;
    private final HelpPathService helpPathService;
    private final RepositoryHookService hookService;
    private final NavBuilder navBuilder;
    private final ApplicationPropertiesService propertiesService;
    private final PullRequestService pullRequestService;
    private final ViewRefTypeHelper viewRefTypeHelper;

    @Autowired
    public RepositoryAdminController(I18nService i18nService, RefService refService,
                                     PermissionService permissionService, InternalProjectService projectService,
                                     RepositoryService repositoryService, InternalScmService scmService,
                                     AttachmentService attachmentService, AuthenticationContext authenticationContext,
                                     FeatureManager featureManager, PluginFormFragmentsFactory fragmentsFactory,
                                     HelpPathService helpPathService, RepositoryHookService hookService,
                                     NavBuilder navBuilder, ApplicationPropertiesService propertiesService,
                                     PullRequestService pullRequestService, ViewRefTypeHelper viewRefTypeHelper) {
        super(i18nService, refService, permissionService, projectService, repositoryService, scmService);

        this.attachmentService = attachmentService;
        this.authenticationContext = authenticationContext;
        this.featureManager = featureManager;
        this.fragmentsFactory = fragmentsFactory;
        this.helpPathService = helpPathService;
        this.hookService = hookService;
        this.navBuilder = navBuilder;
        this.propertiesService = propertiesService;
        this.pullRequestService = pullRequestService;
        this.viewRefTypeHelper = viewRefTypeHelper;
    }

    @RequestMapping(params = "create", method = POST)
    public ModelAndView createRepository(@PathVariable("projectKey") String projectKey,
                                         @Valid RepositoryForm form, Errors errors) {
        Project project = getProject(projectKey);
        List<DetailedError> detailedErrors = Lists.newArrayList();
        if (!errors.hasErrors()) {
            try {
                RepositoryCreateRequest request = new RepositoryCreateRequest.Builder()
                        .forkable(form.isForkable())
                        .project(project)
                        .name(form.getName())
                        .scmId(form.getScmId()) //From 3.3 the creation form should always include the SCM ID
                        .build();
                Repository repository = repositoryService.create(request);

                return new ModelAndView(new RedirectView(navBuilder.project(projectKey).repo(repository.getSlug()).buildRelNoContext(), true));
            } catch (RequestCanceledException e) {
                detailedErrors.add(StashWebErrorUtils.toDetailedError(e));
            } catch (ServiceException e) {
                rejectException(errors, e);
            } catch (ConstraintViolationException e) {
                rejectException(errors, e);
            }
        }

        return new StashSoyResponseBuilder(PAGE_CREATE)
                .putProject(project)
                .putValidationErrors(errors)
                .putDetailedErrors(detailedErrors)
                .put("repositoryForm", form)
                .put("scms", getAvailableScms())
                .put("repoNameMaxSize", Repository.NAME_MAX_SIZE)
                .build();
    }

    @RequestMapping(params = "create", method = RequestMethod.GET)
    public ModelAndView createRepositoryView(
            @PathVariable("projectKey") String projectKey) {
        Project project = projectService.getByKey(projectKey);
        if (project == null) {
            throw newNoSuchProjectException(projectKey);
        }

        if (permissionService.hasProjectPermission(project, Permission.PROJECT_ADMIN)) {
            return new StashSoyResponseBuilder(PAGE_CREATE)
                    .putProject(project)
                    .put("scms", getAvailableScms())
                    .put("repoNameMaxSize", Repository.NAME_MAX_SIZE)
                    .build();
        }
        throw new AccessDeniedException(i18nService.getMessage("bitbucket.web.repository.create.accessdenied", project.getName()));
    }

    @RequestMapping(value = "{repoSlug}/settings", method = GET)
    public ModelAndView getSettings(@PathVariable("projectKey") String projectKey,
                                    @PathVariable("repoSlug") String repoSlug) {
        Repository repository = getRepository(projectKey, repoSlug);
        if (!permissionService.hasRepositoryPermission(repository, Permission.REPO_ADMIN)) {
            throw new AccessDeniedException(i18nService.getMessage("bitbucket.web.repository.edit.accessdenied", repository.getName()));
        }

        return createSettingsPageBuilder(repository,
                new RepositoryForm(repository, getDefaultBranchIfAvailable(repository)))
                .build();
    }

    @RequestMapping(value = "{repoSlug}/settings", method = POST)
    public ModelAndView updateSettings(@PathVariable("projectKey") String projectKey,
                                       @PathVariable("repoSlug") String repoSlug,
                                       @Valid RepositoryForm repositoryForm, Errors errors,
                                       HttpServletRequest request, RedirectAttributes redirectAttributes) {
        //The SCM ID is not required on update since a repository's SCM can't be changed after creation
        errors = new FilteredErrors(errors, "scmId");

        Repository repository = getRepository(projectKey, repoSlug);
        PluginValidationErrors pluginErrors = new PluginValidationErrors(errors);

        PluginFormFragments fragments = fragmentsFactory.forKey(FRAGS_GENERAL_SETTINGS, prepareSettingsContext(repository));
        fragments.validate(request.getParameterMap(), pluginErrors);

        if (!pluginErrors.hasErrors() && StringUtils.isNotBlank(repositoryForm.getDefaultBranchId())) {
            try {
                refService.setDefaultBranch(repository, repositoryForm.getDefaultBranchId().trim());
            } catch (ServiceException e) {
                rejectException(errors, e);
            } catch (UnsupportedOperationException e) {
                log.warn("{}: Setting the default branch is not supported", repository, e);
            }
        }

        List<DetailedError> detailedErrors = Lists.newArrayList();
        if (!pluginErrors.hasErrors()) {
            try {
                repository = repositoryService.update(
                        new RepositoryUpdateRequest.Builder(repository)
                                .forkable(repositoryForm.isForkable())
                                .name(repositoryForm.getName())
                                .build());
                fragments.execute(request.getParameterMap());
                redirectAttributes.addFlashAttribute("isEditSuccess", true);
                return new ModelAndView("redirect:" + navBuilder
                        .project(repository.getProject())
                        .repo(repository)
                        .settings()
                        .buildRelNoContext());
            } catch (ConstraintViolationException e) {
                rejectException(errors, e);
            } catch (RequestCanceledException e) {
                detailedErrors.add(StashWebErrorUtils.toDetailedError(e));
            } catch (ServiceException e) {
                rejectException(errors, e);
            }
        }

        //If we make it here, it means there were validation errors or the settings could not be saved
        repositoryForm.setDefaultBranch(getDefaultBranchIfAvailable(repository));
        return createSettingsPageBuilder(repository, repositoryForm)
                .putValidationErrors(errors)
                .putDetailedErrors(detailedErrors)
                .put("pluginFieldsContent", fragments.getErrorHtml(request.getParameterMap(), pluginErrors.getFieldErrors()))
                .build();
    }

    @RequestMapping(method = RequestMethod.GET)
    public RedirectView repositoryList(@PathVariable("projectKey") String projectKey) {
        return new RedirectView(navBuilder.project(projectKey).buildRelNoContext(), true);
    }

    @RequestMapping(value = "{repoSlug}/settings/hooks")
    public ModelAndView getHooks(@PathVariable("projectKey") String projectKey,
                                 @PathVariable("repoSlug") String repoSlug) {
        Repository repository = getRepository(projectKey, repoSlug);

        // TODO create page request in a more configurable / abstract way
        Page<RepositoryHook> preReceiveHookPage = hookService.findByType(repository, RepositoryHookType.PRE_RECEIVE, PageUtils.newRequest(0, 25));
        Page<RepositoryHook> postReceiveHookPage = hookService.findByType(repository, RepositoryHookType.POST_RECEIVE, PageUtils.newRequest(0, 25));

        return new StashSoyResponseBuilder(PAGE_HOOK_SETTINGS)
                .putRepository(repository)
                .put("preReceiveHookPage", preReceiveHookPage)
                .put("postReceiveHookPage", postReceiveHookPage)
                .build();
    }

    @RequestMapping(value = "{repoSlug}/settings/pull-requests")
    public ModelAndView getPullRequestSettings(@PathVariable("projectKey") String projectKey,
                                               @PathVariable("repoSlug") String repoSlug) {
        Repository repository = getRepository(projectKey, repoSlug);
        checkCanEditPullRequestSettings(repository);
        return buildPullRequestSettings(repository, null, null, null, shouldShowMergeStrategies(repository), null);
    }

    @RequestMapping(value = "{repoSlug}/settings/pull-requests", method = POST)
    public ModelAndView updatePullRequestSettings(@PathVariable("projectKey") String projectKey,
                                                  @PathVariable("repoSlug") String repoSlug,
                                                  HttpServletRequest request, RedirectAttributes redirectAttributes) {
        Repository repository = getRepository(projectKey, repoSlug);
        checkCanEditPullRequestSettings(repository);

        // validate any fields added by plugins
        PluginFormFragments fragments = fragmentsFactory.forKey(FRAGS_PULL_REQUEST_SETTINGS, prepareSettingsContext(repository));
        PluginValidationErrors pluginValidationErrors = new PluginValidationErrors(null);
        Map<String, String[]> parameterMap = request.getParameterMap();
        fragments.validate(parameterMap, pluginValidationErrors);

        boolean showMergeStrategies = shouldShowMergeStrategies(repository);

        if (pluginValidationErrors.hasFieldErrors()) {
            return buildPullRequestSettings(repository, pluginValidationErrors, fragments, parameterMap, showMergeStrategies, null);
        }

        if (showMergeStrategies) {
            Set<Map<String, String>> mergeStrategyErrors = updateMergeStrategySettings(repository, parameterMap);
            if (!mergeStrategyErrors.isEmpty()) {
                return buildPullRequestSettings(repository, pluginValidationErrors, fragments, parameterMap,
                        showMergeStrategies, mergeStrategyErrors);
            }
        }

        fragments.execute(parameterMap);
        redirectAttributes.addFlashAttribute("isEditSuccess", true);
        return new ModelAndView("redirect:" + navBuilder.repo(repository).settings().pullRequests().buildRelNoContext());
    }

    @RequestMapping(value = "{repoSlug}", params = "fork", method = GET)
    public ModelAndView forkRepositoryView(@PathVariable("projectKey") String projectKey,
                                           @PathVariable("repoSlug") String repoSlug) {
        ApplicationUser currentUser = authenticationContext.getCurrentUser();
        if (currentUser == null) {
            throw new AccessDeniedException(i18nService.getMessage("bitbucket.web.repository.fork.accessdenied"));
        }

        Repository repository = getRepository(projectKey, repoSlug);
        Project personalProject = projectService.getPersonalProject();

        StashSoyResponseBuilder builder = new StashSoyResponseBuilder(PAGE_FORK)
                .putRepository(repository)
                .put("pluginFieldsContent", fragmentsFactory.forKey(FRAGS_FORK, prepareForkContext(repository, null)).getViewHtml())
                .put("repoNameMaxSize", Repository.NAME_MAX_SIZE);

        if (featureManager.isEnabled(StandardFeature.PERSONAL_REPOS)) {
            builder.put("personalProject", personalProject);
        }

        return builder.build();
    }

    @RequestMapping(value = "{repoSlug}", params = "fork", method = POST)
    public ModelAndView forkRepository(@PathVariable("projectKey") String projectKey,
                                       @PathVariable("repoSlug") String repoSlug,
                                       @Valid ForkForm form, Errors errors,
                                       HttpServletRequest request) {
        Repository upstream = getRepository(projectKey, repoSlug);
        List<DetailedError> detailedErrors = Lists.newArrayList();
        Project toProject = null;

        // validate any fields added by plugins
        PluginFormFragments fragments = fragmentsFactory.forKey(FRAGS_FORK, prepareForkContext(upstream, null));
        PluginValidationErrors pluginValidationErrors = new PluginValidationErrors(null);
        fragments.validate(request.getParameterMap(), pluginValidationErrors);

        if (!pluginValidationErrors.hasFieldErrors() && !errors.hasErrors()) {
            try {
                toProject = getProject(form.getToProjectId());

                RepositoryForkRequest.Builder requestBuilder = new RepositoryForkRequest.Builder(upstream)
                        .forkable(form.isForkable())
                        .name(form.getName())
                        .project(toProject);
                Repository fork = repositoryService.fork(requestBuilder.build());

                fragments = fragmentsFactory.forKey(FRAGS_FORK, prepareForkContext(upstream, fork));
                fragments.execute(request.getParameterMap());

                return new ModelAndView(new RedirectView(navBuilder.project(fork.getProject().getKey()).repo(fork.getSlug()).buildRelNoContext(), true));
            } catch (RequestCanceledException e) {
                detailedErrors.add(StashWebErrorUtils.toDetailedError(e));
            } catch (NoSuchProjectException e) {
                errors.rejectValue("toProjectId", e.getMessageKey(), e.getLocalizedMessage());
            } catch (ServiceException e) {
                rejectException(errors, e);
            } catch (ConstraintViolationException e) {
                rejectException(errors, e);
            }
        }

        Project personalProject = projectService.getPersonalProject();
        StashSoyResponseBuilder builder = new StashSoyResponseBuilder(PAGE_FORK)
                .putRepository(upstream)
                .put("personalProject", personalProject)
                .put("pluginFieldsContent", fragments.getErrorHtml(request.getParameterMap(), pluginValidationErrors.getFieldErrors()))
                .put("nameMaxSize", Repository.NAME_MAX_SIZE)
                .put("forkForm", form)
                .putValidationErrors(errors)
                .putDetailedErrors(detailedErrors);

        if (toProject != null) {
            builder.put("toProject", toProject);
        }

        return builder.build();
    }

    @ResponseBody
    @RequestMapping(value = "{repoSlug}/sizes", method = GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Map<String, Long> getSizes(@PathVariable("projectKey") String projectKey,
                                      @PathVariable("repoSlug") String repoSlug) {
        Repository repository = getRepository(projectKey, repoSlug);
        checkCanEditPullRequestSettings(repository);

        return ImmutableMap.of("repository", repositoryService.getSize(repository),
                "attachments", attachmentService.getSize(repository));
    }

    @VisibleForTesting
    Map<String, ?> mergeConfigToMap(Repository repository) {
        ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();

        PullRequestMergeConfig mergeConfig = pullRequestService.getMergeConfig(
                new GetPullRequestMergeConfigRequest.Builder(repository).build());

        builder.put(MERGE_CONFIG_DEFAULT_STRATEGY, mergeStrategyToMap(mergeConfig.getDefaultStrategy()));
        builder.put(MERGE_CONFIG_STRATEGIES, mergeConfig.getStrategies().stream()
                .map(RepositoryAdminController::mergeStrategyToMap)
                .collect(toImmutableList()));
        builder.put(MERGE_CONFIG_TYPE, mergeConfig.getType().toString());

        return builder.build();
    }

    @VisibleForTesting
    Set<Map<String, String>> updateMergeStrategySettings(Repository repository, Map<String, String[]> parameterMap) {
        Set<Map<String, String>> errors = new HashSet<>(4, 1.0F);
        SetPullRequestMergeConfigRequest.Builder builder = new SetPullRequestMergeConfigRequest.Builder(repository);

        String[] defaultId = parameterMap.get(MERGE_CONFIG_DEFAULT_STRATEGY_ID);

        if (defaultId == null || defaultId.length != 1) {
            errors.add(toMap(MERGE_CONFIG_DEFAULT_STRATEGY_UNSET,
                    i18nService.getMessage("bitbucket.web.repository.settings.pullrequest.mergestrategy.invalid.default")));
        } else {
            builder.defaultStrategyId(defaultId[0]);
        }

        String[] enabledIds = parameterMap.get(MERGE_CONFIG_ENABLED_STRATEGY_IDS);
        if (enabledIds == null || enabledIds.length < 1) {
            errors.add(toMap(MERGE_CONFIG_NO_STRATEGY_ENABLED,
                    i18nService.getMessage("bitbucket.web.repository.settings.pullrequest.mergestrategy.invalid.selection")));
        } else {
            builder.enabledStrategyIds(ImmutableSet.copyOf(enabledIds));
        }

        if (errors.isEmpty()) {
            try {
                pullRequestService.setMergeConfig(builder.build());
            } catch (ArgumentValidationException e) {
                errors.add(toMap(MERGE_CONFIG_ERROR,
                        i18nService.getMessage("bitbucket.web.repository.settings.pullrequest.mergestrategy.error")));
            }
        }
        return errors;
    }

    private static Map<String, Object> mergeStrategyToMap(PullRequestMergeStrategy strategy) {
        ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();

        builder.put("description", strategy.getDescription());
        builder.put("flag", strategy.getFlag().orElse(""));
        builder.put("id", strategy.getId());
        builder.put("isEnabled", strategy.isEnabled());
        builder.put("name", strategy.getName());

        return builder.build();
    }

    private static Map<String, String> toMap(String id, String message) {
        return ImmutableMap.of("id", id, "message", message);
    }

    private ModelAndView buildPullRequestSettings(Repository repository, PluginValidationErrors pluginValidationErrors,
                                                  PluginFormFragments fragments, Map<String, String[]> parameterMap,
                                                  boolean showMergeStrategies,
                                                  Set<Map<String, String>> mergeStrategyErrors) {
        StashSoyResponseBuilder builder = new StashSoyResponseBuilder(PAGE_PULL_REQUEST_SETTINGS)
                .putRepository(repository)
                .put("showMergeStrategies", showMergeStrategies);

        if (pluginValidationErrors != null && pluginValidationErrors.hasFieldErrors()) {
            builder.put("pluginFieldsContent", fragments.getErrorHtml(parameterMap, pluginValidationErrors.getFieldErrors()));
        } else {
            builder.put("pluginFieldsContent", fragmentsFactory.forKey(FRAGS_PULL_REQUEST_SETTINGS,
                    prepareSettingsContext(repository)).getViewHtml());
        }

        if (showMergeStrategies) {
            builder.put("mergeStrategies", mergeConfigToMap(repository));

            String helpTopic = scmService.getMergeStrategies(repository.getScmId()).getHelpTopic();
            String helpUrl = helpPathService.getPageUrl(helpTopic);
            builder.put("mergeStrategiesHelpUrl", helpUrl == null ? helpTopic : helpUrl);

            if (mergeStrategyErrors != null && !mergeStrategyErrors.isEmpty()) {
                builder.put("mergeStrategyErrors", mergeStrategyErrors);
            }
        }
        return builder.build();
    }

    private void checkCanEditPullRequestSettings(Repository repository) {
        if (!permissionService.hasRepositoryPermission(repository, Permission.REPO_ADMIN)) {
            throw new AccessDeniedException(i18nService.getMessage("bitbucket.web.repository.settings.pull.request", repository.getProject().getName(), repository.getName()));
        }
    }

    private StashSoyResponseBuilder createSettingsPageBuilder(Repository repository, RepositoryForm repositoryForm) {
        StashSoyResponseBuilder builder = new StashSoyResponseBuilder(PAGE_GENERAL_SETTINGS)
                .putRepository(repository)
                .put("repositoryForm", repositoryForm)
                .put("pluginFieldsContent", fragmentsFactory.forKey(FRAGS_GENERAL_SETTINGS, prepareSettingsContext(repository)).getViewHtml())
                .put("repoNameMaxSize", Repository.NAME_MAX_SIZE);

        // We only display the repository location on disk to SYS_ADMINs as they are the
        // only users that should have knowledge of the system Stash is installed on
        if (permissionService.hasGlobalPermission(Permission.SYS_ADMIN)) {
            builder.put("repositoryLocation", propertiesService.getRepositoryDir(repository).getAbsolutePath());
        }

        return builder;
    }

    /**
     * Transforms {@link AvailableScm available SCMs} to a map suitable for use with the AUI RadioField widget.
     *
     * @return a list of maps with "isChecked", "labelText" and "value" keys, for use with AUI's RadioField
     * @since 3.3
     */
    private List<Map<String, Object>> getAvailableScms() {
        return Chainable.chain(scmService.getAvailable()).transform(new Function<AvailableScm, Map<String, Object>>() {

            private int count;

            @Override
            public Map<String, Object> apply(AvailableScm scm) {
                return ImmutableMap.of(
                        "id", "scm-type-" + scm.getId(),
                        "isChecked", count++ == 0,
                        "labelText", scm.getName(),
                        "value", scm.getId());
            }
        }).toList();
    }

    private ViewRef getDefaultBranchIfAvailable(Repository repository) {
        if (repository.getState() != Repository.State.AVAILABLE) {
            log.warn("Failed to resolve default branch. state={}", repository.getState());
            return null;
        }
        try {
            Branch branch = refService.getDefaultBranch(repository);
            return new ViewRef(branch.getId(), branch.getDisplayId(), branch.getIsDefault(),
                    viewRefTypeHelper.getViewRefType(ViewRefType.BRANCH), branch.getLatestCommit());
        } catch (NoDefaultBranchException e) {
            log.info("Failed to resolve default branch. Cause: {}", e.getMessage());
            return null;
        }
    }

    private Map<String, Object> prepareForkContext(Repository upstream, Repository fork) {
        Map<String, Object> context = new HashMap<>();
        context.put("upstream", upstream);
        if (fork != null) {
            context.put("fork", fork);
        }

        return context;
    }

    private Map<String, Object> prepareSettingsContext(Repository repository) {
        Map<String, Object> context = new HashMap<>();
        context.put("repository", repository);

        return context;
    }

    private boolean shouldShowMergeStrategies(Repository repository) {
        return scmService.isSupported(repository, ScmFeature.MERGE_STRATEGIES);
    }
}
