package batch.status;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import batch.util.ParameterUtil;
import common.db.jdbc.Jdbc;
import common.sql.QueryUtil;
import common.sql.QueryUtil.StatementCreator;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;

/**
 * ジョブ管理テーブル取得／更新
 *
 * @author Tadashi Nakayama
 */
public class JobStatusImpl implements JobStatus {

	/**
	 * ジョブ連番取得処理
	 *
	 * @param conn コネクション
	 * @return ジョブ連番
	 */
	private long getNextJobSeq(final Connection conn) {
		final var query = QueryUtil.getSqlFromFile("SelectNextVal", this.getClass());
		if (!Objects.toString(query, "").isEmpty()) {
			try (var psmt = Jdbc.wrap(conn).readonlyStatement(query)) {
				var ret = -1L;
				try (var rs = psmt.executeQuery()) {
					if (rs.next()) {
						ret = rs.getLong(1);
					}
				}
				return ret;

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

	/**
	 * ジョブ管理状態取得
	 *
	 * @param conn コネクション
	 * @param hostId ホストID
	 * @param ts タイムスタンプ
	 * @return ジョブ管理テーブル情報
	 */
	@Override
	public List<Job> getJobListByTimestamp(final Connection conn,
			final String hostId, final Timestamp ts) {
		final var map = new HashMap<String, Object>();
		map.put("EntryDateTime", ts);
		map.put("HostName", hostId);

		final var query = QueryUtil.getSqlFromFile("SelectJobStatus", this.getClass());
		try (var psmt = QueryUtil.createStatement(
				query, map, Jdbc.wrap(conn)::readonlyStatement)) {
			final var l = new ArrayList<Job>();
			try (var rs = psmt.executeQuery()) {
				while (rs.next()) {
					l.add(toJobObject(rs));
				}
			}
			return l;

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

	/**
	 * ジョブ管理テーブル項目取得（排他）
	 *
	 * @param conn コネクション
	 * @param seq ジョブ連番
	 * @return ジョブ管理テーブル情報
	 */
	@Override
	public Job getJobWithLock(final Connection conn, final long seq) {

		final var query = QueryUtil.getSqlFromFile("SelectJobStatusById", this.getClass());
		try (var psmt = QueryUtil.createStatement(query, Collections.singletonMap("JobSeq", seq),
				Jdbc.wrap(conn)::readonlyStatement)) {
			Job ret = null;
			try (var rs = psmt.executeQuery()) {
				if (rs.next()) {
					ret = toJobObject(rs);
				}
			}
			return ret;

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

	/**
	 * ジョブ管理状態更新
	 *
	 * @param seq ジョブ連番
	 * @param sts ステータス
	 * @param msg メッセージ
	 * @param conn コネクション
	 * @return 更新レコード数
	 */
	@Override
	public int updateJobStatus(final Connection conn, final long seq,
			final String host, final String msg, final JobState sts) {
		final var map = new HashMap<String, Object>();
		map.put("Status", sts.value());
		map.put("HostName", host);
		map.put("Message", msg);
		map.put("JobSeq", seq);

		final var query = QueryUtil.getSqlFromFile("UpdateJobStatus", this.getClass());
		try (var psmt = QueryUtil.createStatement(
				query, map, Jdbc.wrap(conn)::prepareStatement)) {
			return psmt.executeUpdate();

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

	/**
	 * ジョブ管理状態取得
	 *
	 * @param conn コネクション
	 * @param hostId ホストID
	 * @param sts ステータス
	 * @return ジョブ管理テーブル情報
	 */
	@Override
	public List<Job> getJobListByStatus(final Connection conn,
			final String hostId, final EnumSet<JobState> sts) {
		final var map = new HashMap<String, Object>();
		map.put("Status", sts.stream().map(JobState::value).toArray(Integer[]::new));
		map.put("HostName", hostId);

		final var query = QueryUtil.getSqlFromFile("SelectJobStatusList", this.getClass());
		try (var psmt = QueryUtil.createStatement(
				query, map, Jdbc.wrap(conn)::readonlyStatement)) {
			// バッチ管理テーブル読み込み
			final var l = new ArrayList<Job>();
			try (var rs = psmt.executeQuery()) {
				while (rs.next()) {
					l.add(toJobObject(rs));
				}
			}
			return l;

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

	/**
	 * ジョブ管理レコード作成
	 *
	 * @param conn コネクション
	 * @param job ジョブ管理
	 * @return 採番された番号（1以上）
	 */
	@Override
	public long insertJob(final Connection conn, final Job job) {

		// ジョブ管理レコード作成
		try {
			final var map = toMap(job);
			StatementCreator sc = q -> Jdbc.wrap(conn).
					prepareStatement(q, Statement.RETURN_GENERATED_KEYS);
			final var ret = getNextJobSeq(conn);
			if (0 < ret) {
				map.put("JobSeq", ret);
				sc = Jdbc.wrap(conn)::prepareStatement;
			}

			final var query = QueryUtil.getSqlFromFile("InsertJobStatus", this.getClass());
			try (var psmt = QueryUtil.createStatement(query, map, sc)) {
				if (psmt.executeUpdate() != 1) {
					return -1;
				}

				if (ret <= 0) {
					try (var rs = psmt.getGeneratedKeys()) {
						if (rs.next()) {
							return rs.getInt(1);
						}
					}
				}
			}

			return ret;

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

	/**
	 * レコード作成
	 *
	 * @param job ジョブ管理
	 * @return 作成された場合 true を返す。
	 */
	private Map<String, Object> toMap(final Job job) {
		final var map = new HashMap<String, Object>();
		map.put("JobId", job.getJobId());
		map.put("EntryUserId", job.getUid());
		map.put("EntryDateTime", job.getDateTime());
		map.put("JobParam", ParameterUtil.toParameter(job.getJobParam()));
		map.put("DispParam", job.getGamenParam());
		map.put("JobName", job.getJobName());
		map.put("EntryIp", job.getIp());
		map.put("HostName", job.getHostId());
		map.put("Status", job.getJobSts());
		map.put("Remark", job.getRemark());
		map.put("BatParam", ParameterUtil.toParameter(job.getExecParam()));
		return map;
	}

	/**
	 * ジョブ管理レコード削除
	 *
	 * @param conn コネクション
	 * @param seq ジョブ連番
	 * @return 削除件数
	 */
	@Override
	public int deleteJob(final Connection conn, final long seq) {

		final var query = QueryUtil.getSqlFromFile("DeleteJobStatus", this.getClass());
		try (var psmt = QueryUtil.createStatement(query, Collections.singletonMap("JobSeq", seq),
				Jdbc.wrap(conn)::prepareStatement)) {
			return psmt.executeUpdate();
		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブ管理テーブル項目取得
	 *
	 * @param conn コネクション
	 * @param seq バッチ処理番号
	 * @return 取得項目値
	 */
	@Override
	public Job getJob(final Connection conn, final long seq) {

		final var query = QueryUtil.getSqlFromFile("SelectJobStatusById", this.getClass());
		try (var psmt = QueryUtil.createStatement(query, Collections.singletonMap("JobSeq", seq),
				Jdbc.wrap(conn)::readonlyStatement)) {
			// ジョブ管理テーブル読み込み
			Job ret = null;
			try (var rs = psmt.executeQuery()) {
				if (rs.next()) {
					ret = toJobObject(rs);
				}
			}
			return ret;

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

	/**
	 * ジョブオブジェクト取得
	 *
	 * @param rs 結果セット
	 * @return ジョブオブジェクト
	 * @throws SQLException SQL例外
	 */
	private Job toJobObject(final ResultSet rs) throws SQLException {
		final var ret = new Job();
		ret.setJobSeq(rs.getLong("JOB_SEQ"));
		ret.setJobId(rs.getString("JOB_ID"));
		ret.setJobSts(rs.getInt("JOB_STATUS"));
		ret.setUid(rs.getString("ENTRY_USER_ID"));
		ret.setIp(rs.getString("ENTRY_IP"));
		ret.setRemark(rs.getString("REMMARK"));
		ret.setJobParam(ParameterUtil.toArray(rs.getString("JOB_PARAM")));
		ret.setExecParam(ParameterUtil.toArray(rs.getString("BAT_PARAM")));
		ret.setDateTime(rs.getTimestamp("ENTRY_DATE_TIME"));
		ret.setGamenParam(rs.getString("DISP_PARAM"));
		ret.setJobName(rs.getString("JOB_NAME"));
		ret.setHostId(rs.getString("EXEC_HOST"));
		ret.setMsgTxt(rs.getString("MESSAGE"));
		return ret;
	}
}
