content: package fr.mmarie.core.jira;
  
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Maps;
  import com.google.inject.Inject;
  import com.squareup.okhttp.OkHttpClient;
  import com.squareup.okhttp.Request;
  import fr.mmarie.api.jira.Comment;
  import fr.mmarie.api.jira.Transition;
  import fr.mmarie.api.jira.input.TransitionInput;
  import fr.mmarie.api.jira.response.CommentResponse;
  import fr.mmarie.api.jira.response.TransitionResponse;
  import lombok.NonNull;
  import lombok.extern.slf4j.Slf4j;
  import retrofit.Callback;
  import retrofit.JacksonConverterFactory;
  import retrofit.Response;
  import retrofit.Retrofit;
  import retrofit.RxJavaCallAdapterFactory;
  
  import java.io.IOException;
  import java.util.Base64;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import static fr.mmarie.utils.Common.sanitizeURL;
  
  @Slf4j
  public class JiraService {
      public static final String TRANSITION_HOLDER = "{transition}";
  
      /**
       * Matches issues with name like : #TEST-1, does not support special characters in
       * project name, #TEST-TEST-1 won't match.
       */
      public static final Pattern ISSUE_PATTERN = Pattern.compile("#\\s*(\\w+-\\d+)");
  
      private final JiraConfiguration jiraConfiguration;
  
      private final JiraEndPoints jiraEndPoints;
  
      @Inject
      public JiraService(@NonNull JiraConfiguration jiraConfiguration) {
          this.jiraConfiguration = jiraConfiguration;
  
          OkHttpClient httpClient = new OkHttpClient();
          httpClient.interceptors().add(chain -> {
              String credentials = jiraConfiguration.getUsername() + ":" + jiraConfiguration.getPassword();
              String encodedHeader = "Basic " + new String(Base64.getEncoder().encode(credentials.getBytes()));
  
              Request requestWithAuthorization = chain.request().newBuilder().addHeader("Authorization", encodedHeader).build();
              return chain.proceed(requestWithAuthorization);
          });
  
          this.jiraEndPoints = new Retrofit.Builder()
                  .baseUrl(sanitizeURL(jiraConfiguration.getUrl()))
                  .addConverterFactory(JacksonConverterFactory.create())
                  .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                  .client(httpClient)
                  .build()
                  .create(JiraEndPoints.class);
      }
  
      public Response<Object> getIssue(String issue) throws IOException {
          return jiraEndPoints.getIssue(issue).execute();
      }
  
      public Response<CommentResponse> getCommentsOfIssue(String issue) throws IOException {
          return jiraEndPoints.getCommentsOfIssue(issue).execute();
      }
  
      public Response<Comment> commentIssue(String issue, Comment comment) throws IOException {
          return jiraEndPoints.commentIssue(issue, comment).execute();
      }
  
      public TransitionResponse getTransitionsOfIssue(String issue) {
          return jiraEndPoints.getTransitionsOfIssue(issue).toBlocking().firstOrDefault(null);
      }
  
      public boolean transitionOnIssue(String issue, TransitionInput transitionInput) {
          try {
              Response<Void> response = jiraEndPoints.transitionsOnIssue(issue, transitionInput).execute();
              if(response.isSuccess()) {
                  log.info("Transition {} has been made on {}", transitionInput, issue);
                  return true;
              } else {
                  log.error("Bad response received <" + response.code() + ">, "
                                  + "unable to make the transition <{}> on <{}>, received message : " + response.message(),
                          issue,
                          transitionInput);
  
                  try {
                      log.error("Response body received : {}",
                              response.errorBody().string());
                  } catch (IOException e) {
                      log.error("Unable to read response body", e);
                  }
              }
          } catch (IOException e) {
              log.error("Unable to perform transition <{}> on <{}>", transitionInput, issue);
          }
  
          return false;
      }
  
      public Response<Map<String, Object>> serverInfo() throws IOException {
          return jiraEndPoints.serverInfo().execute();
      }
  
      /**
       * Extracts issues names from given {@code message}.
       *
       * @param message Commit message
       * @return Matching issues name
       */
      public List<String> extractIssuesFromMessage(String message) {
          List<String> issues = Lists.newArrayList();
  
          Matcher matcher = ISSUE_PATTERN.matcher(message);
  
          while (matcher.find()) {
              issues.add(matcher.group(1));
          }
  
          return issues;
      }
  
      /**
       * Extracts all transitions from given {@code message}.
       *
       * @param message Commit message
       * @return Matching [ISSUE, TRANSITION]
       */
      public Map<String, String> extractMatchingTransitionsFromMessage(String message) {
          Map<String, String> matchingTransition = Maps.newHashMap();
  
          List<String> issues = extractIssuesFromMessage(message);
  
          issues.forEach(issue -> {
              Optional<String> optionalTransition = extractMatchingTransitionsFromMessage(message, issue);
              if(optionalTransition.isPresent()) {
                  matchingTransition.put(issue, optionalTransition.get());
              }
          });
  
          return matchingTransition;
      }
  
      /**
       * Extracts first matching transition from given {@code message} related to the given issue.
       *
       * @param message Commit message
       * @return Matching transition if it exists
       */
      public Optional<String> extractMatchingTransitionsFromMessage(String message, String issue) {
          List<TransitionConfiguration> transitions = jiraConfiguration.getTransitions();
  
          for (TransitionConfiguration transition : transitions) {
              for (String keyword : transition.getKeywords()) {
                  String regex = keyword.toLowerCase() + " #" + issue.toLowerCase();
                  Matcher matcher = Pattern.compile(regex).matcher(message.toLowerCase());
  
                  if(matcher.find()) {
                      return Optional.of(transition.getName());
                  }
              }
          }
  
          return Optional.empty();
      }
  
      public boolean performTransition(String message, String issue, String comment) {
          Optional<String> optionalTransitionName = extractMatchingTransitionsFromMessage(message, issue);
  
          if(optionalTransitionName.isPresent()) {
              String transitionName = optionalTransitionName.get();
              Optional<Transition> optionalTransition = getTransition(issue, transitionName);
  
              if(optionalTransition.isPresent()) {
                  log.info("Performing transition <{}> for issue <{}>", transitionName, issue);
                  TransitionInput.CommentWrapper commentWrapper =
                          new TransitionInput.CommentWrapper(new Comment(comment.replace(TRANSITION_HOLDER, transitionName)));
  
                  final TransitionInput transitionInput = new TransitionInput(
                          new TransitionInput.Update(ImmutableList.of(commentWrapper)),
                          optionalTransition.get()
                  );
  
                  return transitionOnIssue(issue, transitionInput);
              } else {
                  log.warn("Transaction <{}> does not exists, issue <{}> can not be edited",
                          transitionName,
                          issue);
              }
          }
  
          return false;
      }
  
      public boolean isExistingIssue(String issue) {
          try {
              return (getIssue(issue).code() == 200);
          } catch (Exception e) {
              log.error("Unable to get issue <{}>", issue, e);
              return false;
          }
      }
  
      public Optional<Transition> getTransition(String issue, String name) {
          return getTransitionsOfIssue(issue)
                  .getTransitions()
                  .stream()
                  .filter(transition -> transition.getName().equalsIgnoreCase(name))
                  .findFirst();
      }
  
      public boolean isExistingTransition(String issue, String name) {
          return getTransition(issue, name).isPresent();
      }
  
      public boolean isIssueAlreadyCommented(String issue, String commitId) {
          try {
              List<Comment> comments = getCommentsOfIssue(issue).body().getComments();
              return comments.stream().anyMatch(comment -> comment.getBody().contains(commitId));
          } catch (IOException e) {
              return true;
          }
      }
  }
  