package batch.status;

import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

import org.apache.logging.log4j.LogManager;

import common.db.jdbc.Jdbc;
import common.sql.QueryUtil;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.bean.Pair;

/**
 * ジョブファイル管理テーブル取得／更新
 *
 * @author Tadashi Nakayama
 */
public class JobFileStatusImpl implements JobFileStatus {

	/**
	 * ファイル管理レコード取得
	 *
	 * @param jseq ジョブ連番
	 * @param dseq ジョブ詳細連番
	 * @param fseq ファイル連番
	 * @param conn コネクション
	 * @param query クエリ
	 * @return レコード情報
	 */
	private JobFile getJobFile(final long jseq, final int dseq,
			final int fseq, final Connection conn, final String query) {
		final var map = new HashMap<String, Object>();
		map.put("JobSeq", jseq);
		map.put("BatSeq", dseq);
		map.put("FileSeq", fseq);

		try (var psmt = QueryUtil.createStatement(
				query, map, Jdbc.wrap(conn)::readonlyStatement)) {
			// ジョブ管理テーブル読み込み
			JobFile ret = null;
			try (var rs = psmt.executeQuery()) {
				if (rs.next()) {
					ret = toJobFileObject(rs);
				}
			}
			return ret;

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

	/**
	 * ファイル管理レコードリスト取得
	 *
	 * @param conn コネクション
	 * @param seq ジョブ連番
	 * @return レコードリスト
	 */
	@Override
	public List<JobFile> selectJobFiles(final Connection conn, final long seq) {

		final var query = QueryUtil.getSqlFromFile("SelectJobFileList", this.getClass());
		try (var psmt = QueryUtil.createStatement(query, Collections.singletonMap("JobSeq", seq),
				Jdbc.wrap(conn)::readonlyStatement)) {
			final var ret = new ArrayList<JobFile>();
			try (var rs = psmt.executeQuery()) {
				while (rs.next()) {
					ret.add(toJobFileObject(rs));
				}
			}
			return ret;

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

	/**
	 * ファイル管理レコード更新
	 *
	 * @param conn コネクション
	 * @param jseq ジョブ連番
	 * @param dseq ジョブ詳細連番
	 * @param fseq ファイル連番
	 * @param count ダウンロード回数
	 * @param size ファイルサイズ
	 * @return 更新件数
	 */
	@Override
	public int updateJobFile(final Connection conn, final long jseq, final int dseq,
			final int fseq, final int count, final long size) {
		final var map = new HashMap<String, Object>();
		map.put("JobSeq", jseq);
		map.put("BatSeq", dseq);
		map.put("FileSeq", fseq);
		map.put("Count", count);
		map.put("FileSize", size);

		final var query = QueryUtil.getSqlFromFile("UpdateJobFile", 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 seq ジョブ連番
	 * @return 削除件数
	 */
	@Override
	public int deleteJobFile(final Connection conn, final long seq) {
		final var query = QueryUtil.getSqlFromFile("DeleteJobFile", 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 jfile ジョブファイル
	 * @return 作成件数
	 */
	@Override
	public int insertJobFile(final Connection conn, final JobFile jfile) {
		final var map = new HashMap<String, Object>();
		map.put("JobSeq", jfile.getJobSeq());
		map.put("BatSeq", jfile.getBatSeq());
		map.put("FileSeq", jfile.getFileSeq());
		map.put("FileName", jfile.getFileName());
		map.put("FilePath", jfile.getPathName());
		map.put("FileSize", jfile.getFileSize());
		map.put("DownloadName", jfile.getDownloadName());
		map.put("Count", 0);

		final var query = QueryUtil.getSqlFromFile("InsertJobFile", 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 seq ジョブ連番
	 * @param dtlno バッチ連番
	 * @param list 作成ファイルリスト
	 * @return 作成レコード数
	 */
	@Override
	public int setFiles(final Connection conn, final long seq,
			final int dtlno, final List<Pair<String, String>> list) {

		var ret = 0;
		if (list != null) {
			var fseq = 1;
			for (final var strs : list) {
				// フルパスファイル名取得
				final var fullpath = strs.left();
				if (fullpath == null) {
					continue;
				}

				final var file = new File(fullpath);

				final int cnt;
				// ファイル管理テーブル更新
				var info = getJobFileWithLock(conn, seq, dtlno, fseq);
				if (info == null) {
					info = new JobFile();
					info.setJobSeq(seq);
					info.setBatSeq(dtlno);
					info.setFileSeq(fseq);
					info.setFileName(file.getName());
					info.setPathName(new File(file.getParent()).toURI().getPath());
					info.setFileSize(file.length());
					info.setDownloadName(strs.right());
					cnt = insertJobFile(conn, info);
				} else {
					cnt = updateJobFile(conn, seq, dtlno, fseq,
								info.getDownloadCount(), file.length());
				}

				if (cnt != 1) {
					throw new IllegalStateException(String.valueOf(cnt));
				}

				fseq++;
				ret += cnt;
			}
		}
		return ret;
	}

	/**
	 * ファイル削除
	 *
	 * @param conn コネクション
	 * @param seq ジョブ連番
	 * @return 処理結果数
	 */
	@Override
	public int deleteFiles(final Connection conn, final long seq) {
		var removed = 0;
		final var files = selectJobFiles(conn, seq);
		if (!files.isEmpty()) {
			for (final var file : files) {
				if (removeFile(file.getFileName(), file.getPathName())) {
					removed++;
				}
			}
		}
		return removed;
	}

	/**
	 * ファイル削除処理
	 *
	 * @param fname ファイル名
	 * @param path パス名
	 * @return 正常に削除された場合 true
	 */
	private boolean removeFile(final String fname, final String path) {
		if (Objects.toString(fname, "").trim().isEmpty()) {
			return false;
		}

		final var dir = Objects.toString(path, "");
		// 作成ファイルの削除
		LogManager.getLogger().warn("remove path={} file={}", dir, fname);
		final var file = new File(dir, fname);
		return file.delete();
	}

	/**
	 * ファイル管理テーブルのダウンロード回数を更新
	 *
	 * @param conn コネクション
	 * @param jseq ジョブ連番
	 * @param dseq ジョブ詳細連番
	 * @param fseq ファイル連番
	 * @return 取得項目値
	 */
	@Override
	public JobFile countUpDownload(final Connection conn, final long jseq,
			final int dseq, final int fseq) {
		// ファイル管理情報取得
		final var ret = getJobFileWithLock(conn, jseq, dseq, fseq);
		if (ret != null) {
			final var count = ret.getDownloadCount();
			final var size = ret.getFileSize();
			final var cnt = updateJobFile(conn, jseq, dseq, fseq, count + 1, size);
			if (cnt != 1) {
				throw new IllegalStateException(String.valueOf(cnt));
			}
		}
		return ret;
	}

	/**
	 * ファイル管理レコード取得
	 *
	 * @param conn コネクション
	 * @param jseq ジョブ連番
	 * @param dseq ジョブ詳細連番
	 * @param fseq ファイル連番
	 * @return レコード情報
	 */
	@Override
	public JobFile getJobFileWithLock(final Connection conn, final long jseq,
			final int dseq, final int fseq) {
		final var query = QueryUtil.getSqlFromFile("SelectJobFileWithLock", this.getClass());
		return getJobFile(jseq, dseq, fseq, conn, query);
	}

	/**
	 * ファイル管理レコード取得
	 *
	 * @param conn コネクション
	 * @param jseq ジョブ連番
	 * @param dseq ジョブ詳細連番
	 * @param fseq ファイル連番
	 * @return レコード情報
	 */
	@Override
	public JobFile getJobFile(final Connection conn, final long jseq,
			final int dseq, final int fseq) {
		final var query = QueryUtil.getSqlFromFile("SelectJobFile", this.getClass());
		return getJobFile(jseq, dseq, fseq, conn, query);
	}

	/**
	 * ジョブファイル管理オブジェクト取得
	 *
	 * @param rs 結果セット
	 * @return ジョブファイル管理
	 * @throws SQLException SQL例外
	 */
	private JobFile toJobFileObject(final ResultSet rs) throws SQLException {
		final var ret = new JobFile();
		ret.setJobSeq(rs.getLong("JOB_SEQ"));
		ret.setBatSeq(rs.getInt("BAT_SEQ"));
		ret.setFileSeq(rs.getInt("FILE_SEQ"));
		ret.setFileName(rs.getString("FILE_NAME"));
		ret.setPathName(rs.getString("FILE_PATH"));
		ret.setFileSize(rs.getLong("FILE_SIZE"));
		ret.setDownloadName(rs.getString("DOWNLOAD_NAME"));
		ret.setDownloadCount(rs.getInt("DOWNLOAD_COUNT"));
		return ret;
	}
}
