package io.flowing.retail.payment.resthacks;

import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;

import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;

import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.variable.Variables;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.builder.EndEventBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import io.flowing.retail.payment.resthacks.adapter.NotifySemaphorAdapter;

/**
 * Step5: Use Camunda state machine for long-running retry, external task & compensation
 */
@RestController
public class PaymentRestHacksControllerV5 {

  @Autowired
  private ProcessEngine camunda;

  @RequestMapping(path = "/payment/v5", method = PUT)
  public String retrievePayment(String retrievePaymentPayload, HttpServletResponse response) throws Exception {
    // TODO: Think about making the API indempotent, as I talk about that this should be done all the time :-)
    String traceId = UUID.randomUUID().toString();
    String customerId = "0815"; // get somehow from retrievePaymentPayload
    long amount = 15; // get somehow from retrievePaymentPayload

    Semaphore newSemaphore = NotifySemaphorAdapter.newSemaphore(traceId);    
    ProcessInstance pi = retrievePayment(traceId, customerId, amount);    
    boolean finished = newSemaphore.tryAcquire(500, TimeUnit.MILLISECONDS);
    NotifySemaphorAdapter.removeSemaphore(traceId);
    
    if (finished) {
      response.setStatus(HttpServletResponse.SC_OK);
      return resultUsingProcessHistory(traceId, pi.getId());
    } else {
      response.setStatus(HttpServletResponse.SC_ACCEPTED);
      return resultPending(traceId);
    }    
  }

  public ProcessInstance retrievePayment(String traceId, String customerId, long remainingAmount) {
    return camunda.getRuntimeService() //
        .startProcessInstanceByKey("paymentV5", traceId,//
            Variables.putValue("amount", remainingAmount));
  }

  @RequestMapping(path = "/payment/v5/{traceId}/status", method = GET)
  public String getPaymentStatus(@PathVariable("traceId") String traceId, HttpServletResponse response) throws Exception {
    HistoricProcessInstance processInstance = camunda.getHistoryService() //
            .createHistoricProcessInstanceQuery() //
            .processInstanceBusinessKey(traceId) //
            .singleResult();

    if (processInstance.getEndTime()!=null) { // process has ended
      return resultUsingProcessHistory(processInstance.getId(), traceId);
    } else { // process has not yet ended
      return resultPending(traceId);
    }
  }

  private String resultPending(String traceId) {
    return "{\"status\":\"pending\", \"traceId\": \"" + traceId + "\"}";
  }

  private String resultUsingProcessHistory(String processInstanceId, String traceId) {
    HistoricVariableInstance historicVariableInstance = camunda.getHistoryService().createHistoricVariableInstanceQuery()
            .processInstanceId(processInstanceId) //
            .variableName("paymentTransactionId") //
            .singleResult();
    if (historicVariableInstance!=null) {
      String paymentTransactionId = (String)historicVariableInstance.getValue();
      return "{\"status\":\"completed\", \"traceId\": \"" + traceId + "\", \"paymentTransactionId\": \""+paymentTransactionId+"\"}";
    } else {
      return "{\"status\":\"completed\", \"traceId\": \"" + traceId + "\", \"payedByCredit\": \"true\"}";
    }
  }

}
