package batch.base;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.util.Unbox;

import batch.controller.JobUtil;
import batch.status.Job;
import batch.status.JobDetail;
import batch.status.JobDetailStatus;
import batch.status.JobFileStatus;
import batch.status.JobState;
import batch.status.JobStatus;
import core.config.Factory;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.DateUtil;
import core.util.NumberUtil;

/**
 * バッチステータス確認／更新
 *
 * @author Tadashi Nakayama
 */
public class BatchStatus {

	/** 起動パラメタ（開始確認） */
	public static final String JOB_START = "-start";
	/** 起動パラメタ（終了確認） */
	public static final String JOB_END = "-end";
	/** 起動パラメタ（継続） */
	public static final String JOB_CONT = "-continue";
	/** 起動パラメタ（バッチジョブクリア） */
	public static final String JOB_CLEAR = "-clear";

	/** 削除対象日付（日数）のデフォルト値 */
	private int interval = 6;
	/** ジョブID */
	private String jobId = "";
	/** ジョブ名称 */
	private String jobName = "";
	/** ジョブステータス */
	private int status = 0;
	/** ジョブ連番 */
	private long jobSeq = 0;
	/** ユーザID */
	private String uid = null;
	/** IP */
	private String ip = null;
	/** 処理時間 */
	private final Timestamp dateTime = DateUtil.getDateTime();
	/** 削除件数リスト */
	private final List<Integer> clearedList = new ArrayList<>();

	/**
	 * バッチmain処理
	 *
	 * @param args 起動パラメタ
	 */
	public static void main(final String... args) {
		final var ret = new BatchStatus().perform(args);
		Runtime.getRuntime().exit(ret);
	}

	/**
	 * 削除件数リスト取得
	 *
	 * @return 削除件数リスト
	 */
	public List<Integer> getCleared() {
		return this.clearedList;
	}

	/**
	 * 処理実行
	 *
	 * @param args 引数
	 * @return 処理結果
	 */
	public int perform(final String... args) {
		var ret = Batch.RET_SUCCESS;
		try {
			// 引数チェック
			final var prms = checkArgs(args);

			if (JOB_START.equals(args[0])) {
				// ジョブ管理状態確認
				ret = checkJobStart(prms);
			} else if (JOB_CONT.equals(args[0])) {
				// ジョブ詳細管理状態確認
				ret = checkLastJobDetail(prms);
			} else if (JOB_END.equals(args[0])) {
				// ジョブ詳細管理状態確認
				ret = checkAllJobDetail(prms);
			} else {
				// バッチジョブクリア
				clearJob();
			}

		} catch (final IllegalArgumentException ex) {
			LogManager.getLogger().info(ex.getMessage());
			printUsage(BatchStatus.class.getName());
			ret = Batch.RET_PARAM_ERROR;
		} catch (final Throwable t) {
			ThrowableUtil.error(t);
			ret = Batch.RET_ENV_ERROR;
		}

		return ret;
	}

	/**
	 * 使用方法表示
	 *
	 * @param name クラス名
	 */
	public static void printUsage(final String name) {
		System.err.println(String.format("java %s -clear [n] %n", name));
		System.err.println(String.format("java %s -start -o jobseq %n", name));
		System.err.println(String.format("java %s -start jobid jobname [-u userid] %n", name));
		System.err.println(String.format("java %s -continue -o jobseq %n", name));
		System.err.println(String.format("java %s -end [status message] -o jobseq %n", name));
	}

