package com.eactive.eai.rms.onl.audit.adapter;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.query.criteria.MatchMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.eactive.eai.data.entity.onl.adapter.Adapter;
import com.eactive.eai.data.entity.onl.adapter.AdapterGroup;
import com.eactive.eai.data.entity.onl.adapter.AdapterProp;
import com.eactive.eai.data.entity.onl.adapter.AdapterPropGroup;
import com.eactive.eai.rms.data.entity.onl.audit.CustomRevisionEntity;
import com.eactive.eai.rms.data.entity.onl.audit.CustomRevisionEntityRepository;
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService;
import com.eactive.eai.rms.onl.audit.adapter.map.AdapterAuditUIMapperWithoutChild;
import com.eactive.eai.rms.onl.audit.adapter.map.AdapterGroupAuditUIMapper;
import com.eactive.eai.rms.onl.audit.adapter.map.AdapterGroupAuditUIMapperWithoutChild;
import com.eactive.eai.rms.onl.audit.adapter.map.AdapterPropAuditUIMapper;
import com.eactive.eai.rms.onl.audit.adapter.map.AdapterPropGroupAuditUIMapperWithoutChild;
import com.eactive.eai.rms.onl.audit.adapter.map.AuditUIMapper;
import com.eactive.eai.rms.onl.audit.adapter.ui.AdapterAuditUI;
import com.eactive.eai.rms.onl.audit.adapter.ui.AdapterGroupAuditUI;
import com.eactive.eai.rms.onl.audit.adapter.ui.AdapterGroupAuditUIChanges;
import com.eactive.eai.rms.onl.audit.adapter.ui.AdapterPropAuditUI;
import com.eactive.eai.rms.onl.audit.adapter.ui.AdapterPropGroupAuditUI;
import com.eactive.eai.rms.onl.common.exception.BizException;
import com.eactive.eai.rms.onl.manage.adapter.adapter.ui.AdapterGroupUISearch;

@Service
@Transactional
public class AdapterAuditService {

	@PersistenceContext
	private EntityManager entityManager;

	@Autowired
	private AuditUIMapper auditUIMapper;

	@Autowired
	private AdapterGroupAuditUIMapper adapterGroupAuditUIMapper;

	@Autowired
	private AdapterGroupAuditUIMapperWithoutChild adapterGroupAuditUIMapperWithoutChild;

	@Autowired
	private AdapterAuditUIMapperWithoutChild adapterAuditUIMapperWithoutChild;

	@Autowired
	private AdapterPropGroupAuditUIMapperWithoutChild adapterPropGroupAuditUIMapperWithoutChild;

	@Autowired
	private AdapterPropAuditUIMapper adapterPropAuditUIMapper;

	@Autowired
	private CustomRevisionEntityRepository customRevisionEntityRepository;

	@Autowired
	private UserBusinessService userBusinessService;

	/**
	 * 해당 revision의 AdapterGroup 정보 전체 정보(snapshot)를 조회한다. 전체 정보를 조회하기 위해서는 최초 어댑터
	 * 생성부터 Audit 관리되어야 가능하다.
	 * 
	 * @param rev
	 * @return
	 */
	public AdapterGroupAuditUI getAuditSnapshotAtRevision(long rev, String adptrbzwkgroupname) {
		AuditReader reader = AuditReaderFactory.get(entityManager);

		Optional<CustomRevisionEntity> optionalRevsionInfo = customRevisionEntityRepository.findById(rev);
		CustomRevisionEntity revisionInfo = optionalRevsionInfo.isPresent() ? optionalRevsionInfo.get() : null;

		AdapterGroup adapterGroup = (AdapterGroup) reader.createQuery().forEntitiesAtRevision(AdapterGroup.class, rev)
				.add(AuditEntity.id().eq(adptrbzwkgroupname)).getSingleResult();

		AdapterGroupAuditUI adapterGroupUI = adapterGroupAuditUIMapper.toVo(adapterGroup);
		this.auditUIMapper.map(revisionInfo, adapterGroupUI);

		return adapterGroupUI;
	}
	
	public AdapterGroupAuditUI getPrevAuditSnapshotAtRevision(long rev, String adptrbzwkgroupname) {
		return this.getNearAuditSnapshotAtRevision(rev, adptrbzwkgroupname, true);
	}
	
