package project.common.master;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import common.db.JdbcSource;
import common.db.jdbc.Jdbc;
import common.master.IntervalTimer.IntervalCache;
import common.sql.QueryUtil;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.NumberUtil;

/**
 * アプリケーション環境保持
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class AppConfigImpl implements AppConfig, IntervalCache {

	/** 自身保持用 */
	private static final AtomicReference<AppConfigImpl> INSTANCE = new AtomicReference<>();

	/** 格納用ハッシュ */
	private final ConcurrentMap<String, List<String>> config = new ConcurrentHashMap<>();

	static {
		INSTANCE.set(new AppConfigImpl());
	}

	/**
	 * コンストラクタ
	 */
	private AppConfigImpl() {
		if (INSTANCE.get() != null) {
			throw new AssertionError();
		}
	}

	/**
	 * キー値取得
	 *
	 * @param vals キー構成文字
	 * @return キー値
	 */
	private String getValueKey(final String... vals) {
		return Stream.of(vals).collect(Collectors.joining("-", "", "-VALUE"));
	}

	/**
	 * キー値取得
	 *
	 * @param vals キー構成文字
	 * @return キー値
	 */
	private String getDetailKey(final String... vals) {
		return Stream.of(vals).collect(Collectors.joining("-", "", "-DETAIL"));
	}

	/**
	 * インスタンス取得
	 *
	 * @return インスタンス
	 */
	public static AppConfigImpl getInstance() {
		return INSTANCE.get();
	}

	/**
	 * 選択項目取得
	 *
	 * @param kinoId 機能ID
	 * @param kankyoSbt 環境種別
	 * @param detailSbt 詳細種別
	 * @return 環境設定値
	 */
	@Override
	public String getValue(final String kinoId,
			final String kankyoSbt, final String detailSbt) {
		final var detail = this.config.get(getDetailKey(kinoId, kankyoSbt));
		if (detail != null) {
			final var loc = detail.indexOf(detailSbt);
			if (0 <= loc) {
				final var value = this.config.get(getValueKey(kinoId, kankyoSbt));
				return value.get(loc);
			}
		}
		return "";
	}

	/**
	 * 選択項目取得
	 *
	 * @param kinoId 機能ID
	 * @param kankyoSbt 環境種別
	 * @param detailSbt 詳細種別
	 * @param def デフォルト
	 * @return 環境設定値
	 */
	@Override
	public String getValue(final String kinoId, final String kankyoSbt,
			final String detailSbt, final String def) {
		final var ret = getValue(kinoId, kankyoSbt, detailSbt);
		if (Objects.toString(ret, "").isEmpty()) {
			return def;
		}
		return ret;
	}

	/**
	 * 選択項目取得
	 *
	 * @param kinoId 機能ID
	 * @param kankyoSbt 環境種別
	 * @param detailSbt 詳細種別
	 * @param def デフォルト
	 * @return 環境設定値
	 */
	@Override
	public int getValue(final String kinoId, final String kankyoSbt,
			final String detailSbt, final int def) {
		final var ret = getValue(kinoId, kankyoSbt, detailSbt);
		if (Objects.toString(ret, "").trim().isEmpty()) {
			return def;
		}
		return NumberUtil.toInt(ret, def);
	}

	/**
	 * 存在確認
	 *
	 * @param kinoId 機能ID
	 * @param kankyoSbt 環境種別
	 * @param detailSbt 詳細種別
	 * @return 存在した場合 true を返す。
	 */
	@Override
	public boolean hasValue(final String kinoId,
			final String kankyoSbt, final String detailSbt) {
		final var list = this.config.get(getDetailKey(kinoId, kankyoSbt));
		return list != null && list.contains(detailSbt);
	}

	/**
	 * 選択環境項目取得
	 * 機能ＩＤと環境種別を条件に、該当するレコードの内容をリストにして返却する。
	 *
	 * @param kinoId 機能ID
	 * @param kankyoSbt 環境種別
	 * @return 設定値一覧リスト
	 */
	@Override
	public List<String> getValueList(final String kinoId, final String kankyoSbt) {
		return this.config.getOrDefault(
				getValueKey(kinoId, kankyoSbt), Collections.emptyList());
	}

	/**
	 * 選択環境項目取得
	 * 機能ＩＤと環境種別を条件に、該当するレコードのキー値をリストにして返却する。
	 *
	 * @param kinoId 機能ID
	 * @param kankyoSbt 環境種別
	 * @return 設定値一覧リスト
	 */
	@Override
	public List<String> getDetailList(final String kinoId, final String kankyoSbt) {
		return this.config.getOrDefault(
				getDetailKey(kinoId, kankyoSbt), Collections.emptyList());
	}

	/**
	 * 初期処理を行う。
	 */
	@Override
	public void initialize() {
		init(null);
	}

	/**
	 * 初期処理を行う。主にバッチ用。
	 *
	 * @param id 機能ID
	 */
	public synchronized void init(final String id) {
		// クリア
		this.config.clear();

		try (var conn = JdbcSource.getConnection()) {
			final var query = QueryUtil.getSqlFromFile(this.getClass().getName());
			try (var psmt = QueryUtil.createStatement(
					query, Collections.singletonMap("FunctionId", id),
					Jdbc.wrap(conn)::readonlyStatement)) {
				String kid = null;
				String kst = null;
				var value = new ArrayList<String>();
				var detail = new ArrayList<String>();
				try (var rs = psmt.executeQuery()) {
					while (rs.next()) {
						final var kinoId = rs.getString("FUNCTION_ID");
						final var kankyoSbt = rs.getString("ENVIRONMENT_SBT");
						if (kid != null && (!kinoId.equals(kid) || !kankyoSbt.equals(kst))) {
							this.config.put(getValueKey(kid, kst), value);
							this.config.put(getDetailKey(kid, kst), detail);
							value = new ArrayList<>();
							detail = new ArrayList<>();
						}
						kid = kinoId;
						kst = kankyoSbt;
						detail.add(rs.getString("KB_DETAIL_SBT"));
						value.add(rs.getString("CONTENTS"));
					}
				}

				if (!value.isEmpty()) {
					this.config.put(getValueKey(kid, kst), value);
					this.config.put(getDetailKey(kid, kst), detail);
				}
			}
		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}
}
