package com.pingcap.kb.integration.jira;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.pingcap.kb.integration.config.JiraConfiguration;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Component
public class JiraClient {

  private final ObjectMapper mapper;
  @Getter
  private final JiraConfiguration config;
  private final RestTemplate restTemplate;
  private final RetryTemplate retryTemplate;

  public JiraClient(JiraConfiguration config, RetryTemplate retryTemplate) {
    this.retryTemplate = retryTemplate;
    this.mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    this.config = config;
    restTemplate = new RestTemplateBuilder()
        .defaultHeader("Authorization", "Bearer " + config.getToken())
        .rootUri(config.getUrl())
        .build();
  }

  public List<JiraIssue> search(String jql, int startAt) {
    var list = restTemplate.getForObject("/rest/api/latest/search?jql={jql}&startAt={startAt}&maxResults=25", JiraList.class, jql, startAt);
    assert list != null;
    return list.getIssues().stream().peek(this::transformIssue)
        .flatMap(obj -> parse(obj).stream())
        .collect(Collectors.toList());
  }

  private void transformIssue(ObjectNode issueObject) {
    var fields = (ObjectNode) issueObject.get("fields");
    config.getCustomFields().forEach((k, v) -> {
      fields.set(v, fields.get(k));
    });
  }

  private Optional<JiraIssue> parse(ObjectNode obj) {
    try {
      return Optional.of(mapper.treeToValue(obj, JiraIssue.class));
    } catch (JsonProcessingException e) {
      log.warn("failed to deserialize issue {}", obj, e);
      return Optional.empty();
    }
  }

  public Optional<JiraIssue> get(String num) {
    try {
      ObjectNode issueObject = Optional.ofNullable(restTemplate.getForObject("/rest/api/latest/issue/{project}-{num}", ObjectNode.class, config.getProject(), num))
          .orElseThrow();
      transformIssue(issueObject);

      return parse(issueObject);
    } catch (RestClientException e) {
      log.warn("failed to find jira issue {}", num);
      return Optional.empty();
    }
  }

  public void update(String num, JiraUpdate update) {
    retryTemplate.execute(context -> {
      restTemplate.put("/rest/api/latest/issue/{project}-{num}", update, config.getProject(), num);
      return null;
    });
  }

  public String getLinkFor(String num) {
    return config.getUrl() + "/browse/" + config.getProject() + "-" + num;
  }
}
