package com.atlassian.stash.internal.scm.git.ref;

import com.atlassian.bitbucket.ForbiddenException;
import com.atlassian.bitbucket.event.tag.*;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.scm.git.GitRefPattern;
import com.atlassian.bitbucket.scm.git.GitScm;
import com.atlassian.bitbucket.scm.git.command.GitRefCommandFactory;
import com.atlassian.bitbucket.scm.git.ref.GitCreateTagCommandParameters;
import com.atlassian.bitbucket.scm.git.ref.GitDeleteTagCommandParameters;
import com.atlassian.bitbucket.util.SimpleCancelState;
import com.atlassian.bitbucket.validation.ArgumentValidationException;
import com.atlassian.event.api.EventPublisher;

import javax.annotation.Nonnull;

/**
 * @since 3.7
 */
public class DefaultGitRefService implements GitRefService {

    private final EventPublisher eventPublisher;
    private final I18nService i18nService;
    private final PermissionService permissionService;
    private final RefService refService;
    private final GitRefCommandFactory refCommandFactory;
    private final RepositoryService repositoryService;

    public DefaultGitRefService(I18nService i18nService, PermissionService permissionService,
                                GitRefCommandFactory refCommandFactory, RefService refService,
                                RepositoryService repositoryService, EventPublisher eventPublisher) {
        this.i18nService = i18nService;
        this.permissionService = permissionService;
        this.refCommandFactory = refCommandFactory;
        this.refService = refService;
        this.repositoryService = repositoryService;
        this.eventPublisher = eventPublisher;
    }

    @Nonnull
    @Override
    public Tag createTag(@Nonnull Repository repository, @Nonnull GitCreateTagCommandParameters parameters) {
        validateIsGitRepo(repository);
        validateRepoPerms(repository);
        validateRepoIsNotEmpty(repository);

        SimpleCancelState cancelState = new SimpleCancelState();
        eventPublisher.publish(new TagCreationRequestedEvent(this, repository, simpleTag(parameters), cancelState));
        if (cancelState.isCanceled()) {
            throw new TagCreationCanceledException(i18nService.createKeyedMessage("bitbucket.service.tag.creationcanceled"),
                    cancelState.getCancelMessages());
        }

        Tag tag = refCommandFactory.createTag(repository, parameters)
                .call();

        if (tag == null) {
            throw new NoSuchTagException(i18nService.createKeyedMessage("bitbucket.service.tag.creationerror",
                    parameters.getName()), parameters.getName());
        }

        eventPublisher.publish(new TagCreatedEvent(this, repository, tag));

        return tag;
    }

    @Override
    public void deleteTag(@Nonnull Repository repository, @Nonnull GitDeleteTagCommandParameters parameters) {
        validateRepoPerms(repository);
        validateIsGitRepo(repository);

        ResolveRefRequest request = new ResolveRefRequest.Builder(repository)
                .refId(GitRefPattern.TAGS.qualify(parameters.getName()))
                .build();
        Tag tag = Tag.class.cast(refService.resolveRef(request));

        if (tag == null) {
            throw new NoSuchTagException(i18nService.createKeyedMessage("bitbucket.git.tag.delete.error",
                    parameters.getName()), parameters.getName());
        }

        SimpleCancelState cancelState = new SimpleCancelState();
        eventPublisher.publish(new TagDeletionRequestedEvent(this, repository, tag, cancelState));
        if (cancelState.isCanceled()) {
            throw new TagDeletionCanceledException(i18nService.createKeyedMessage("bitbucket.git.tag.delete.cancelled",
                    tag.getDisplayId()), cancelState.getCancelMessages());
        }

        refCommandFactory.deleteTag(repository, parameters).call();

        eventPublisher.publish(new TagDeletedEvent(this, repository, tag));
    }

    private void validateIsGitRepo(Repository repository) {
        if (!GitScm.ID.equals(repository.getScmId())) {
            throw new ArgumentValidationException(i18nService.createKeyedMessage("bitbucket.git.service.tag.nongit",
                    repository.getScmId()));
        }
    }

    private void validateRepoPerms(Repository repository) {
        if (!permissionService.hasRepositoryPermission(repository, Permission.REPO_WRITE)) {
            throw new ForbiddenException(i18nService.createKeyedMessage("bitbucket.git.service.tag.unauthorized",
                    repository.getProject().getKey(), repository.getSlug()));
        }
    }

    private void validateRepoIsNotEmpty(Repository repository) {
        if (repositoryService.isEmpty(repository)) {
            throw new IllegalRepositoryStateException(i18nService.createKeyedMessage("bitbucket.git.service.tag.creation.emptyrepository",
                    repository.getProject().getKey(), repository.getSlug()));
        }
    }

    private static SimpleTag simpleTag(@Nonnull GitCreateTagCommandParameters parameters) {
        return new SimpleTag.Builder()
                .id(GitRefPattern.TAGS.qualify(parameters.getName()))
                .displayId(parameters.getName())
                .latestCommit(parameters.getStartPoint())
                .build();
    }
}
