package com.style24.batch.biz.job;

import java.sql.Timestamp;
import java.util.Collection;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import com.style24.batch.biz.service.TsbBatchService;
import com.style24.batch.biz.service.TsbMonitoringService;
import com.style24.persistence.domain.BatchLog;

import lombok.extern.slf4j.Slf4j;

/**
 * Batch Abastract Class
 *
 * @author gagamel
 * @since 2020. 11. 13
 */
@Slf4j
public abstract class TsbAbstractJob<I, O, R> {

	private long startTime = 0L;
	private long endTime = 0L;

	private String batchId;
	private String batchName;

	private I readItem = null;
	private O convertedItem = null;
	private R resultItem = null;

	public int totalCount = 0;

	@Autowired
	private TsbBatchService batchService;

	private Integer batchLogSq;	// 배치로그일련번호

	@Autowired
	private TsbMonitoringService monitoringService;

	/**
	 * Batch Job 시작 로그를 출력한다.
	 */
	protected void printStart() {
		startTime = System.currentTimeMillis();

		log.info("################################################################################");
		log.info("{} Start!!!\n", batchName);
	}

	/**
	 * Batch Job 결과 건수 로그를 출력한다.
	 */
	@SuppressWarnings("rawtypes")
	protected void printResult(int successCount, int failureCount) {
		if (readItem instanceof Collection) {
			totalCount = ((Collection)readItem).size();

			log.info("--------------------------------------------------------------------------------");
			log.info("배치: {}, 대상: {}건 중 성공: {}건, 실패: {}건", batchName, totalCount, successCount, failureCount);
			log.info("--------------------------------------------------------------------------------");
		}
	}

	/**
	 * Batch Job 결과 건수 로그를 출력한다.
	 */
	protected void printResult(int totalCount, int successCount, int failureCount) {
		log.info("--------------------------------------------------------------------------------");
		log.info("배치: {}, 대상: {}건 중 성공: {}건, 실패: {}건", batchName, totalCount, successCount, failureCount);
		log.info("--------------------------------------------------------------------------------");
	}

	/**
	 * Batch Job 종료 로그를 출력한다.
	 */
	protected void printEnd() {
		if (StringUtils.isNotBlank(batchId)) {
			// 배치로그 종료 처리
			batchService.updateBatchLog(batchLogSq, batchId);
		}

		endTime = System.currentTimeMillis();

		log.info("Start Time: {}", new Timestamp(startTime));
		log.info("End Time: {}", new Timestamp(endTime));
		log.info("Processing Time: {} seconds\n", ((endTime - startTime) / 1000.0));
		log.info("{} End!!!", batchName);
		log.info("################################################################################\n\n");
	}

	/**
	 * 배치ID로 배치를 실행한다.
	 * @param batchId - 배치ID
	 * @throws Exception
	 */
	public void runById(String batchId) throws Exception {
		this.batchId = batchId;

		if (StringUtils.isNotBlank(batchId)) {
			// 배치명 조회
			BatchLog batchLog = batchService.getBatchInfo(batchId);

			if (batchLog == null) {
				log.info("{} 배치는 미사용으로 종료합니다.(TB_BATCH 테이블 참조)", batchId);
				return;
			}

			if (batchLog.getBatchStat().equals("I")) {
				log.info("{} 배치는 현재 실행중입니다.(TB_BATCH 테이블 참조)", batchId);
				return;
			}

			batchName = batchLog.getBatchNm();

			// 배치로그 생성
			batchLogSq = batchService.createBatchLog(batchId);
		}

		try {
			this.printStart();

			readItem = this.read();

			if (readItem != null) {
				convertedItem = this.process(readItem);

				resultItem = this.write(convertedItem);

				this.notify(resultItem);
			} else { // 배치 처리할 대상 데이터가 없으면 종료
				log.info("처리할 데이터가 없습니다.\n");
			}

			this.printEnd();

		} catch (Exception e) {
			log.error(e.getMessage());
			if (StringUtils.isNotBlank(batchId)) {
				// 배치로그 종료 처리
				batchService.updateBatchLog(batchLogSq, batchId, e.getMessage());
			}

			try {
				// 실패 시 알림(SMS) 보내기
				monitoringService.createSystemAlarmSms("A000", batchName);
			} catch (Exception e1) {
				// Do nothing
			}
		}
	}

	/**
	 * 배치명으로 배치를 실행한다.
	 * @param batchId - 배치ID
	 * @throws Exception
	 */
	public void run(String batchName) throws Exception {
		this.batchName = batchName;

		this.printStart();

		readItem = this.read();

		if (readItem != null) {
			convertedItem = this.process(readItem);

			resultItem = this.write(convertedItem);

			this.notify(resultItem);
		} else { // 배치 처리할 대상 데이터가 없으면 종료
			log.info("처리할 데이터가 없습니다.\n");
		}

		this.printEnd();
	}

//	/**
//	 * 데이터 로그를 출력한다.
//	 * @param dataMap - 데이터
//	 */
//	protected void printRowValue(GagaMap dataMap) {
//		Iterator<Entry<Object, Object>> itr = dataMap.entrySet().iterator();
//
//		StringBuilder sb = new StringBuilder();
//		while (itr.hasNext()) {
//			Entry<Object, Object> entry = itr.next();
//			sb.append(" | ").append(entry.getValue());
//		}
//
//		String value = "";
//		if (StringUtils.hasLength(sb.toString())) {
//			value += "[" + sb.toString().substring(2) + "]";
//			logger.info(value);
//		}
//	}

	/**
	 * 배치 처리할 항목을 조회한다.
	 * @return 배치 처리를 위해 조회한 항목
	 * @throws Exception
	 */
	public abstract I read() throws Exception;

	/**
	 * 조회한 항목을 실제 데이터 처리를 위해 변환한다. 변환할 데이터가 없으면 아무 처리 하지 않아도 된다.
	 * @param readItem - 조회한 항목
	 * @return 변환된 항목
	 * @throws Exception
	 */
	public abstract O process(I readItem) throws Exception;

	/**
	 * 변환된 항목으로 배치를 처리하고, 그 결과를 조회한다.
	 * @param convertedItem - 변환된 항목
	 * @return 처리결과 항목
	 * @throws Exception
	 */
	public abstract R write(O convertedItem) throws Exception;

	/**
	 * 배치처리결과를 노티한다.
	 * @param resultItem - 배치처리결과 항목
	 * @throws Exception
	 */
	public abstract void notify(R resultItem) throws Exception;

}
