package com.eactive.eai.rms.onl.apim.approval.app;

import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
import com.eactive.apim.portal.approval.statemachine.listener.ApprovalListener;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendService;
import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceType;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.data.entity.onl.apim.apigroup.ApiGroupService;
import com.eactive.eai.rms.onl.apim.approval.RandomStringGenerator;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialManService;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class PortalAppApprovalListener implements ApprovalListener {

    private static final Logger logger = LoggerFactory.getLogger(PortalAppApprovalListener.class);
    private static final String PORTAL_PROPERTY_GROUP = "Portal";


    @Qualifier("onlAgentUtilService")
    @Autowired
    private AgentUtilService agentUtilService;

    private final ApiGroupService apiGroupService;
    private final AppRequestRepository appRequestRepository;
    private final CredentialManService credentialManService;
    private final CredentialUIMapper credentialUIMapper;
    private final ClientManService clientManService;
    private final MessageSendService messageSendService;


    @Override
    public void execute(Approval approval, Map<String, Object> option1) throws ApprovalDeployException {
        Optional<AppRequest> optRequest = appRequestRepository.findAppRequestByApproval(approval);

        AppRequest appRequest = optRequest.orElseGet(null);
        if (appRequest != null) {
            AppRequestType type = appRequest.getType();

            // Portal DB에 credential 저장 (트랜잭션 커밋 보장)
            CredentialUI credentialUI = saveCredential(appRequest, type);

            // 위 메서드가 반환되면 트랜잭션이 커밋되어 Portal DB에 영구 저장됨
            // 이제 APIGW 동기화 및 GW 리로드 수행
            syncTargetServer(type, credentialUI);
            sendApprovalResult(appRequest);
        }
    }

    /**
     * Portal DB에 credential 정보를 저장하고 트랜잭션을 커밋합니다.
     * GW 동기화 이전에 Portal DB의 변경사항이 확정되어야 하므로 별도 트랜잭션으로 분리합니다.
     *
     * @param appRequest 앱 등록/수정/삭제 요청 정보
     * @param type 요청 타입 (NEW/MODIFY/DELETE)
     * @return 저장된 credential 정보
     */
    @Transactional
    public CredentialUI saveCredential(AppRequest appRequest, AppRequestType type) {
        CredentialUI credentialUI = null;

        if (type.equals(AppRequestType.NEW)) {
            credentialUI = createAndSaveCredentialUI(appRequest);
        } else if (type.equals(AppRequestType.MODIFY)) {
            credentialUI = modifyClientUI(appRequest);
        } else if (type.equals(AppRequestType.DELETE)) {
            credentialManService.delete(appRequest.getClientId());
            credentialUI = new CredentialUI();
            credentialUI.setClientid(appRequest.getClientId());
        }

        return credentialUI;
    }

    private void sendApprovalResult(AppRequest appRequest) {
        com.eactive.apim.portal.portaluser.entity.PortalUser requester =
            appRequest.getApproval() != null ? appRequest.getApproval().getRequester() : null;
        if (requester == null) {
            logger.warn("승인 결과 통지 생략 - 요청자 정보 없음. appRequestId: {}", appRequest.getId());
            return;
        }
        MessageRecipient recipient = new MessageRecipient();
        recipient.setUsername(requester.getUserName());
        recipient.setPhone(requester.getMobileNumber());
        recipient.setUserId(requester.getEmailAddr());
        Map<String, String> params = new HashMap<>();
        // DB 템플릿 APP_APPROVE SMS 변수(%APP_NAME%)에 매핑
        params.put("APP_NAME", appRequest.getClientName());
        messageSendService.sendMessage(MessageCode.APP_APPROVE, recipient, params);
    }

    private void syncTargetServer(AppRequestType action, CredentialUI credentialUI) throws ApprovalDeployException {

        DataSourceType type = DataSourceTypeManager.getDataSourceType("APIGW");
        DataSourceContextHolder.setDataSourceType(type);

        if (action.equals(AppRequestType.NEW)) {
            clientManService.insert(credentialUIMapper.mapClient(credentialUI));
        } else if (action.equals(AppRequestType.MODIFY)) {
            clientManService.update(credentialUIMapper.mapClient(credentialUI));
        } else if (action.equals(AppRequestType.DELETE)) {
            clientManService.delete(credentialUI.getClientid());
        }

        CommonCommand.builder()
            .name(CommonCommand.RELOAD_CLIENT_COMMAND)
            .args(credentialUI.getClientid())
            .build().broadcast(agentUtilService);
    }

    private CredentialUI modifyClientUI(AppRequest appRequest) {
        CredentialUI existingCredential = credentialManService.selectDetail(appRequest.getClientId());
        existingCredential.getApiList().clear();

        Set<String> apiIds = processApiGroups(appRequest);
        for (String apiId : apiIds) {
            ApiSpecInfoUI apiSpecInfoUI = new ApiSpecInfoUI();
            apiSpecInfoUI.setApiId(apiId);
            existingCredential.getApiList().add(apiSpecInfoUI);
        }

        credentialManService.update(existingCredential);
        return existingCredential;
    }

    private CredentialUI createAndSaveCredentialUI(AppRequest appRequest) {
        CredentialUI credentialUI = new CredentialUI();
        credentialUI.setClientid(RandomStringGenerator.generateString(32));
        credentialUI.setClientsecret(RandomStringGenerator.generateString(128));
        credentialUI.setClientname(appRequest.getClientName());
        credentialUI.setScope("api");
        credentialUI.setGranttypes("client_credentials");
        credentialUI.setOrgid(appRequest.getOrg() != null ? appRequest.getOrg().getId() : null);
        credentialUI.setOrgname(appRequest.getOrg() != null ? appRequest.getOrg().getOrgName() : null);
        credentialUI.setAllowedips(appRequest.getIpWhitelist());
        credentialUI.setRedirecturi(appRequest.getCallbackUrl());
        credentialUI.setAppIconFileId(appRequest.getAppIconFileId());
        credentialUI.setAppDescription(appRequest.getAppDescription());
        credentialUI.setAccesstokenvalidityseconds("86400");

        Set<String> apiIds = processApiGroups(appRequest);
        for (String apiId : apiIds) {
            ApiSpecInfoUI apiSpecInfoUI = new ApiSpecInfoUI();
            apiSpecInfoUI.setApiId(apiId);
            credentialUI.getApiList().add(apiSpecInfoUI);
        }

        credentialManService.insert(credentialUI);
        return credentialUI;
    }


    private Set<String> processApiGroups(AppRequest appRequest) {
        // Guard clause for null request or both null lists
        if (appRequest == null || (StringUtils.isBlank(appRequest.getApiGroupList()) && StringUtils.isBlank(appRequest.getApiList()))) {
            return Collections.emptySet();
        }

        Set<String> requestApis = processApiList(appRequest.getApiList());
        Set<String> serviceApis = processApiGroupList(appRequest.getApiGroupList());

        // Combine serviceApis and requestApis into one set
        requestApis.addAll(serviceApis);
        return requestApis;
    }

    private Set<String> processApiGroupList(String apiGroupList) {
        if (StringUtils.isBlank(apiGroupList)) {
            return Collections.emptySet();
        }

        return Arrays.stream(apiGroupList.split(","))
            .map(String::trim) // Trim leading and trailing spaces
            .filter(StringUtils::isNotBlank)
            .flatMap(apiGroupId ->
                apiGroupService.findByIdWithApiList(apiGroupId)
                    .map(apiGroup -> apiGroup.getApiGroupApiList().stream()
                        .map(apiGroupApi -> apiGroupApi.getId().getApiId()))
                    .orElse(Stream.empty())
            )
            .filter(Objects::nonNull)
            .collect(Collectors.toSet());
    }

    private Set<String> processApiList(String apiList) {
        if (StringUtils.isBlank(apiList)) {
            return Collections.emptySet();
        }

        return Arrays.stream(apiList.split(","))
            .map(String::trim) // Trim leading and trailing spaces
            .filter(StringUtils::isNotBlank)
            .collect(Collectors.toSet());
    }

    @Override
    @Transactional
    public void rollback(Approval approval) {
        logger.info("Approval {} rollback", approval.getId());

        // 앱 사용 거절 통지
        Optional<AppRequest> optRequest = appRequestRepository.findAppRequestByApproval(approval);
        if (!optRequest.isPresent()) {
            return;
        }
        AppRequest appRequest = optRequest.get();
        com.eactive.apim.portal.portaluser.entity.PortalUser requester = approval.getRequester();
        if (requester == null) {
            logger.warn("앱 거절 통지 생략 - 요청자 정보 없음. appRequestId: {}", appRequest.getId());
            return;
        }
        MessageRecipient recipient = new MessageRecipient();
        recipient.setUsername(requester.getUserName());
        recipient.setPhone(requester.getMobileNumber());
        recipient.setUserId(requester.getEmailAddr());
        Map<String, String> params = new HashMap<>();
        // DB 템플릿 APP_REJECTED SMS 변수(%APP_NAME%, %REASON%)에 매핑
        params.put("APP_NAME", appRequest.getClientName());
        params.put("REASON", extractRejectReason(approval));
        messageSendService.sendMessage(MessageCode.APP_REJECTED, recipient, params);
    }

    private String extractRejectReason(Approval approval) {
        if (approval.getApprovers() == null) {
            return "";
        }
        return approval.getApprovers().stream()
            .map(com.eactive.apim.portal.approval.entity.Approver::getApprovalMessage)
            .filter(m -> m != null && !m.isEmpty())
            .findFirst().orElse("");
    }
}
