package project.batch;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.ThreadContext;

import batch.base.BaseBatch;
import batch.base.BatchParameter;
import core.config.Env;
import core.config.Factory;
import core.util.DateUtil;
import project.common.master.AppConfig;
import project.common.master.Msg;

/**
 * バッチ作成用親
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public abstract class ProjectBatch extends BaseBatch {

	/** 処理件数設定用タグ：DBテーブル更新件数 */
	protected static final String UPDATE = "UPDATE";
	/** 処理件数設定用タグ：DBテーブル登録件数 */
	protected static final String INSERT = "INSERT";
	/** 処理件数設定用タグ：DBテーブル削除件数 */
	protected static final String DELETE = "DELETE";
	/** 処理件数設定用タグ：DBテーブル読込件数 */
	protected static final String SELECT = "SELECT";
	/** 処理件数設定用タグ：ファイル出力件数 */
	protected static final String WRITE = "WRITE";
	/** 処理件数設定用タグ：ファイル読込件数 */
	protected static final String READ = "READ";
	/** 処理件数設定用タグ：ファイル削除件数 */
	protected static final String REMOVE = "REMOVE";

	/** ログ出力区分 */
	private static final String KUBUN_RUN = "BatRun";
	/** ログサフィックス */
	private static final String LOG_SUFFIX = ".log";

	/** 環境種別 */
	private static final String KANKYO_SBT = "BATCH";
	/** 詳細コード 運用日付差分 */
	private static final String DETAIL_DATE = "DIFF_DATE";

	/** 機能ID */
	private String kinoId = null;
	/** バッチパラメタ */
	private ProjectParameter parameter = null;
	/** メッセージ汎用 */
	private final List<String> msgList = new ArrayList<>();

	/**
	 * バインドマップ化
	 * @param prms パラメタ
	 * @return バインドマップ
	 */
	protected Map<String, Object> toBindMap(final Map<String, String[]> prms) {
		final var ret = new HashMap<String, Object>(prms);
		ret.put("Uid", getParameter().getUid());
		ret.put("DateTime", getParameter().getDateTime());
		return ret;
	}

	/**
	 * クエリインスタンス取得
	 * @param <T> ジェネリックス
	 * @param cls クエリクラス
	 * @return クエリインスタンス
	 */
	protected <T> T create(final Class<T> cls) {
		final T jq = Factory.create(cls);

		var m = Factory.getMethod(cls, "setUid", String.class);
		Factory.invoke(jq, m, getParameter().getUid());

		m = Factory.getMethod(cls, "setDateTime", Timestamp.class);
		Factory.invoke(jq, m, getParameter().getDateTime());

		return jq;
	}

	/**
	 * バッチ実行ログヘッダ取得
	 *
	 * @return バッチ実行ログヘッダ
	 */
	protected StringJoiner getLogHeader() {
		final var sb = new StringJoiner(",");
		sb.add(this.parameter.getUid());
		sb.add(this.parameter.getIp());
		sb.add(getKinoID());
		return sb;
	}

	/**
	 * バッチ実行ログヘッダ取得
	 *
	 * @param str 出力文字列
	 */
	protected void writeBatchLog(final String str) {
		final var sb = getLogHeader();
		sb.add(KUBUN_RUN);
		sb.add(str);
		LogManager.getLogger().fatal(sb.toString());
	}

	/**
	 * 運用日付取得
	 *
	 * @return 運用日付(java.sql.Date型)
	 */
	protected java.sql.Date getUnyoDate() {
		Date date = getParameter().getDateTime();

		final var cfg = Factory.create(AppConfig.class);
		final var diff = cfg.getValue(getKinoID(), KANKYO_SBT, DETAIL_DATE, 0);
		if (0 < diff) {
			date = DateUtil.calcDay(date, diff);
		}

		return DateUtil.toDate(date);
	}

	/**
	 * 機能ID取得
	 *
	 * @return 機能ID
	 */
	protected String getKinoID() {
		final Supplier<String> supplier = () -> {
			final String name = this.getClass().getSimpleName();
			return name.endsWith("Batch")
					? name.substring(0, name.length() - "Batch".length()) : name;
		};
		return Objects.requireNonNullElseGet(this.kinoId, supplier);
	}

	/**
	 * 機能ID設定
	 *
	 * @param id 機能ID
	 */
	protected void setKinoID(final String id) {
		this.kinoId = id;
	}

	/**
	 * 処理件数設定を行う。
	 *
	 * @param name 処理対象名 (テーブル名やファイル名を指定)
	 * @param type UPDATE, INSERT, DELETE, SELECT, WRITE, READ のいずれか。
	 * @param count 処理件数
	 */
	protected synchronized void setRecordCount(
			final String name, final String type, final int count) {
		if (name == null || type == null) {
			return;
		}

		final var cnt = String.valueOf(count);
		final var msg = Factory.create(Msg.class);
		if (UPDATE.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_UPD), name, cnt));
		} else if (INSERT.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_INS), name, cnt));
		} else if (DELETE.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_DEL), name, cnt));
		} else if (SELECT.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_SEL), name, cnt));
		} else if (WRITE.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_WRI), name, cnt));
		} else if (READ.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_RED), name, cnt));
		} else if (REMOVE.equals(type)) {
			this.msgList.add(msg.getMessage(Env.getEnv(BatStatus.MSGID_RMV), name, cnt));
		}
	}

	/**
	 * 処理件数印刷処理を行う。
	 *
	 */
	protected void printResult() {
		for (final var msg : this.msgList) {
			LogManager.getLogger().fatal(msg);
		}

		final var list = getOutputFiles();
		if (list != null) {
			for (final var file : list) {
				LogManager.getLogger().fatal(file.left());
			}
		}
	}

	/**
	 * プロジェクトパラメタ取得
	 * @return プロジェクトパラメタ
	 */
	protected ProjectParameter getParameter() {
		return this.parameter;
	}

	/**
	 * プロジェクトパラメタ
	 *
	 * @param prm プロジェクトパラメタ
	 */
	@Override
	public void setBatchParameter(final BatchParameter prm) {
		if (ProjectParameter.class.isInstance(prm)) {
			this.parameter = ProjectParameter.class.cast(prm);
			this.parameter.setJobId(getKinoID());

			final var name = getBatchLogFileName();
			final var path = Env.getEnv("Batch.LogPath", "");

			ThreadContext.put("route", "Audit");
			ThreadContext.put("path", path);
			ThreadContext.put("filename", name);

			addFile(path + name, Env.getEnv("Batch.LogName"));
		}
	}

	/**
	 * バッチロガー情報取得
	 *
	 * @return バッチロガー情報
	 */
	private String getBatchLogFileName() {
		final var dt = DateUtil.format(
				getParameter().getDateTime(), DateUtil.FORMAT_DATE_TIME);
		return getKinoID() + dt + this.parameter.getUid() + LOG_SUFFIX;
	}
}
