// Copyright (C) 2019 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.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.entities.LabelFunction;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.common.BatchLabelInput;
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.restapi.project.PostLabels;
import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;

/** Tests for the {@link PostLabels} REST endpoint. */
public class PostLabelsIT extends AbstractDaemonTest {
  @Inject private RequestScopeOperations requestScopeOperations;
  @Inject private ProjectOperations projectOperations;

  @Test
  public void anonymous() throws Exception {
    requestScopeOperations.setApiUserAnonymous();
    AuthException thrown =
        assertThrows(
            AuthException.class,
            () -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
    assertThat(thrown).hasMessageThat().contains("Authentication required");
  }

  @Test
  public void notAllowed() throws Exception {
    projectOperations
        .project(allProjects)
        .forUpdate()
        .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
        .update();

    requestScopeOperations.setApiUser(user.id());
    AuthException thrown =
        assertThrows(
            AuthException.class,
            () -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
    assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
  }

  @Test
  public void deleteNonExistingLabel() throws Exception {
    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of("Foo");

    UnprocessableEntityException thrown =
        assertThrows(
            UnprocessableEntityException.class,
            () -> gApi.projects().name(allProjects.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Foo not found");
  }

  @Test
  public void deleteLabels() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);
    configLabel("Bar", LabelFunction.NO_OP);
    assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();

    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of("Foo", "Bar");
    gApi.projects().name(project.get()).labels(input);
    assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
  }

  @Test
  public void deleteLabels_labelNamesAreTrimmed() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);
    configLabel("Bar", LabelFunction.NO_OP);
    assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();

    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of(" Foo ", " Bar ");
    gApi.projects().name(project.get()).labels(input);
    assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
  }

