package com.oguerreiro.resilient.activity;

import java.time.Instant;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;

import com.oguerreiro.resilient.domain.AbstractResilientLongIdEntity;
import com.oguerreiro.resilient.domain.ResilientLog;
import com.oguerreiro.resilient.domain.enumeration.ResilientLogLevel;
import com.oguerreiro.resilient.error.ResilientException;
import com.oguerreiro.resilient.error.ResilientRuntimeException;
import com.oguerreiro.resilient.service.ResilientLogService;

public abstract class AbstractTask<TR extends TaskResult, TG extends TaskGuide, TI extends Task<TR, TG>>
    extends AbstractSelfEnabler<TI> implements Task<TR, TG> {
  protected final Logger log = LoggerFactory.getLogger(this.getClass());

  @Autowired
  private ApplicationContext applicationContext;

  @Autowired
  private MessageSource messageSource;

  @Autowired
  private ResilientLogService resilientLogService;

  protected AbstractTask() {
  }

  /**
   * Sugar method to get a bean from {@link ApplicationContext}. Use this to get a a bean of scope
   * {@link ConfigurableBeanFactory#SCOPE_PROTOTYPE SCOPE_PROTOTYPE}.
   * Any other scope, just inject the bean into the class
   * 
   * @param  <B>
   * @param  beanClass
   * @return
   */
  protected <B> B getBean(Class<B> beanClass) {
    return applicationContext.getBean(beanClass);
  }

  public final String getLocalizedMessage(Throwable exception) {
    String localizedMessage = "";
    Locale locale = LocaleContextHolder.getLocale();

    if (exception instanceof ResilientException) {
      //This is a translatable Resilient message
      ResilientException resilientException = (ResilientException) exception;
      try {
        localizedMessage = messageSource.getMessage(resilientException.getMessageCode(),
            resilientException.getMessageArgs(), locale);
      } catch (NoSuchMessageException ex) {
        // Fall back to exception LocalizedMessage.
        localizedMessage = exception.getLocalizedMessage();
      }

      if (localizedMessage == null) {
        localizedMessage = resilientException.getMessageCode();
      }
    } else if (exception instanceof ResilientRuntimeException) {
      //This is a translatable Resilient message
      ResilientRuntimeException resilientException = (ResilientRuntimeException) exception;
      try {
        localizedMessage = messageSource.getMessage(resilientException.getMessageCode(),
            resilientException.getMessageArgs(), locale);
      } catch (NoSuchMessageException ex) {
        // Fall back to exception LocalizedMessage.
        localizedMessage = exception.getLocalizedMessage();
      }

      if (localizedMessage == null) {
        localizedMessage = resilientException.getMessageCode();
      }
    } else {
      localizedMessage = exception.getLocalizedMessage();
    }

    return localizedMessage;
  }

  protected void logError(AbstractResilientLongIdEntity<?> id, String username, Throwable error) {
    String localizedMessage = getLocalizedMessage(error);

    this.logError(id, username, localizedMessage);
  }

  protected void logError(AbstractResilientLongIdEntity<?> id, String username, String message) {
    this.log(ResilientLogLevel.ERROR, id, username, message);
  }

  protected void logInfo(AbstractResilientLongIdEntity<?> id, String username, String message) {
    this.log(ResilientLogLevel.INFO, id, username, message);
  }

  private void log(ResilientLogLevel level, AbstractResilientLongIdEntity<?> id, String username, String message) {
    ResilientLog log = new ResilientLog();

    //@formatter:off
    log
      .ownerId(id.getId())
      .level(level)
      .creationDate(Instant.now())
      .creationUsername(username)
      .logMessage(message);
    //@formatter:on

    this.resilientLogService.saveWithNewTransaction(log);
  }
}