	/**
	 * パラメタチェック
	 *
	 * @param args パラメタ
	 * @return 引数
	 */
	public String[] checkArgs(final String... args) {
		final var param = new BatchParameterImpl(args);

		final var prms = param.getParameter();
		if (prms.length == 0) {
			throw new IllegalArgumentException();
		}

		if (JOB_CLEAR.equalsIgnoreCase(prms[0])) {
			if (1 < prms.length) {
				// 数値チェック
				final var num = NumberUtil.toInt(prms[1], -1);
				if (num < 0) {
					throw new IllegalArgumentException(prms[1]);
				}
				setInterval(num);
			}
			return prms;
		} else if (JOB_START.equalsIgnoreCase(prms[0])) {
			if (prms.length == 3) {
				setJobId(prms[1]);
				setJobName(prms[2]);
			} else if (prms.length != 1) {
				throw new IllegalArgumentException();
			}
			setUid(param.getUid());

		} else if (JOB_END.equalsIgnoreCase(prms[0])) {
			if (prms.length != 1 && prms.length != 3) {
				throw new IllegalArgumentException();
			}
		}

		// JobSeqチェック
		if (param.getJobSeq() == 0) {
			if (!JOB_START.equalsIgnoreCase(prms[0])) {
				throw new IllegalArgumentException();
			}
		}

		this.jobSeq = param.getJobSeq();

		return prms;
	}