  @Test
  public void cannotDeleteTheSameLabelTwice() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);

    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of("Foo", "Foo");

    UnprocessableEntityException thrown =
        assertThrows(
            UnprocessableEntityException.class,
            () -> gApi.projects().name(project.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Foo not found");
  }

  @Test
  public void cannotCreateLabelWithNameThatIsAlreadyInUse() throws Exception {
    LabelDefinitionInput labelInput = new LabelDefinitionInput();
    labelInput.name = LabelId.CODE_REVIEW;
    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(labelInput);

    ResourceConflictException thrown =
        assertThrows(
            ResourceConflictException.class,
            () -> gApi.projects().name(allProjects.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Code-Review already exists");
  }

  @Test
  public void cannotCreateTwoLabelsWithTheSameName() throws Exception {
    LabelDefinitionInput fooInput = new LabelDefinitionInput();
    fooInput.name = "Foo";
    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(fooInput, fooInput);

    ResourceConflictException thrown =
        assertThrows(
            ResourceConflictException.class,
            () -> gApi.projects().name(project.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Foo already exists");
  }

  @Test
  public void cannotCreateTwoLabelsWithNamesThatAreTheSameAfterTrim() throws Exception {
    LabelDefinitionInput foo1Input = new LabelDefinitionInput();
    foo1Input.name = "Foo";
    foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    LabelDefinitionInput foo2Input = new LabelDefinitionInput();
    foo2Input.name = " Foo ";
    foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(foo1Input, foo2Input);

    ResourceConflictException thrown =
        assertThrows(
            ResourceConflictException.class,
            () -> gApi.projects().name(project.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Foo already exists");
  }

  @Test
  public void cannotCreateTwoLabelsWithConflictingNames() throws Exception {
    LabelDefinitionInput foo1Input = new LabelDefinitionInput();
    foo1Input.name = "Foo";
    foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    LabelDefinitionInput foo2Input = new LabelDefinitionInput();
    foo2Input.name = "foo";
    foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(foo1Input, foo2Input);

    ResourceConflictException thrown =
        assertThrows(
            ResourceConflictException.class,
            () -> gApi.projects().name(project.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label foo conflicts with existing label Foo");
  }

  @Test
  public void createLabels() throws Exception {
    LabelDefinitionInput fooInput = new LabelDefinitionInput();
    fooInput.name = "Foo";
    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    LabelDefinitionInput barInput = new LabelDefinitionInput();
    barInput.name = "Bar";
    barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(fooInput, barInput);

    gApi.projects().name(allProjects.get()).labels(input);
    assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
    assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
  }

  @Test
  public void createLabels_labelNamesAreTrimmed() throws Exception {
    LabelDefinitionInput fooInput = new LabelDefinitionInput();
    fooInput.name = " Foo ";
    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    LabelDefinitionInput barInput = new LabelDefinitionInput();
    barInput.name = " Bar ";
    barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(fooInput, barInput);

    gApi.projects().name(allProjects.get()).labels(input);
    assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
    assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
  }

  @Test
  public void cannotCreateLabelWithoutName() throws Exception {
    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(new LabelDefinitionInput());

    BadRequestException thrown =
        assertThrows(
            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label name is required for new label");
  }

  @Test
  public void cannotSetCommitMessageOnLabelDefinitionInputForCreate() throws Exception {
    LabelDefinitionInput labelInput = new LabelDefinitionInput();
    labelInput.name = "Foo";
    labelInput.commitMessage = "Create Label Foo";

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(labelInput);

    BadRequestException thrown =
        assertThrows(
            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
    assertThat(thrown)
        .hasMessageThat()
        .contains("commit message on label definition input not supported");
  }

  @Test
  public void updateNonExistingLabel() throws Exception {
    BatchLabelInput input = new BatchLabelInput();
    input.update = ImmutableMap.of("Foo", new LabelDefinitionInput());

    UnprocessableEntityException thrown =
        assertThrows(
            UnprocessableEntityException.class,
            () -> gApi.projects().name(allProjects.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Foo not found");
  }

  @Test
  public void updateLabels() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);
    configLabel("Bar", LabelFunction.NO_OP);

    LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
    fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
    LabelDefinitionInput barUpdate = new LabelDefinitionInput();
    barUpdate.name = "Baz";

    BatchLabelInput input = new BatchLabelInput();
    input.update = ImmutableMap.of("Foo", fooUpdate, "Bar", barUpdate);

    gApi.projects().name(project.get()).labels(input);

    assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
        .isEqualTo(fooUpdate.function);
    assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
    assertThrows(
        ResourceNotFoundException.class,
        () -> gApi.projects().name(project.get()).label("Bar").get());
  }

  @Test
  public void updateLabels_labelNamesAreTrimmed() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);
    configLabel("Bar", LabelFunction.NO_OP);

    LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
    fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
    LabelDefinitionInput barUpdate = new LabelDefinitionInput();
    barUpdate.name = "Baz";

    BatchLabelInput input = new BatchLabelInput();
    input.update = ImmutableMap.of(" Foo ", fooUpdate, " Bar ", barUpdate);

    gApi.projects().name(project.get()).labels(input);

    assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
        .isEqualTo(fooUpdate.function);
    assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
    assertThrows(
        ResourceNotFoundException.class,
        () -> gApi.projects().name(project.get()).label("Bar").get());
  }

  @Test
  public void cannotSetCommitMessageOnLabelDefinitionInputForUpdate() throws Exception {
    LabelDefinitionInput labelInput = new LabelDefinitionInput();
    labelInput.commitMessage = "Update label";

    BatchLabelInput input = new BatchLabelInput();
    input.update = ImmutableMap.of(LabelId.CODE_REVIEW, labelInput);

    BadRequestException thrown =
        assertThrows(
            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
    assertThat(thrown)
        .hasMessageThat()
        .contains("commit message on label definition input not supported");
  }

  @Test
  public void deleteAndRecreateLabel() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);

    LabelDefinitionInput fooInput = new LabelDefinitionInput();
    fooInput.name = "Foo";
    fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of("Foo");
    input.create = ImmutableList.of(fooInput);

    gApi.projects().name(project.get()).labels(input);

    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
    assertThat(fooLabel.function).isEqualTo(fooInput.function);
  }

  @Test
  public void deleteRecreateAndUpdateLabel() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);

    LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
    fooCreateInput.name = "Foo";
    fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
    fooCreateInput.values =
        ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
    fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();

    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of("Foo");
    input.create = ImmutableList.of(fooCreateInput);
    input.update = ImmutableMap.of("Foo", fooUpdateInput);

    gApi.projects().name(project.get()).labels(input);

    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
    assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
  }

  @Test
  public void cannotDeleteAndUpdateLabel() throws Exception {
    configLabel("Foo", LabelFunction.NO_OP);

    LabelDefinitionInput fooInput = new LabelDefinitionInput();
    fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();

    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of("Foo");
    input.update = ImmutableMap.of("Foo", fooInput);

    UnprocessableEntityException thrown =
        assertThrows(
            UnprocessableEntityException.class,
            () -> gApi.projects().name(project.get()).labels(input));
    assertThat(thrown).hasMessageThat().contains("label Foo not found");
  }

  @Test
  public void createAndUpdateLabel() throws Exception {
    LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
    fooCreateInput.name = "Foo";
    fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
    fooCreateInput.values =
        ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");

    LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
    fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();

    BatchLabelInput input = new BatchLabelInput();
    input.create = ImmutableList.of(fooCreateInput);
    input.update = ImmutableMap.of("Foo", fooUpdateInput);

    gApi.projects().name(project.get()).labels(input);

    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
    assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
  }

  @Test
  public void noOpUpdate() throws Exception {
    RevCommit refsMetaConfigHead =
        projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG);

    gApi.projects().name(allProjects.get()).labels(new BatchLabelInput());

    assertThat(projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG))
        .isEqualTo(refsMetaConfigHead);
  }

  @Test
  public void defaultCommitMessage() throws Exception {
    BatchLabelInput input = new BatchLabelInput();
    input.delete = ImmutableList.of(LabelId.CODE_REVIEW);
    gApi.projects().name(allProjects.get()).labels(input);
    assertThat(
            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
        .isEqualTo("Update labels");
  }

  @Test
  public void withCommitMessage() throws Exception {
    BatchLabelInput input = new BatchLabelInput();
    input.commitMessage = "Batch Update Labels";
    input.delete = ImmutableList.of(LabelId.CODE_REVIEW);
    gApi.projects().name(allProjects.get()).labels(input);
    assertThat(
            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
        .isEqualTo(input.commitMessage);
  }

  @Test
  public void commitMessageIsTrimmed() throws Exception {
    BatchLabelInput input = new BatchLabelInput();
    input.commitMessage = " Batch Update Labels ";
    input.delete = ImmutableList.of(LabelId.CODE_REVIEW);
    gApi.projects().name(allProjects.get()).labels(input);
    assertThat(
            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
        .isEqualTo("Batch Update Labels");
  }
}
