package de.ivu.elect.business.system.control;

import de.ivu.elect.business.system.boundary.CacheResetSession;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.BackgroundServiceCounter;
import de.ivu.elect.business.system.entity.BackgroundServiceStatusFactory;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.jee.background.AbstractBackgroundService;
import de.ivu.elect.jee.exception.ExceptionUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.EntityNotFoundException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;

public abstract class AbstractElectBackgroundService extends AbstractBackgroundService {

  private static final Logger LOG = LoggerFactory.getLogger(AbstractElectBackgroundService.class);

  protected final BackgroundServiceCounter backgroundServiceCounter = new BackgroundServiceCounter();

  @Inject
  protected transient SystemLogWriter systemLogWriter;

  @Inject
  private transient CacheResetSession cacheResetSession;

  @PostConstruct
  public void createStatusFactory() {
    getLog().info("createStatusFactory: " + this.getClass() + ", " + this);
    Consumer<Boolean> disabler = disabled -> {
      getDisabledInternal().set(disabled);
      getRunning().set(RUNNING_INITIAL); // ELECT-8258
      if (!disabled) {
        getErrorInFolgeCount().set(0); // Beim Aktivieren Fehler wieder von vorne zählen
      }
    };
    var previous = BackgroundServiceStatusProvider.addFactory(getSystemLogQuelle(),
        new BackgroundServiceStatusFactory(getSystemLogQuelle(), running::get,
            getRunning()::get,
            getDisabledInternal()::get, backgroundServiceCounter.todoCheckCount::get, backgroundServiceCounter.invocationCount::get,
            backgroundServiceCounter.invocationErrors::get, backgroundServiceCounter.invocationMs::get,
            () -> disabler, backgroundServiceCounter.paralleleAusfuehrungen::get, backgroundServiceCounter.anzahlSlots::get));

    if (previous != null) {
      getLog().warn("Doppelter Eintrag: " + previous + ", " + this);
    }

    postInit();

  }

  protected void postInit() {
    // nichts zu tun
  }

  @Override
  protected boolean isSomethingToDo() {
    if (isDisabled()) {
      return false;
    }
    try {
      backgroundServiceCounter.todoCheckCount.addAndGet(1L);
      return isSomethingToDoInternal();
    } catch (Exception e) {
      if (isEntityNotFoundException(e)) {
        resetCache(e);
      } else if (isRetryAfterException(e)) {
        getLog().warn("Fehler beim Bestimmen von Status Hintergrundprozess, versuche erneut. " + e.getMessage() + ", " + e.getClass());
      } else {
        systemLogWriter.writeErrorLogMessageFuerSystemMandant("Fehler in Hintergrundprozess (I)", e, getSystemLogQuelle());
      }
      return false;
    }
  }

  @Override
  protected void doSomething() {
    if (!isDisabled()) {
      var start = System.currentTimeMillis();
      try {
        doSomethingInternal();
        backgroundServiceCounter.errorInFolgeCount.set(0);
      } catch (Exception e) {
        if (isEntityNotFoundException(e)) {
          resetCache(e);
        }
        backgroundServiceCounter.invocationErrors.addAndGet(1L);
        if (backgroundServiceCounter.errorInFolgeCount.addAndGet(1L) > BackgroundServiceRetry.MAX_ERROR_COUNT.get()) {
          systemLogWriter.writeErrorLogMessageFuerSystemMandant(
              "Zu viele Fehler in Hintergrundprozess (II) - DEAKTIVIERT Hintergrunddienst muss wieder aktiviert werden", e, getSystemLogQuelle());
          setDisabled();
        } else {
          if (!isEntityNotFoundException(e)) {
            if (isRetryAfterException(e)) {
              // ELECT-6706
              systemLogWriter.writeInfoLogMessageFuerSystemMandant("DB-Lock in Hintergrundprozess (IV), Ausführung wird wiederholt (" + e.getMessage() + ")",
                  getSystemLogQuelle());
            } else {
              systemLogWriter.writeErrorLogMessageFuerSystemMandant("Fehler in Hintergrundprozess (III)", e, getSystemLogQuelle());
            }
          }
        }
      } finally {
        var duration = System.currentTimeMillis() - start;
        if (isCountInvocatiom()) {
          backgroundServiceCounter.invocationMs.addAndGet(duration);
          backgroundServiceCounter.invocationCount.addAndGet(1L);
        }
      }
    }
  }

  protected boolean isCountInvocatiom() {
    return true;
  }

  protected void resetCache(Exception e) {
    try {
      LOG.info("Reset Cache");
      cacheResetSession.resetCache();
    } catch (Exception ex) {
      // ignore:
      getLog().warn("Fehler beim Zurücksetzen des Cache: " + ex.getMessage());
    }
  }

  protected boolean isEntityNotFoundException(Throwable e) {
    if (e == null) {
      return false;
    }
    if (e instanceof EntityNotFoundException) {
      return true;
    }
    return isEntityNotFoundException(e.getCause());
  }

  protected boolean isRetryAfterException(Exception e) {
    return ExceptionUtil.isDatabaseLockException(e) || isWeld2492Bug(e);
  }

  /**
   * ELECT-8179
   */
  protected boolean isWeld2492Bug(Throwable e) {
    if (e == null) {
      return false;
    }
    // https://issues.jboss.org/browse/WELD-2492
    if (!StringUtils.isBlank(e.getMessage()) && e.getMessage().contains("WFLYEE0042: Failed to construct component instance")) {
      getLog().warn("WELD-2492: " + e.getMessage());
      return true;
    }
    return isWeld2492Bug(e.getCause());

  }

  protected abstract AtomicBoolean getDisabledInternal();

  protected abstract boolean isSomethingToDoInternal();

  protected abstract void doSomethingInternal();

  public abstract SystemLogQuelle getSystemLogQuelle();

  protected void setDisabled() {
    getDisabledInternal().set(true);
  }

  @Override
  protected boolean isDisabled() {
    return getDisabledInternal().get();
  }

  protected static AtomicBoolean createDisabled() {
    return new AtomicBoolean(false);
  }

  protected AtomicLong getErrorInFolgeCount() {
    return backgroundServiceCounter.errorInFolgeCount;
  }

  protected Logger getLog() {
    // TODO in Implementierungen überschreiben
    return LOG;
  }
}