	/**
	 * 開始状態チェック
	 *
	 * @param prms パラメタ
	 * @return バッチ終了ステータス
	 */
	public int checkJobStart(final String... prms) {
		LogManager.getLogger().debug(Unbox.box(prms.length));

		int ret = Batch.RET_SUCCESS;
		try (var conn = JobUtil.getConnection()) {

			if (this.jobSeq != 0L) {
				// ジョブ管理状態取得
				final var job = getJobStatus(conn);
				if (job == null) {
					ret = Batch.RET_ENV_ERROR;
					return ret;
				}

				if (JobState.isCancel(job.getJobSts())) {
					ret = Batch.RET_CANCELED;
				} else if (JobState.ID_B_SH_START.value() != job.getJobSts()) {
					ret = Batch.RET_ENV_ERROR;
				} else {
					// ジョブ管理状態確認・更新
					final var js = Factory.create(JobStatus.class);
					if (js.updateJobStatus(conn, this.jobSeq,
							job.getHostId(), "", JobState.ID_B_EDIT) != 1) {
						ret = Batch.RET_ENV_ERROR;
					}
				}
			} else {
				// ジョブ管理テーブル作成
				final var js = Factory.create(JobStatus.class);
				final var seq = js.insertJob(conn, newJobInstance());
				if (seq <= 0) {
					ret = Batch.RET_ENV_ERROR;
					return ret;
				}
				// ジョブ連番表示
				// バッチまたはシェルが標準入力から取得
				System.out.println(String.valueOf(seq));

				this.jobSeq = seq;
			}

			conn.commit();

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

		return ret;
	}

	/**
	 * ジョブレコード作成
	 *
	 * @return ジョブレコード
	 */
	private Job newJobInstance() {
		final Supplier<String> getHostName = () -> {
			try {
				final var localhost = InetAddress.getLocalHost();
				return localhost.getHostName();
			} catch (final UnknownHostException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
				throw new PhysicalException(ex);
			}
		};

		final var job = new Job();
		job.setJobId(getJobId());
		job.setUid(getUid());
		job.setDateTime(this.dateTime);
		job.setGamenParam("");
		job.setJobName(getJobName());
		job.setIp(getIp());
		job.setHostId(getHostName.get());
		job.setJobSts(JobState.ID_B_EDIT.value());
		job.setRemark("");
		return job;
	}

	/**
	 * ジョブ継続処理
	 *
	 * @param prms パラメタ
	 * @return バッチ終了ステータス
	 */
	public int checkLastJobDetail(final String... prms) {
		LogManager.getLogger().debug(Unbox.box(prms.length));

		var ret = Batch.RET_FAILED;
		try (var conn = JobUtil.getConnection()) {

			// ジョブ管理状態取得
			final var job = getJobStatus(conn);
			if (job == null) {
				ret = Batch.RET_ENV_ERROR;
				return ret;
			}

			// ジョブ詳細管理状態取得
			// 逐次を前提
			final var detail = updateJobDetail(conn, job.getJobSts());
			if (detail == null || detail.isEmpty()) {
				ret = Batch.RET_ENV_ERROR;
				return ret;
			}

			// 最終バッチ状態判定
			final var batSts = detail.get(0).getBatSts();
			if (JobState.isEnd(batSts)) {
				ret = Batch.RET_SUCCESS;
			} else if (JobState.isCancel(batSts)) {
				ret = Batch.RET_CANCELED;
			}

			conn.commit();

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

	/**
	 * ジョブ終了処理
	 *
	 * @param prms パラメタ
	 * @return バッチ終了ステータス
	 */
	public int checkAllJobDetail(final String... prms) {
		int ret = Batch.RET_SUCCESS;

		try (var conn = JobUtil.getConnection()) {

			// ジョブ管理状態取得
			final var job = getJobStatus(conn);
			if (job == null) {
				ret = Batch.RET_ENV_ERROR;
				return ret;
			}

			if (prms != null && 2 < prms.length) {
				job.setJobSts(NumberUtil.toInt(prms[1], 0));
				job.setMsgTxt(prms[2]);
			} else {
				ret = setJobStatus(conn, job);
			}

			// ジョブ管理状態更新
			final var js = Factory.create(JobStatus.class);
			if (js.updateJobStatus(conn, this.jobSeq, job.getHostId(),
					job.getMsgTxt(), JobState.valueOf(job.getJobSts())) != 1) {
				ret = Batch.RET_FAILED;
			}

			conn.commit();

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

		return ret;
	}

	/**
	 * ジョブステータス取得
	 *
	 * @param conn コネクション
	 * @return ジョブステータス
	 */
	private Job getJobStatus(final Connection conn) {
		final var js = Factory.create(JobStatus.class);
		final var job = js.getJobWithLock(conn, this.jobSeq);
		if (job != null) {
			setJobId(job.getJobId());
			setStatus(job.getJobSts());
			setUid(job.getUid());
			this.ip = job.getIp();
		}

		return job;
	}

	/**
	 * ジョブステータス設定
	 *
	 * @param job ジョブ管理オブジェクト
	 * @param conn コネクション
	 * @return 処理結果
	 */
	private int setJobStatus(final Connection conn, final Job job) {

		// ジョブ詳細管理状態取得
		final var detail = updateJobDetail(conn, job.getJobSts());
		if (detail == null || detail.isEmpty()) {
			LogManager.getLogger().info("detail list is empty.");
			job.setMsgTxt("");
			job.setJobSts(JobState.ID_B_INVALID.value());
			return Batch.RET_SUCCESS;
		}

		job.setMsgTxt("");
		job.setJobSts(JobState.ID_B_NEND.value());

		// ジョブ終了状態確定
		var ret = -1;
		for (final var jd : detail) {
			final var batSts = jd.getBatSts();
			if (JobState.isError(batSts)) {
				ret = Batch.RET_FAILED;
				job.setJobSts(JobState.ID_B_INVALID.value());
				break;
			} else if (JobState.isCancel(batSts)) {
				ret = Batch.RET_CANCELED;
				job.setJobSts(JobState.ID_B_CANCEL.value());
			} else if (JobState.ID_B_NODATA.value() == batSts && ret == -1) {
				ret = Batch.RET_NODATA;
				job.setJobSts(JobState.ID_B_NODATA.value());
			} else if (JobState.ID_B_NEND.value() == batSts
							&& (ret == -1 || ret == Batch.RET_NODATA)) {
				ret = Batch.RET_SUCCESS;
				job.setJobSts(JobState.ID_B_NEND.value());
			} else if (JobState.ID_B_WARNING.value() == batSts
							&& (ret == -1 || ret == Batch.RET_NODATA || ret == Batch.RET_SUCCESS)) {
				ret = Batch.RET_WARNING;
				job.setJobSts(JobState.ID_B_WARNING.value());
			}
		}

		return ret;
	}

	/**
	 * ジョブ詳細管理更新
	 *
	 * @param conn コネクション
	 * @param jobsts ジョブステータス
	 * @return ジョブ詳細管理リスト
	 */
	private List<JobDetail> updateJobDetail(final Connection conn, final int jobsts) {
		final var jds = Factory.create(JobDetailStatus.class);
		final var detail = jds.selectJobDetails(conn, this.jobSeq);
		if (detail != null && !detail.isEmpty()) {
			final var dtlno = detail.size();
			final var dtlsts = detail.get(0).getBatSts();

			LogManager.getLogger().debug("JOB_SEQ:{}", Unbox.box(this.jobSeq));
			LogManager.getLogger().debug("JOB_DTL_SEQ:{}", Unbox.box(dtlno));
			LogManager.getLogger().debug("JOB_DTL_STS:{}", Unbox.box(dtlsts));

			// ジョブ詳細管理が処理中のとき
			if (JobState.ID_B_EDIT.value() == dtlsts) {
				final var jd = detail.get(0);
				if (JobState.isCancel(jobsts)) {
					jds.updateJobDetail(conn, this.jobSeq, dtlno,
							null, JobState.ID_B_CANCEL, this.dateTime);
					jd.setBatSts(JobState.ID_B_CANCEL.value());
				} else {
					jds.updateJobDetail(conn, this.jobSeq, dtlno,
							null, JobState.ID_B_INVALID, this.dateTime);
					jd.setBatSts(JobState.ID_B_INVALID.value());
				}
			}
		}
		return detail;
	}

	/**
	 * バッチジョブクリア
	 *
	 */
	public void clearJob() {
		// 削除対象日付を計算
		final var dt = this.dateTime.getTime() - (getInterval() * TimeUnit.DAYS.toMillis(1));

		var jobdel = 0;
		var dtldel = 0;
		var fildel = 0;
		var rmfile = 0;

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

		var committed = false;
		try (var conn = JobUtil.getConnection()) {
			final var list = js.getJobListByTimestamp(conn, null, new Timestamp(dt));
			if (list != null) {
				for (final var job : list) {
					final var seq = job.getJobSeq();
					// 作成ファイルとレコードの削除
					rmfile += jfs.deleteFiles(conn, seq);
					// ファイル管理削除
					fildel += jfs.deleteJobFile(conn, seq);
					// ジョブ管理テーブル削除
					jobdel += js.deleteJob(conn, seq);
					// ジョブ詳細管理削除
					dtldel += jds.deleteJobDetail(conn, seq);

					conn.commit();
					committed = true;
				}
			}

			if (committed) {
				this.clearedList.add(jobdel);
				this.clearedList.add(dtldel);
				this.clearedList.add(fildel);
				this.clearedList.add(rmfile);
			}

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * @return Returns the interval.
	 */
	public int getInterval() {
		return this.interval;
	}

	/**
	 * @param val The interval to set.
	 */
	public void setInterval(final int val) {
		this.interval = val;
	}

	/**
	 * @return Returns the jobId.
	 */
	public String getJobId() {
		return this.jobId;
	}

	/**
	 * @param val The jobId to set.
	 */
	public void setJobId(final String val) {
		this.jobId = val;
	}

	/**
	 * @return Returns the jobName.
	 */
	public String getJobName() {
		return this.jobName;
	}

	/**
	 * @param val The jobName to set.
	 */
	public void setJobName(final String val) {
		this.jobName = val;
	}

	/**
	 * @return Returns the status.
	 */
	public int getStatus() {
		return this.status;
	}

	/**
	 * @param val The status to set.
	 */
	public void setStatus(final int val) {
		this.status = val;
	}

	/**
	 * @return Returns the jobSeq.
	 */
	public long getJobSeq() {
		return this.jobSeq;
	}

	/**
	 * @return Returns the uid.
	 */
	public String getUid() {
		return Objects.toString(this.uid, this.jobId);
	}

	/**
	 * @param val the uid to set
	 */
	public void setUid(final String val) {
		this.uid = val;
	}

	/**
	 * @return Returns the ip.
	 */
	public String getIp() {
		if (this.ip == null) {
			try {
				this.ip = InetAddress.getLocalHost().getHostAddress();
			} catch (final UnknownHostException ex) {
				LogManager.getLogger().warn(ex.getMessage(), ex);
			}
		}
		return this.ip;
	}
}