	public AdapterGroupAuditUI getNextAuditSnapshotAtRevision(long rev, String adptrbzwkgroupname) {
		return this.getNearAuditSnapshotAtRevision(rev, adptrbzwkgroupname, false);
	}

	private AdapterGroupAuditUI getNearAuditSnapshotAtRevision(long rev, String adptrbzwkgroupname,
			boolean isPrevious) {
		AuditReader auditReader = AuditReaderFactory.get(entityManager);

		AuditQuery auditQuery = auditReader.createQuery().forRevisionsOfEntity(AdapterGroup.class, false, true)
				.add(AuditEntity.id().eq(adptrbzwkgroupname));
		
		if (isPrevious) {
			auditQuery.add(AuditEntity.revisionNumber().lt(rev)).addOrder(AuditEntity.revisionNumber().desc());
		} else {
			auditQuery.add(AuditEntity.revisionNumber().gt(rev)).addOrder(AuditEntity.revisionNumber().asc());
		}
		
		List<?> revisions = auditQuery.getResultList();

		if (revisions == null || revisions.isEmpty())
			return null;

		List<AdapterGroupAuditUI> adapterGroupUIs = new ArrayList<>();
		this.addAdapterGroupAuditUIFromAuditRaw((Object[]) revisions.get(0), adapterGroupUIs);
		
		if(adapterGroupUIs.get(0).getRevType().equals("DEL")) {
			throw new BizException("삭제된 어댑터(revNo=" + adapterGroupUIs.get(0).getRevNo() + ", revUserId="
					+ adapterGroupUIs.get(0).getRevUserId() + ", revTstmp="
					+ adapterGroupUIs.get(0).getRevTstmp() + ")");
		}
		
		return getAuditSnapshotAtRevision(adapterGroupUIs.get(0).getRevNo(), adptrbzwkgroupname);

	}

	/**
	 * 해당 리비전의 변경분만 가져온다.
	 * 
	 * @param rev 리비젼번호
	 * @param adptrbzwkgroupname 어댑터그룹이름
	 * @return
	 */
	public AdapterGroupAuditUIChanges getAuditAtRevision(long rev, String adptrbzwkgroupname) {
		AdapterGroupAuditUIChanges result = new AdapterGroupAuditUIChanges();
		AuditReader reader = AuditReaderFactory.get(entityManager);

		// 1. adapter group
		// 1-1. 각 List의 아이템은 크기3의 오브젝트 배열 형태이다. Entity, CustomRevisionEntity,
		// RevisionType 이다.
		List<?> adapterGroupRaws = reader.createQuery().forRevisionsOfEntity(AdapterGroup.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).add(AuditEntity.id().eq(adptrbzwkgroupname)).getResultList();
		List<AdapterGroupAuditUI> adapterGroupUIs = new ArrayList<>();
		adapterGroupRaws.forEach(raws -> this.addAdapterGroupFromAuditRaw((Object[]) raws, adapterGroupUIs));
		result.setAdapterGroups(adapterGroupUIs);

		// 2. adapter
		List<?> adapterRaws = reader.createQuery().forRevisionsOfEntity(Adapter.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).getResultList();
		List<AdapterAuditUI> adapterUIs = new ArrayList<>();
		adapterRaws.forEach(raws -> this.addAdapterFromAuditRaw((Object[]) raws, adapterUIs));
		result.setAdapters(adapterUIs);

		// 3. adapter property group
		List<?> adapterPropGroupRaws = reader.createQuery().forRevisionsOfEntity(AdapterPropGroup.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).getResultList();
		List<AdapterPropGroupAuditUI> adapterPropGroupUIs = new ArrayList<>();
		adapterPropGroupRaws
				.forEach(raws -> this.addAdapterPropGroupFromAuditRaw((Object[]) raws, adapterPropGroupUIs));
		result.setAdapterPropGroups(adapterPropGroupUIs);

		// 4. adapter property
		List<?> adapterPropRaws = reader.createQuery().forRevisionsOfEntity(AdapterProp.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).getResultList();
		List<AdapterPropAuditUI> adapterPropUIs = new ArrayList<>();
		adapterPropRaws.forEach(raws -> this.addAdapterPropFromAuditRaw((Object[]) raws, adapterPropUIs));
		result.setAdapterProps(adapterPropUIs);

		return result;

	}

