package zielu.gittoolbox.ui.projectview;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.Mockito.when;

import com.google.common.collect.ImmutableList;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.ProjectViewNode;
import git4idea.GitLocalBranch;
import git4idea.repo.GitRepository;
import java.util.List;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import zielu.gittoolbox.cache.RepoInfo;
import zielu.gittoolbox.cache.RepoStatus;
import zielu.gittoolbox.config.DecorationPartConfig;
import zielu.gittoolbox.config.DecorationPartType;
import zielu.gittoolbox.config.GitToolBoxConfig2;
import zielu.gittoolbox.status.GitAheadBehindCount;
import zielu.gittoolbox.ui.StatusPresenters;

@ExtendWith(MockitoExtension.class)
abstract class NodeDecorationBaseTest {
  private static final String LOCATION_PATH = "/var/log/project";
  private static final String AHEAD_BEHIND = "1 // 1";
  private static final String BRANCH_NAME = "master";

  private static final String WS = "\\p{javaWhitespace}+";

  @Mock
  private GitRepository repository;
  @Mock
  private ProjectViewNode node;

  private GitToolBoxConfig2 config = new GitToolBoxConfig2();
  private GitAheadBehindCount count = GitAheadBehindCount.success(1, null, 1, null);
  private RepoInfo repoInfo = RepoInfo.create(RepoStatus.empty(), count, ImmutableList.of());

  @BeforeEach
  void before() {
    when(repository.getCurrentBranch()).thenReturn(new GitLocalBranch(BRANCH_NAME));
    config.setPresenter(StatusPresenters.text);
  }

  abstract NodeDecoration createDecoration(GitToolBoxConfig2 config, GitRepository repository, RepoInfo repoInfo);

  abstract DecorationData getDecorationData(PresentationData data);

  PresentationData presentationData(boolean location) {
    PresentationData data = presentationData();
    if (location) {
      data.setLocationString(LOCATION_PATH);
    }
    return data;
  }

  private PresentationData presentationData() {
    return new PresentationData();
  }

  PresentationData apply(PresentationData presentationData) {
    NodeDecoration decoration = createDecoration(config, repository, repoInfo);
    decoration.apply(node, presentationData);
    return presentationData;
  }

  private List<DecorationPartConfig> statusBeforeLocationParts() {
    return Lists.newArrayList(
        DecorationPartConfig.builder()
            .withType(DecorationPartType.BRANCH)
            .build(),
        DecorationPartConfig.builder()
            .withType(DecorationPartType.STATUS)
            .build(),
        DecorationPartConfig.builder()
            .withType(DecorationPartType.LOCATION)
            .build()
    );
  }

  private String decorationPattern(String... parts) {
    return WS + String.join(WS, parts);
  }

  @Test
  @DisplayName("Location is shown before status")
  void locationBeforeStatus() {
    PresentationData presentationData = apply(presentationData(true));

    DecorationData decorationData = getDecorationData(presentationData);
    assertThat(decorationData.text).matches(decorationPattern("-", LOCATION_PATH, BRANCH_NAME, AHEAD_BEHIND));
  }

  @Test
  @DisplayName("Status is shown when location before status and location is null")
  void locationBeforeStatusNullLocation() {
    PresentationData presentationData = apply(presentationData(false));

    DecorationData decorationData = getDecorationData(presentationData);
    assertSoftly(softly -> {
      softly.assertThat(decorationData.text)
          .matches(decorationPattern(BRANCH_NAME, AHEAD_BEHIND));
      softly.assertThat(decorationData.locationString)
          .isNull();
    });
  }

  @Test
  @DisplayName("Status is shown before location")
  void statusBeforeLocation() {
    config.setDecorationParts(statusBeforeLocationParts());

    PresentationData presentationData = apply(presentationData(true));

    DecorationData decorationData = getDecorationData(presentationData);
    assertSoftly(softly -> {
      softly.assertThat(decorationData.text)
          .matches(decorationPattern(BRANCH_NAME, AHEAD_BEHIND));
      softly.assertThat(decorationData.locationString)
          .matches(decorationPattern(LOCATION_PATH));
    });
  }

  @Test
  @DisplayName("Status is shown when status before location and location is null")
  void statusBeforeLocationNullLocation() {
    config.setDecorationParts(statusBeforeLocationParts());

    PresentationData presentationData = apply(presentationData(false));

    DecorationData decorationData = getDecorationData(presentationData);
    assertSoftly(softly -> {
      softly.assertThat(decorationData.text)
          .matches(decorationPattern(BRANCH_NAME, AHEAD_BEHIND));
      softly.assertThat(decorationData.locationString)
          .isNull();
    });
  }

  @Test
  @DisplayName("Status is shown when location is hidden")
  void statusWhenLocationBeforeStatusAndLocationNotShown() {
    config.setDecorationParts(Lists.newArrayList(
        DecorationPartConfig.builder()
            .withType(DecorationPartType.BRANCH)
            .build(),
        DecorationPartConfig.builder()
            .withType(DecorationPartType.STATUS)
            .build()
    ));

    PresentationData presentationData = apply(presentationData(true));

    DecorationData decorationData = getDecorationData(presentationData);
    assertThat(decorationData.text).matches(decorationPattern(BRANCH_NAME, AHEAD_BEHIND));
  }

  class DecorationData {
    final String text;
    final String locationString;

    DecorationData(String text, String locationString) {
      this.text = text;
      this.locationString = locationString;
    }
  }
}