package com.capgemini.framework.teamcenter.authent;

import com.capgemini.framework.teamcenter.exceptions.runtime.ConnectionLostException;
import com.teamcenter.schemas.soa._2006_03.exceptions.ConnectionException;
import com.teamcenter.schemas.soa._2006_03.exceptions.InternalServerException;
import com.teamcenter.schemas.soa._2006_03.exceptions.ProtocolException;
import com.teamcenter.soa.client.ExceptionHandler;
import com.teamcenter.soa.exceptions.CanceledOperationException;
import lombok.extern.slf4j.Slf4j;

/** Implementation of the ExceptionHandler for Teamcenter requests. **/
@Slf4j
public class TcExceptionHandler implements ExceptionHandler {

    /** If delay between two Exceptions is higher than this, then the two exceptions are non-related, so we can restart the countdown, ms. */
    private static final long MAX_INTERVAL_BETWEEN_BREAKUPS = 60 * 60 * 1000; // 60 minutes
    /** Number maximum of consecutive retries before fatal exception is thrown. */
    private static final int MAX_COUNTDOWN = 10;
    /** Wait time between consecutive retries, ms. */
    private static final int WAIT_BETWEEN_RETRIES_MS = 10000; // 10s

    long firstErrorTimeMillis;
    int countdown;

    /**
     * Creates an Exception Handler. <br>
     * The typical behaviour of this handler when subject to an {@link InternalServerException} is the following:
     * <ul>
     * <li>If the exception is a {@link ProtocolException}, a RuntimeException is thrown to interrupt report generation, as these ProtocolException are usually not recoverable from.</li>
     * <li>If the exception is a {@link ConnectionException}, then the following happens:
     * <ol>
     * <li>The connection is retried after a delay of</li>
     * <li></li>
     * </ol>
     * </li>
     * <li>Otherwise the exception is an unknown InternalServerException, a RuntimeException (actually a {@link ConnectionLostException}) is thrown</li>
     * </ul>
     */
    public TcExceptionHandler() {
        firstErrorTimeMillis = System.currentTimeMillis();
        countdown = 0;
    }

    /**
     * Takes care of {@link InternalServerException}s.
     * 
     * @see com.teamcenter.soa.client.ExceptionHandler#handleException(com.teamcenter.schemas.soa._2006_03.exceptions.InternalServerException)
     */
    @Override
    public void handleException(final InternalServerException anException) {
        if (anException instanceof ProtocolException) {
            // ProtocolException are typically due to programming errors (content of HTTP request is incorrect). These generally cannot be recovered from.
            throw new ConnectionLostException("Internal Teamcenter ProtocolException encountered, not recoverable", anException);
        }
        else if (anException instanceof ConnectionException) {
            handleTemporaryConnectionException((ConnectionException) anException);
        }
        else {
            throw new ConnectionLostException("InternalServerException encountered, not recoverable", anException);
        }
    }

    private void handleTemporaryConnectionException(final ConnectionException anException) {
        // ConnectionException are typically due to a network error (server down .etc) and can be recovered from (the last request can be sent again, after the problem is corrected).
        long time = System.currentTimeMillis();
        if (time > MAX_INTERVAL_BETWEEN_BREAKUPS + firstErrorTimeMillis) {
            // This is the first Exception of a new series of errors
            countdown = 0;
            firstErrorTimeMillis = time;
            log.error("The teamcenter server returned an protocol error: " + anException.getMessage(), anException);
        }
        else {
            // This is the continuation of an on-going series of error, so we increment the countdown
            countdown++;
        }
        if (countdown > MAX_COUNTDOWN) {
            // The maximum number of consecutive errors has been reached, the connection is definitely lost, let's findAll out of here!
            throw new ConnectionLostException("Maximum number of retries reached during connection loss", anException);
        }
        else {
            // To let the TC servers time to recover, let's wait a bit
            try {
                String msg = "A series of " + countdown +
                        " errors from the Teamcenter servers has been detected. A retry will be attempted within " +
                        WAIT_BETWEEN_RETRIES_MS + "ms. Last exception encountered: " + anException.getMessage();
                log.error(msg, anException);
                Thread.sleep(WAIT_BETWEEN_RETRIES_MS);
            }
            catch (InterruptedException e) {
                log.info("Exception handler was interrupted during sleep, while awaiting to recover from a previous connection exception ("
                        + anException.getMessage() + ")", e); // Oh, the irony!
            }
        }
        // Continuing normally will eventually lead to re-trying to perform the operation that did not succeed because of the InternalServerException
    }

    /**
     * Takes care of {@link CanceledOperationException}s.
     * 
     * @see com.teamcenter.soa.client.ExceptionHandler#handleException(com.teamcenter.soa.exceptions.CanceledOperationException)
     */
    @Override
    public void handleException(final CanceledOperationException aCanceledOpException) {
        log.error("Teamcenter server returned a CanceledOperationException:\nIt could be caused by invalid credential during login operation",
                aCanceledOpException);
    }

}