	/**
	 * 해당 리비전의 변경분(리비젼 정보 포함) 가져온다.
	 * 
	 * @param rev 리비젼번호
	 * @param adptrbzwkgroupname 어댑터그룹이름
	 * @return
	 */
	public AdapterGroupAuditUIChanges getAuditAtRevisionWithRevisionInfo(long rev, String adptrbzwkgroupname) {
		AdapterGroupAuditUIChanges result = new AdapterGroupAuditUIChanges();
		AuditReader reader = AuditReaderFactory.get(entityManager);

		// 1. adapter group
		// 1-1. 각 List의 아이템은 크기3의 오브젝트 배열 형태이다. Entity, CustomRevisionEntity,
		// RevisionType 이다.
		List<?> adapterGroupRaws = reader.createQuery().forRevisionsOfEntity(AdapterGroup.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).add(AuditEntity.id().eq(adptrbzwkgroupname)).getResultList();
		List<AdapterGroupAuditUI> adapterGroupUIs = new ArrayList<>();
		adapterGroupRaws.forEach(raws -> this.addAdapterGroupAuditUIFromAuditRaw((Object[]) raws, adapterGroupUIs));
		result.setAdapterGroups(adapterGroupUIs);

		// 2. adapter
		List<?> adapterRaws = reader.createQuery().forRevisionsOfEntity(Adapter.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).getResultList();
		List<AdapterAuditUI> adapterUIs = new ArrayList<>();
		adapterRaws.forEach(raws -> this.addAdapterAuditUIFromAuditRaw((Object[]) raws, adapterUIs));
		result.setAdapters(adapterUIs);

		// 3. adapter property group
		List<?> adapterPropGroupRaws = reader.createQuery().forRevisionsOfEntity(AdapterPropGroup.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).getResultList();
		List<AdapterPropGroupAuditUI> adapterPropGroupUIs = new ArrayList<>();
		adapterPropGroupRaws
				.forEach(raws -> this.addAdapterPropGroupAuditUIFromAuditRaw((Object[]) raws, adapterPropGroupUIs));
		result.setAdapterPropGroups(adapterPropGroupUIs);

		// 4. adapter property
		List<?> adapterPropRaws = reader.createQuery().forRevisionsOfEntity(AdapterProp.class, false, true)
				.add(AuditEntity.revisionNumber().eq(rev)).getResultList();
		List<AdapterPropAuditUI> adapterPropUIs = new ArrayList<>();
		adapterPropRaws.forEach(raws -> this.addAdapterPropAuditUIFromAuditRaw((Object[]) raws, adapterPropUIs));
		result.setAdapterProps(adapterPropUIs);

		return result;

	}

	/**
	 * 리비젼정보 리스트 조회 - 페이징 처리
	 * 
	 * @param pageable 페이징정보
	 * @param adapterGroupUISearch 검색조건
	 * @return
	 */
	public Page<AdapterGroupAuditUI> getAuditHistoryPaged(Pageable pageable,
			AdapterGroupUISearch adapterGroupUISearch) {
		AuditReader auditReader = AuditReaderFactory.get(entityManager);
		
		List<String> userBusiness = userBusinessService.getCurrentUserBusinessCodes();
		
		int totalResults = this.getTotalCount(adapterGroupUISearch, userBusiness);
		
		AuditQuery queryForList = createAuditQuery(auditReader, adapterGroupUISearch, userBusiness, false);
		// Set paging parameters
		queryForList.setFirstResult((int) pageable.getOffset());
		queryForList.setMaxResults(pageable.getPageSize());
		List<?> adapterGroupList = queryForList.getResultList();
		
		List<AdapterGroupAuditUI> list = new ArrayList<>();
		adapterGroupList.forEach(raws -> this.addAdapterGroupAuditUIFromAuditRaw((Object[]) raws, list));

		return new PageImpl<>(list, pageable, totalResults);
	}
	
