// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.acceptance.rest.project;

import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.R_TAGS;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.extensions.api.projects.DeleteTagsInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.inject.Inject;
import java.util.HashMap;
import java.util.List;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;

@NoHttpd
public class DeleteTagsIT extends AbstractDaemonTest {
  private static final ImmutableList<String> TAGS =
      ImmutableList.of("refs/tags/test-1", "refs/tags/test-2", "refs/tags/test-3", "test-4");

  @Inject private ProjectOperations projectOperations;
  @Inject private RequestScopeOperations requestScopeOperations;

  @Before
  public void setUp() throws Exception {
    for (String name : TAGS) {
      project().tag(name).create(new TagInput());
    }
    assertTags(TAGS);
  }

  @Test
  public void deleteTags() throws Exception {
    HashMap<String, RevCommit> initialRevisions = initialRevisions(TAGS);
    DeleteTagsInput input = new DeleteTagsInput();
    input.tags = TAGS;
    project().deleteTags(input);
    assertTagsDeleted();
    assertRefUpdatedEvents(initialRevisions);
  }

  @Test
  public void deleteTagsForbidden() throws Exception {
    DeleteTagsInput input = new DeleteTagsInput();
    input.tags = TAGS;
    requestScopeOperations.setApiUser(user.id());
    ResourceConflictException thrown =
        assertThrows(ResourceConflictException.class, () -> project().deleteTags(input));
    assertThat(thrown).hasMessageThat().isEqualTo(errorMessageForTags(TAGS));
    requestScopeOperations.setApiUser(admin.id());
    assertTags(TAGS);
  }

  @Test
  public void deleteTagsNotFound() throws Exception {
    DeleteTagsInput input = new DeleteTagsInput();
    List<String> tags = Lists.newArrayList(TAGS);
    tags.add("refs/tags/does-not-exist");
    input.tags = tags;
    ResourceConflictException thrown =
        assertThrows(ResourceConflictException.class, () -> project().deleteTags(input));
    assertThat(thrown)
        .hasMessageThat()
        .isEqualTo(errorMessageForTags(ImmutableList.of("refs/tags/does-not-exist")));
    assertTagsDeleted();
  }

  @Test
  public void deleteTagsNotFoundContinue() throws Exception {
    // If it fails on the first tag in the input, it should still
    // continue to process the remaining tags.
    DeleteTagsInput input = new DeleteTagsInput();
    List<String> tags = Lists.newArrayList("refs/tags/does-not-exist");
    tags.addAll(TAGS);
    input.tags = tags;
    ResourceConflictException thrown =
        assertThrows(ResourceConflictException.class, () -> project().deleteTags(input));
    assertThat(thrown)
        .hasMessageThat()
        .isEqualTo(errorMessageForTags(ImmutableList.of("refs/tags/does-not-exist")));
    assertTagsDeleted();
  }

  private String errorMessageForTags(List<String> tags) {
    StringBuilder message = new StringBuilder();
    for (String tag : tags) {
      message
          .append("Cannot delete ")
          .append(prefixRef(tag))
          .append(": it doesn't exist or you do not have permission ")
          .append("to delete it\n");
    }
    return message.toString();
  }

  private HashMap<String, RevCommit> initialRevisions(List<String> tags) throws Exception {
    HashMap<String, RevCommit> result = new HashMap<>();
    for (String tag : tags) {
      String ref = prefixRef(tag);
      result.put(ref, projectOperations.project(project).getHead(ref));
    }
    return result;
  }

  private void assertRefUpdatedEvents(HashMap<String, RevCommit> revisions) throws Exception {
    for (String tag : revisions.keySet()) {
      RevCommit revision = revisions.get(prefixRef(tag));
      eventRecorder.assertRefUpdatedEvents(
          project.get(), prefixRef(tag), null, revision, revision, null);
    }
  }

  private String prefixRef(String ref) {
    return ref.startsWith(R_TAGS) ? ref : R_TAGS + ref;
  }

  private ProjectApi project() throws Exception {
    return gApi.projects().name(project.get());
  }

  private void assertTags(List<String> expected) throws Exception {
    List<TagInfo> actualTags = project().tags().get();
    Iterable<String> actualNames = Iterables.transform(actualTags, b -> b.ref);
    assertThat(actualNames)
        .containsExactlyElementsIn(expected.stream().map(this::prefixRef).collect(toList()))
        .inOrder();
  }

  private void assertTagsDeleted() throws Exception {
    assertTags(ImmutableList.of());
  }
}
