package com.eactive.eai.rms.ext.djb.webhook.service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.eactive.apim.portal.monitoringcode.entity.MonitoringCode;
import com.eactive.apim.portal.monitoringcode.entity.MonitoringCodeId;
import com.eactive.apim.portal.portalorg.entity.QPortalOrg;
import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.common.util.StringUtils;
import com.eactive.eai.rms.data.entity.man.monitoringCode.repository.MonitoringCodeRepository;
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookSendLog;
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog;
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUI;
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUISearch;
import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository;
import com.eactive.eai.rms.onl.common.exception.BizException;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;

/**
 * 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 조회 화면 서비스.
 *
 * <p>조회 전용. orgId 는 엔티티 연관관계가 아닌 단순 컬럼이므로,
 * 기관명은 QueryDSL 로 ptl_org 를 별도 조회하여 매핑한다.</p>
 */
@Service
@RequiredArgsConstructor
@Transactional(transactionManager = "transactionManagerForEMS")
public class WebhookSendLogManService extends BaseService {

	/** 모니터링 공통코드(tseairm28) 상의 웹훅 이벤트유형 코드 그룹. */
	public static final String CODE_GROUP_EVENT_TYPE = "EVENT_TYPE";

	private static final DateTimeFormatter FMT_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	private static final DateTimeFormatter FMT_YYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd");

	private final WebhookSendLogRepository webhookSendLogRepository;
	private final MonitoringCodeRepository monitoringCodeRepository;

	@PersistenceContext(unitName = "entityManagerFactoryForEMS")
	private EntityManager entityManager;

	public Page<WebhookSendLogUI> selectList(Pageable pageable, WebhookSendLogUISearch search) {
		QWebhookSendLog log = QWebhookSendLog.webhookSendLog;
		BooleanBuilder predicate = new BooleanBuilder();

		if (StringUtils.isNotBlank(search.getSearchOrgName())) {
			List<String> orgIds = findOrgIdsByName(search.getSearchOrgName());
			if (orgIds.isEmpty()) {
				// 일치하는 기관이 없으면 쿼리를 실행할 필요 없이 빈 결과를 바로 반환한다.
				// (QueryDSL의 where false 는 이 프로젝트의 Hibernate HQL 파서에서 지원하지 않음)
				return Page.empty(pageable);
			}
			predicate.and(log.orgId.in(orgIds));
		}
		if (StringUtils.isNotBlank(search.getSearchEventType())) {
			predicate.and(log.eventType.eq(search.getSearchEventType()));
		}
		if (StringUtils.isNotBlank(search.getSearchTargetUrl())) {
			predicate.and(log.targetUrl.containsIgnoreCase(search.getSearchTargetUrl()));
		}
		if (StringUtils.isNotBlank(search.getSearchSuccess())) {
			predicate.and(log.success.eq(search.getSearchSuccess()));
		}
		if (StringUtils.isNotBlank(search.getSearchStartDate())) {
			predicate.and(log.sentAt.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay()));
		}
		if (StringUtils.isNotBlank(search.getSearchEndDate())) {
			predicate.and(log.sentAt.loe(parseYYYYMMDD(search.getSearchEndDate()).atTime(23, 59, 59)));
		}

		Page<WebhookSendLog> page = webhookSendLogRepository.findAll(predicate, pageable);

		Map<String, String> orgNameMap = findOrgNames(page.getContent());
		// 목록의 이벤트유형 코드명은 화면에서 콤보(LIST_INIT_COMBO) 기준으로 변환하므로 서버에서는 조회하지 않는다(N+1 방지).
		return page.map(e -> convertToUI(e, orgNameMap.get(e.getOrgId()), null));
	}

	public WebhookSendLogUI selectDetail(Long id) {
		WebhookSendLog entity = webhookSendLogRepository.findById(id)
				.orElseThrow(() -> new BizException("존재하지 않는 웹훅 발송 로그입니다."));

		Map<String, String> orgNameMap = findOrgNames(Collections.singletonList(entity));
		String eventTypeName = resolveEventTypeName(entity.getEventType());
		return convertToUI(entity, orgNameMap.get(entity.getOrgId()), eventTypeName);
	}

	private static LocalDate parseYYYYMMDD(String yyyyMMdd) {
		return LocalDate.parse(yyyyMMdd, FMT_YYYYMMDD);
	}

	/** 모니터링 공통코드(EVENT_TYPE)에서 코드명을 조회한다. 코드가 없으면 원본 코드를 그대로 반환한다. */
	private String resolveEventTypeName(String eventType) {
		if (StringUtils.isBlank(eventType)) {
			return eventType;
		}
		return monitoringCodeRepository.findById(new MonitoringCodeId(CODE_GROUP_EVENT_TYPE, eventType))
				.map(MonitoringCode::getCodeName)
				.orElse(eventType);
	}

	/** 기관명 LIKE 검색으로 매칭되는 orgId 목록. */
	private List<String> findOrgIdsByName(String orgName) {
		QPortalOrg org = QPortalOrg.portalOrg;
		return new JPAQueryFactory(entityManager)
				.select(org.id)
				.from(org)
				.where(org.orgName.containsIgnoreCase(orgName))
				.fetch();
	}

	/** 목록/상세에 필요한 orgId → orgName 매핑 (일괄 조회로 N+1 방지). */
	private Map<String, String> findOrgNames(List<WebhookSendLog> logs) {
		Set<String> orgIds = logs.stream()
				.map(WebhookSendLog::getOrgId)
				.filter(StringUtils::isNotBlank)
				.collect(Collectors.toSet());
		if (orgIds.isEmpty()) {
			return Collections.emptyMap();
		}

		QPortalOrg org = QPortalOrg.portalOrg;
		return new JPAQueryFactory(entityManager)
				.select(org.id, org.orgName)
				.from(org)
				.where(org.id.in(orgIds))
				.fetch()
				.stream()
				.collect(Collectors.toMap(t -> t.get(org.id), t -> t.get(org.orgName)));
	}

	private WebhookSendLogUI convertToUI(WebhookSendLog e, String orgName, String eventTypeName) {
		WebhookSendLogUI ui = new WebhookSendLogUI();
		ui.setId(e.getId());
		ui.setOrgId(e.getOrgId());
		ui.setOrgName(orgName);
		ui.setEventType(e.getEventType());
		ui.setEventTypeName(eventTypeName);
		ui.setTargetUrl(e.getTargetUrl());
		ui.setProxyUrl(e.getProxyUrl());
		ui.setPayload(e.getPayload());
		ui.setSignature(e.getSignature());
		ui.setStatusCode(e.getStatusCode());
		ui.setResponseBody(e.getResponseBody());
		ui.setSuccess(e.getSuccess());
		ui.setSendBy(e.getSendBy());
		ui.setErrorMessage(e.getErrorMessage());
		ui.setRetryCount(e.getRetryCount());
		ui.setCreatedAt(fmt(e.getCreatedAt(), FMT_DATETIME));
		ui.setSentAt(fmt(e.getSentAt(), FMT_DATETIME));
		return ui;
	}

	private static String fmt(LocalDateTime t, DateTimeFormatter f) {
		return t == null ? "" : t.format(f);
	}
}