	private int getTotalCount(AdapterGroupUISearch adapterGroupUISearch, List<String> userBusiness) {
		
		AuditQuery totalCountQuery = AuditReaderFactory
			.get( entityManager )
			.createQuery()
			.forRevisionsOfEntity( AdapterGroup.class, false, true )
			.addProjection( AuditEntity.revisionNumber().count() )
			.add(AuditEntity.property("eaibzwkdstcd").in(userBusiness));
		
		if (!StringUtils.isEmpty(adapterGroupUISearch.getSearchAdptrBzwkGroupName())) {
			totalCountQuery = totalCountQuery
					.add(AuditEntity.id().like(adapterGroupUISearch.getSearchAdptrBzwkGroupName(), MatchMode.ANYWHERE));
		}
		if (!StringUtils.isEmpty(adapterGroupUISearch.getSearchAdptrBzwkGroupDesc())) {
			totalCountQuery = totalCountQuery.add(AuditEntity.property("adptrbzwkgroupdesc")
					.like(adapterGroupUISearch.getSearchAdptrBzwkGroupDesc(), MatchMode.ANYWHERE));
		}
		if (!StringUtils.isEmpty(adapterGroupUISearch.getSearchEaiBzwkDstcd())) {
			totalCountQuery = totalCountQuery
					.add(AuditEntity.property("eaibzwkdstcd").eq(adapterGroupUISearch.getSearchEaiBzwkDstcd()));
		}

		Number count = (Number) totalCountQuery.getSingleResult();

		return count.intValue();
	}
	
	private AuditQuery createAuditQuery(AuditReader auditReader, AdapterGroupUISearch adapterGroupUISearch,
			List<String> userBusiness, boolean selectEntityOnly) {
		AuditQuery query = auditReader.createQuery().forRevisionsOfEntity(AdapterGroup.class, selectEntityOnly, true);
	
		if (!StringUtils.isEmpty(adapterGroupUISearch.getSearchAdptrBzwkGroupName())) {
			query = query
					.add(AuditEntity.id().like(adapterGroupUISearch.getSearchAdptrBzwkGroupName(), MatchMode.ANYWHERE));
		}
		if (!StringUtils.isEmpty(adapterGroupUISearch.getSearchAdptrBzwkGroupDesc())) {
			query = query.add(AuditEntity.property("adptrbzwkgroupdesc")
					.like(adapterGroupUISearch.getSearchAdptrBzwkGroupDesc(), MatchMode.ANYWHERE));
		}
		if (!StringUtils.isEmpty(adapterGroupUISearch.getSearchEaiBzwkDstcd())) {
			query = query.add(AuditEntity.property("eaibzwkdstcd").eq(adapterGroupUISearch.getSearchEaiBzwkDstcd()));
		}
		query = query.add(AuditEntity.property("eaibzwkdstcd").in(userBusiness))
				.addOrder(AuditEntity.revisionNumber().desc());
		
		return query;
	}

	/**
	 * 해당 어댑터그룹의 전체 리비젼정보 리스트 조회
	 * 
	 * @param adptrbzwkgroupname 어댑터그룹이름
	 * @return
	 */
	public List<AdapterGroupAuditUI> getAuditList(String adptrbzwkgroupname) {
		List<String> userBusiness = userBusinessService.getCurrentUserBusinessCodes();

		AuditReader reader = AuditReaderFactory.get(entityManager);
		AuditQuery query = reader.createQuery().forRevisionsOfEntity(AdapterGroup.class, false, true);
		if (!StringUtils.isEmpty(adptrbzwkgroupname))
			query = query.add(AuditEntity.id().like(adptrbzwkgroupname, MatchMode.ANYWHERE));
		query = query.add(AuditEntity.property("eaibzwkdstcd").in(userBusiness))
				.addOrder(AuditEntity.revisionNumber().desc());
		List<?> adapterGroupList = query.getResultList();
		List<AdapterGroupAuditUI> list = new ArrayList<>();
		adapterGroupList.forEach(raws -> this.addAdapterGroupAuditUIFromAuditRaw((Object[]) raws, list));
		return list;
	}

	/**
	 * 모든 어댑터그룹의 전체 리비젼정보 리스트 조회
	 * 
	 * @return
	 */
	public List<AdapterGroupAuditUI> getAuditList() {
		return this.getAuditList("");
	}

