package batch.base;

import java.sql.SQLException;
import java.sql.Timestamp;

import org.apache.logging.log4j.LogManager;

import batch.controller.JobUtil;
import batch.status.JobDetailStatus;
import batch.status.JobFileStatus;
import batch.status.JobState;
import common.db.ExclusiveException;
import core.config.Env;
import core.config.Factory;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.DateUtil;

/**
 * バッチ実行実装
 *
 * @author Tadashi Nakayama
 * @param <B> Type
 * @param <P> Type
 */
public class BatchProcessorImpl<B extends Batch, P extends BatchParameter>
		implements BatchProcessor {

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

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

	/** 処理日時 */
	private final Timestamp dateTime = DateUtil.getDateTime();
	/** 実行パラメタ */
	private final P param;
	/** 実行バッチクラス */
	private final Class<B> cls;

	/** 実行バッチ */
	private B batch;

	/**
	 * コンストラクタ
	 *
	 * @param bat 実行バッチ
	 * @param prm 実行パラメタ
	 */
	public BatchProcessorImpl(final Class<B> bat, final Class<P> prm) {
		this.cls = bat;
		this.param = Factory.create(prm);
		this.param.setDateTime(this.dateTime);
		this.batch = getBatchInstance(bat);
	}

	/**
	 * 処理開始
	 *
	 * @param args 引数
	 * @param cls 実行バッチ
	 * @return 処理結果
	 */
	public static int start(final Class<? extends Batch> cls, final String... args) {
		return new BatchProcessorImpl<>(cls, BatchParameterImpl.class).execute(args);
	}

	/**
	 * バッチオブジェクト取得
	 *
	 * @return バッチオブジェクト
	 */
	public B getBatch() {
		return this.batch;
	}

	/**
	 * バッチインスタンス作成
	 *
	 * @param bat バッチクラス
	 * @return バッチインスタンス
	 */
	private B getBatchInstance(final Class<B> bat) {
		final B ret = Factory.create(bat);
		ret.setBatchParameter(this.param);
		return ret;
	}

	/**
	 * 開始前処理を行う。
	 *
	 * @param args 引数
	 * @return 終了ステータス
	 */
	private int execute(final String... args) {
		try {
			// 前処理
			var exitCode = preprocess(args);
			if (exitCode == Batch.RET_SUCCESS) {
				// 処理実行
				exitCode = process(args);
			}
			// 処理結果書き込み
			return postprocess(exitCode);

		} catch (final PhysicalException ex) {
			LogManager.getLogger().info(ex.getMessage());
			return Batch.RET_FAILED;
		} catch (final Throwable t) {
			ThrowableUtil.error(t);
			return Batch.RET_FAILED;
		}
	}

	/**
	 * 前処理
	 *
	 * @param args 引数
	 * @return 終了ステータス
	 */
	@Override
	public int preprocess(final String... args) {
		this.param.setCommandParameter(args);
		if (!this.param.setupOnline(this.batch.getBatchName(), this.dateTime)) {
			return Batch.RET_CANCELED;
		}
		return Batch.RET_SUCCESS;
	}

	/**
	 * 実行処理
	 *
	 * @param prms 引数
	 * @return 終了ステータス
	 */
	@Override
	public int process(final String... prms) {
		var exitCode = 0;

		try {
			var retry = 0;
			while (true) {
				try {
					// メソッド呼び出し
					exitCode = this.batch.perform(this.param.getParameter());
					break;
				} catch (final ExclusiveException ex) {
					retry++;
					if (RETRY_MAX <= retry) {
						exitCode = Batch.RET_FAILED;
						ThrowableUtil.warn(ex);
						break;
					}
				}
				Thread.sleep(RETRY_SLEEP);
				this.batch = getBatchInstance(this.cls);
			}
		} catch (final InterruptedException ex) {
			LogManager.getLogger().info(ex.getMessage());
			exitCode = Batch.RET_FAILED;
		}

		return exitCode;
	}

	/**
	 * 処理結果更新
	 *
	 * @param exitCode 終了コード
	 * @return 終了ステータス
	 */
	@Override
	public int postprocess(final int exitCode) {
		return (this.param.getJobSeq() == 0) ? exitCode : updateDetail(exitCode);
	}

	/**
	 * ジョブ詳細管理更新
	 *
	 * @param exitCode 終了コード
	 * @return 終了ステータス
	 */
	private int updateDetail(final int exitCode) {

		final var jds = Factory.create(JobDetailStatus.class);
		final var jfs = Factory.create(JobFileStatus.class);

		final var jobseq = this.param.getJobSeq();
		final var dtlSeq = this.param.getJobDtlSeq();

		try (var conn = JobUtil.getConnection()) {
			if (jds.getJobDetail(conn, jobseq, dtlSeq) != null) {
				// ジョブ詳細管理更新
				jds.updateJobDetail(conn, jobseq, dtlSeq, this.batch.getMessage(),
								toJobState(exitCode), DateUtil.getDateTime());
			}
			// ジョブファイル更新
			jfs.setFiles(conn, jobseq, dtlSeq, this.batch.getOutputFiles());

			conn.commit();
			return exitCode;

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			return Batch.RET_DB_ERROR;
		}
	}

	/**
	 * ジョブ状態変換
	 *
	 * @param exitCode 終了コード
	 * @return ジョブ状態
	 */
	private JobState toJobState(final int exitCode) {
		var sts = JobState.ID_B_INVALID;
		if (exitCode == Batch.RET_SUCCESS) {
			sts = JobState.ID_B_NEND;
		} else if (exitCode == Batch.RET_NODATA) {
			sts = JobState.ID_B_NODATA;
		} else if (exitCode == Batch.RET_WARNING) {
			sts = JobState.ID_B_WARNING;
		}
		return sts;
	}
}
