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.portalproperty.service.PortalPropertyService;
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.apim.portaluser.PortalUserUI;
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.manage.authserver.client.ClientUI;
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.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;
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.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

@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 PortalPropertyService portalPropertyService;
    private final ApiGroupService apiGroupService;
    private final AppRequestRepository appRequestRepository;
    private final CredentialManService credentialManService;
    private final CredentialUIMapper credentialUIMapper;
    private final ClientManService clientManService;
    private final MessageSendService messageSendService;

    public static RestTemplate createRestTemplateWithTimeout(int connectTimeout) {

        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(connectTimeout)
            .setConnectTimeout(connectTimeout)
            .setSocketTimeout(connectTimeout)
            .build();

        HttpClient httpClient =
            HttpClientBuilder.create()
                .setMaxConnTotal(50)
                .setMaxConnPerRoute(20)
                .setDefaultRequestConfig(requestConfig)
                .build();

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(factory);

        return restTemplate;
    }

    @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();
            String server = determineServer(type);

            CredentialUI credentialUI = null;

            if (appRequest.getType().equals(AppRequestType.PROD_NEW)) {
                credentialUI = createAndSaveCredentialUI("prod", optRequest.get());
            } else if (appRequest.getType().equals(AppRequestType.PROD_MODIFY)) {
                credentialUI = modifyClientUI(optRequest.get());
            } else if (appRequest.getType().equals(AppRequestType.PROD_DELETE)) {
                credentialManService.delete(optRequest.get().getClientId());
                credentialUI = new CredentialUI();
                credentialUI.setClientid(optRequest.get().getClientId());
            } else if (appRequest.getType().equals(AppRequestType.NEW)) {
                credentialUI = createAndSaveCredentialUI("stg", optRequest.get());
            } else if (appRequest.getType().equals(AppRequestType.MODIFY)) {
                credentialUI = modifyClientUI(optRequest.get());
            } else if (appRequest.getType().equals(AppRequestType.DELETE)) {
                credentialManService.delete(optRequest.get().getClientId());
                credentialUI = new CredentialUI();
                credentialUI.setClientid(optRequest.get().getClientId());
            }
            syncTargetServer(server, determineAction(type), credentialUI);
            sendApprovalResult(appRequest);
        }
    }

    private void sendApprovalResult(AppRequest appRequest) {
        MessageRecipient recipient = new MessageRecipient();
        recipient.setPhone(appRequest.getApproval().getRequester().getMobileNumber());
        recipient.setUserId(appRequest.getApproval().getRequester().getEmailAddr());
        Map<String, String> params = new HashMap<>();
        params.put("apiKey", appRequest.getClientName());
        messageSendService.sendMessage("APP_REGISTER_APPROVED", recipient, params);
    }

    private String determineAction(AppRequestType type) {
        if (type == AppRequestType.PROD_NEW || type == AppRequestType.NEW) {
            return "insert";
        } else if (type == AppRequestType.PROD_MODIFY || type == AppRequestType.MODIFY) {
            return "update";
        } else if (type == AppRequestType.PROD_DELETE || type == AppRequestType.DELETE) {
            return "delete";
        } else {
            return "none";
        }
    }

    private String determineServer(AppRequestType type) {
        if (type == AppRequestType.NEW || type == AppRequestType.MODIFY || type == AppRequestType.DELETE) {
            return "stg";
        } else {
            return "prod";
        }
    }

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

        // 프록시를 통한 스테이징 서버 호출 추가
        Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP);
        String prodUseProxy = properties.getOrDefault("deploy.prod_use_proxy", "N");

        if (server.equalsIgnoreCase("prod") && prodUseProxy.equalsIgnoreCase("N")) {

            if (action.equalsIgnoreCase("insert")) {
                clientManService.insert(credentialUIMapper.mapClient(credentialUI));
            } else if (action.equalsIgnoreCase("update")) {
                clientManService.update(credentialUIMapper.mapClient(credentialUI));
            } else if (action.equalsIgnoreCase("delete")) {
                clientManService.delete(credentialUI.getClientid());
            }

            DataSourceType type = DataSourceTypeManager.getDataSourceType("APIGW");
            DataSourceContextHolder.setDataSourceType(type);
            CommonCommand.builder()
                .name(CommonCommand.RELOAD_CLIENT_COMMAND)
                .args(credentialUI.getClientid())
                .build().broadcast(agentUtilService);

        } else {
            String apiKey = properties.get("apiKey");
            String proxyUrl = properties.get("portal.url");
            String timeout = properties.getOrDefault("deploy.proxy_timeout", "10000");
            String proxyEndpoint = proxyUrl + "/_onl/admin/authserver/clientMan.json?serviceType=APIGW";

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.set("X-API-KEY", apiKey);
            headers.set("target_host", server);
            headers.set("action", action);

            HttpEntity<ClientUI> request = new HttpEntity<>(credentialUIMapper.mapClient(credentialUI), headers);

            try {
                ResponseEntity<String> response = createRestTemplateWithTimeout(Integer.parseInt(timeout)).exchange(
                    proxyEndpoint,
                    HttpMethod.POST,
                    request,
                    String.class
                );

                if (!response.getStatusCode().is2xxSuccessful()) {
                    logger.error("Failed to sync with {} server. Status: {}, Body: {}", server, response.getStatusCode(), response.getBody());
                    throw new ApprovalDeployException("API키 배포에 실패 했습니다.");
                }
            } catch (Exception e) {
                logger.error("Failed to sync with {} server. {}", server, e.getMessage());
                throw new ApprovalDeployException("API키 배포에 실패 했습니다.");
            }

        }

    }

    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(String server, AppRequest appRequest) {
        CredentialUI credentialUI = new CredentialUI();
        credentialUI.setServer(server);
        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().getId());
        credentialUI.setOrgname(appRequest.getOrg().getOrgName());
        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());
    }
}