	/**
	 * Object[]에서 어댑터그룹 엔터티와 CustomRevisionEntity를 추출하여, AdapterGroupAuditUI배열에
	 * 추가한다.
	 * 
	 * @param raws
	 * @param auditUis
	 */
	private void addAdapterGroupAuditUIFromAuditRaw(Object[] raws, List<AdapterGroupAuditUI> auditUis) {
		AdapterGroupAuditUI auditUI = null;
		CustomRevisionEntity revision = null;
		String revType = "";
		for (Object item : raws) {
			if (item instanceof AdapterGroup) {
				auditUI = this.adapterGroupAuditUIMapperWithoutChild.toVo((AdapterGroup) item);
			} else if (item instanceof CustomRevisionEntity) {
				revision = (CustomRevisionEntity) item;
			} else if (item instanceof RevisionType) {
				revType = ((RevisionType) item).name();
			}
		}
		this.auditUIMapper.map(revision, auditUI);
		auditUI.setRevType(revType);

		auditUis.add(auditUI);
	}

	private void addAdapterAuditUIFromAuditRaw(Object[] raws, List<AdapterAuditUI> auditUis) {
		AdapterAuditUI auditUI = null;
		CustomRevisionEntity revision = null;
		String revType = "";
		for (Object item : raws) {
			if (item instanceof Adapter) {
				auditUI = this.adapterAuditUIMapperWithoutChild.toVo((Adapter) item);
			} else if (item instanceof CustomRevisionEntity) {
				revision = (CustomRevisionEntity) item;
			} else if (item instanceof RevisionType) {
				revType = ((RevisionType) item).name();
			}
		}
		this.auditUIMapper.map(revision, auditUI);
		auditUI.setRevType(revType);

		auditUis.add(auditUI);
	}

	private void addAdapterPropGroupAuditUIFromAuditRaw(Object[] raws, List<AdapterPropGroupAuditUI> auditUis) {
		AdapterPropGroupAuditUI auditUI = null;
		CustomRevisionEntity revision = null;
		String revType = "";
		for (Object item : raws) {
			if (item instanceof AdapterPropGroup) {
				auditUI = this.adapterPropGroupAuditUIMapperWithoutChild.toVo((AdapterPropGroup) item);
			} else if (item instanceof CustomRevisionEntity) {
				revision = (CustomRevisionEntity) item;
			} else if (item instanceof RevisionType) {
				revType = ((RevisionType) item).name();
			}
		}
		this.auditUIMapper.map(revision, auditUI);
		auditUI.setRevType(revType);

		auditUis.add(auditUI);
	}

	private void addAdapterPropAuditUIFromAuditRaw(Object[] raws, List<AdapterPropAuditUI> auditUis) {
		AdapterPropAuditUI auditUI = null;
		CustomRevisionEntity revision = null;
		String revType = "";
		for (Object item : raws) {
			if (item instanceof AdapterProp) {
				auditUI = this.adapterPropAuditUIMapper.toVo((AdapterProp) item);
			} else if (item instanceof CustomRevisionEntity) {
				revision = (CustomRevisionEntity) item;
			} else if (item instanceof RevisionType) {
				revType = ((RevisionType) item).name();
			}
		}
		this.auditUIMapper.map(revision, auditUI);
		auditUI.setRevType(revType);

		auditUis.add(auditUI);
	}

	private void addAdapterGroupFromAuditRaw(Object[] raws, List<AdapterGroupAuditUI> uis) {
		for (Object item : raws) {
			if (item instanceof AdapterGroup) {
				uis.add(this.adapterGroupAuditUIMapperWithoutChild.toVo((AdapterGroup) item));
			}
		}
	}

	private void addAdapterFromAuditRaw(Object[] raws, List<AdapterAuditUI> uis) {
		for (Object item : raws) {
			if (item instanceof Adapter) {
				uis.add(this.adapterAuditUIMapperWithoutChild.toVo((Adapter) item));
			}
		}
	}

	private void addAdapterPropGroupFromAuditRaw(Object[] raws, List<AdapterPropGroupAuditUI> uis) {
		for (Object item : raws) {
			if (item instanceof AdapterPropGroup) {
				uis.add(this.adapterPropGroupAuditUIMapperWithoutChild.toVo((AdapterPropGroup) item));
			}
		}
	}

	private void addAdapterPropFromAuditRaw(Object[] raws, List<AdapterPropAuditUI> uis) {
		for (Object item : raws) {
			if (item instanceof AdapterProp) {
				uis.add(this.adapterPropAuditUIMapper.toVo((AdapterProp) item));
			}
		}
	}

}
