package org.gbif.registry.cli;

import org.gbif.api.model.common.DOI;
import org.gbif.api.model.common.DoiData;
import org.gbif.api.model.common.DoiStatus;
import org.gbif.common.messaging.AbstractMessageCallback;
import org.gbif.common.messaging.api.messages.ChangeDoiMessage;
import org.gbif.doi.service.DoiException;
import org.gbif.doi.service.DoiHttpException;
import org.gbif.doi.service.DoiService;
import org.gbif.registry.persistence.mapper.DoiMapper;

import java.net.URI;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

/**
 * Message callback implementation to take DOI updates and send them to DataCite. Updates the status of the DOI in
 * the registry database.
 */
public class DoiUpdateListener extends AbstractMessageCallback<ChangeDoiMessage> {

  private static final Logger LOG = LoggerFactory.getLogger(DoiUpdateListener.class);
  private static final Marker DOI_SMTP = MarkerFactory.getMarker("DOI_SMTP");

  private final DoiService doiService;
  private final DoiMapper doiMapper;

  public DoiUpdateListener(DoiService doiService, DoiMapper doiMapper) {
    this.doiService = doiService;
    this.doiMapper = doiMapper;
  }

  @Override
  public void handleMessage(ChangeDoiMessage msg) {
    LOG.debug("Handling change DOI to {} message for {}", msg.getStatus(), msg.getDoi());
    final DoiData currState = doiMapper.get(msg.getDoi());
    if (currState == null) {
      // this is bad, we should have an entry for the DOI in our registry table!
      LOG.warn("Skipping unknown GBIF DOI {}", msg.getDoi());
      return;
    }

    while (true) {
      try {
        switch (msg.getStatus()) {
          case REGISTERED:
            register(msg.getDoi(), msg.getTarget(), msg.getMetadata(), currState);
            break;
          case RESERVED:
            reserve(msg.getDoi(), msg.getMetadata(), currState);
            break;
          case DELETED:
            delete(msg.getDoi(), currState);
            break;
          default:
            LOG.warn("Cannot update {} to illegal state {}.", msg.getDoi(), msg.getStatus());
            break;
        }
        break;
      } catch (DoiException e) {
        LOG.warn(DOI_SMTP, "DOI exception updating {} to {}. Trying again in 5 minutes", msg.getDoi(), msg.getStatus(),
          e);
        try {
          Thread.sleep(TimeUnit.MINUTES.toMillis(5));
        } catch (InterruptedException e1) {
          LOG.info("Interrupted eternal retry");
          break;
        }
      }
    }
  }

  private void reserve(DOI doi, String xml, DoiData currState) throws DoiException {
    doiService.reserve(doi, xml);
    DoiData newState = new DoiData(DoiStatus.RESERVED, currState.getTarget());
    doiMapper.update(doi, newState, xml);
  }

  private void delete(DOI doi, DoiData currState) throws DoiException {
    if (currState.getStatus() == null) {
      doiMapper.delete(doi);
    } else {
      try {
        boolean fullDeleted = doiService.delete(doi);
        if (fullDeleted) {
          doiMapper.delete(doi);
        } else {
          DoiData newState = new DoiData(DoiStatus.DELETED, currState.getTarget());
          doiMapper.update(doi, newState, null);
        }
      } catch (DoiHttpException e) {
        // in case of a 404 swallow
        if (e.getStatus() == 404) {
          LOG.warn(DOI_SMTP, "Trying to delete DOI {} failed because it doesn't exist in DataCite; deleting locally",
            doi, e);
          doiMapper.delete(doi);
        } else {
          throw e;
        }
      }
    }
  }

  private void register(DOI doi, URI target, String xml, DoiData currState) throws DoiException {
    doiService.register(doi, target, xml);
    DoiData newState = new DoiData(DoiStatus.REGISTERED, target);
    doiMapper.update(doi, newState, xml);
  }
}

@@ -305,32 +305,80 @@
 e.DoiException;%0A
+import org.gbif.doi.service.DoiExistsException;%0A
 import org.gbif.
@@ -1816,16 +1816,24 @@
 register
+OrUpdate
 (msg.get
@@ -2256,24 +2256,211 @@
      break;%0A
+%0A      %7D catch (DoiExistsException e) %7B%0A        LOG.warn(DOI_SMTP, %22DOI %7B%7D existed already when trying to change status to %7B%7D. Ignore%22, msg.getDoi(), msg.getStatus(), e);%0A        break;%0A%0A
       %7D catc
@@ -2599,26 +2599,16 @@
 tatus(),
-%0A         
  e);%0A   
@@ -3866,16 +3866,24 @@
 register
+OrUpdate
 (DOI doi
@@ -3957,110 +3957,355 @@
     
-doiService.register(doi, target, xml);%0A    DoiData newState = new DoiData(DoiStatus.REGISTERED
+if (DoiStatus.REGISTERED == currState.getStatus()) %7B%0A      // the DOI was already registered, so we only need to update the target url if changed and the metadata%0A      if (!target.equals(currState.getTarget())) %7B%0A        doiService.update(doi, target);%0A      %7D%0A      doiService.update(doi, xml);%0A    %7D else %7B%0A      doiService.register(doi
 , target
 );%0A 
@@ -4304,45 +4304,132 @@
 rget
+, xml
 );%0A    
-doiMapper.update(doi, newState
+%7D%0A    // store the new state in our registry%0A    doiMapper.update(doi, new DoiData(DoiStatus.REGISTERED, target)
 , xm

Allow the DOI listener to register or just update an existing DOI