package common.transaction;

import java.util.Objects;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.apache.logging.log4j.LogManager;

import core.config.Env;
import core.config.Factory;
import core.exception.PhysicalException;

/**
 * ユーザトランザクションユーティリティ
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class XATransactionImpl implements XATransaction {

	/** ローカル変数（ユーザトランザクション用） */
	private static final ThreadLocal<UserTransaction> UT = new ThreadLocal<>();

	/** ロック失敗時のリトライ数 */
	private static final String ENV_RETRY_MAX = "Transaction.RetryMax";
	/** ロック失敗時のスリープ時間（ミリ） */
	private static final String ENV_RETRY_SLEEP = "Transaction.RetrySleep";

	/** ユーザトランザクション */
	private static final String ENV_JTA_USER_TRAN = "Jta.UserTransaction";
	/** ユーザトランザクションタイムアウト */
	private static final String ENV_JTA_TIMEOUT = "Jta.TransactionTimeout";

	/** リトライ回数最大値 */
	private static final int RETRY_MAX = Env.getEnv(ENV_RETRY_MAX, 3);
	/** リトライスリープ値 */
	private static final int RETRY_SLEEP = Env.getEnv(ENV_RETRY_SLEEP, 300);

	/** リトライ回数 */
	private int retry = 0;

	/**
	 * トランザクション中確認
	 *
	 * @return トランザクション時 true を返す。
	 */
	@Override
	public boolean isInTransaction() {
		return Status.STATUS_ACTIVE == getStatus();
	}

	/**
	 * トランザクション開始
	 *
	 */
	@Override
	public void beginTransaction() {
		var utx = UT.get();
		try {
			if (utx == null) {
				utx = getUserTransaction();
			}

			if (utx != null) {
				UT.set(utx);

				utx.begin();
			}
		} catch (final SystemException | NotSupportedException ex) {
			LogManager.getLogger().error(ex.getMessage(), ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ユーザトランザクション取得
	 * @return ユーザトランザクション
	 * @throws SystemException SystemException
	 */
	private UserTransaction getUserTransaction() throws SystemException {

		UserTransaction utx = null;

		final var jndi = Env.getEnv(ENV_JTA_USER_TRAN);
		if (!Objects.toString(jndi, "").isEmpty()) {
			utx = lookup(jndi);
		} else {
			final var utp = Factory.create(UserTransactionProvider.class);
			if (utp != null) {
				utx = utp.getUserTransaction();
			}
		}

		if (utx != null) {
			// タイムアウト設定
			final var timeout = Env.getEnv(ENV_JTA_TIMEOUT, 0);
			if (0 < timeout) {
				utx.setTransactionTimeout(timeout);
			}
		}

		return utx;
	}

	/**
	 * JNDIルックアップ
	 * @param name JNDI名
	 * @return UserTransaction
	 */
	private UserTransaction lookup(final String name) {
		InitialContext ctx = null;
		try {
			// ユーザトランザクション取得
			ctx = new InitialContext();
			return UserTransaction.class.cast(ctx.lookup(name));
		} catch (final NamingException ex) {
			LogManager.getLogger().error(ex.getMessage(), ex);
			throw new PhysicalException(ex);
		} finally {
			if (ctx != null) {
				try {
					ctx.close();
				} catch (final NamingException ex) {
					LogManager.getLogger().warn(ex.getMessage(), ex);
				}
			}
		}
	}

	/**
	 * トランザクション終了
	 *
	 */
	@Override
	public void endTransaction() {
		final var utx = UT.get();
		UT.remove();
		if (utx != null) {
			try {
				if (Status.STATUS_NO_TRANSACTION != utx.getStatus()) {
					utx.rollback();
				}
			} catch (final SystemException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
			}
		}
	}

	/**
	 * コミット処理
	 *
	 * @return コミット時 true を返す。
	 */
	@Override
	public boolean commit() {
		final var utx = UT.get();
		if (utx != null) {
			try {
				if (Status.STATUS_ACTIVE != utx.getStatus()) {
					return false;
				}

				utx.commit();

			} catch (final HeuristicRollbackException | HeuristicMixedException
					| RollbackException | SystemException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
				throw new PhysicalException(ex);
			}
		}
		return true;
	}

	/**
	 * ステータス取得
	 *
	 * @return ステータス
	 */
	@Override
	public int getStatus() {
		final var utx = UT.get();
		if (utx != null) {
			try {
				return utx.getStatus();
			} catch (final SystemException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
				throw new PhysicalException(ex);
			}
		}
		return Status.STATUS_NO_TRANSACTION;
	}

	/**
	 * リトライ判定
	 *
	 * @return リトライ時 true を返す。
	 */
	@Override
	public boolean mayRetry() {
		this.retry += 1;
		if (RETRY_MAX <= this.retry) {
			return false;
		}

		endTransaction();
		try {
			Thread.sleep(RETRY_SLEEP);
		} catch (final InterruptedException ex) {
			LogManager.getLogger().info(ex.getMessage());
			return false;
		}
		beginTransaction();

		return true;
	}
}
