/*
 * Copyright (c) 2015-2025, WSO2 LLC. (http://www.wso2.com).
 *
 * WSO2 LLC. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.wso2.carbon.event.output.adapter.core;

import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.MDC;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.event.output.adapter.core.exception.OutputEventAdapterRuntimeException;
import org.wso2.carbon.event.output.adapter.core.internal.config.AdapterConfig;
import org.wso2.carbon.event.output.adapter.core.internal.config.AdapterConfigs;
import org.wso2.carbon.event.output.adapter.core.internal.ds.OutputEventAdapterServiceValueHolder;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.DiagnosticLog;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EventAdapterUtil {

    private static final Log LOG = LogFactory.getLog(EventAdapterUtil.class);
    private static final String TENANT_DOMAIN = "tenantDomain";
    private static final String ACCESS_TOKEN_KEY = "access_token";

    public static AxisConfiguration getAxisConfiguration() {
        AxisConfiguration axisConfiguration = null;
        if (CarbonContext.getThreadLocalCarbonContext().getTenantId() == MultitenantConstants.SUPER_TENANT_ID) {
            axisConfiguration = OutputEventAdapterServiceValueHolder.getConfigurationContextService().
                    getServerConfigContext().getAxisConfiguration();
        } else {
            ConfigurationContext configurationContext = TenantConfigHolder
                    .getTenantConfig(PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId());
            if (configurationContext != null) {
                axisConfiguration = configurationContext.getAxisConfiguration();
            } else {
                throw new OutputEventAdapterRuntimeException("Tenant configuration not found");
            }
        }
        return axisConfiguration;
    }

    public static void logAndDrop(String adapterName, Object event, String message, Throwable e, Log log,
                                  int tenantId) {
        if (message != null) {
            message = message + ", ";
        } else {
            message = "";
        }
        log.error("Event dropped at Output Adapter '" + adapterName + "' for tenant id '" + tenantId + "', " + message + e
                .getMessage(), e);
        if (log.isDebugEnabled()) {
            log.debug("Error at Output Adapter '" + adapterName + "' for tenant id '" + tenantId + "', dropping event: \n"
                    + event, e);
        }
    }

    public static void logAndDrop(String adapterName, Object event, String message, Log log, int tenantId) {
        log.error("Event dropped at Output Adapter '" + adapterName + "' for tenant id '" + tenantId + "', " + message);
        if (log.isDebugEnabled()) {
            log.debug("Error at Output Adapter '" + adapterName + "' for tenant id '" + tenantId + "', dropping event: \n"
                    + event);
        }
    }

    public static Map<String, String> getGlobalProperties(String type) {
        AdapterConfigs adapterConfigs = OutputEventAdapterServiceValueHolder.getGlobalAdapterConfigs();
        if (adapterConfigs != null) {
            AdapterConfig adapterConfig = adapterConfigs.getAdapterConfig(type);
            if (adapterConfig != null) {
                return adapterConfig.getGlobalPropertiesAsMap();
            }
        }
        return null;
    }

    /**
     * Retrieves the access token using the client credentials grant type.
     *
     * @param clientId      The client ID.
     * @param secret        The client Secret.
     * @param tokenEndpoint The token endpointURL.
     * @param scopes        The scopes to be requested.
     * @return Access Token.
     */
    public static String getAccessToken(String clientId, String secret, String tokenEndpoint, String scopes) {

        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(createTokenRequest(clientId, secret,
                     tokenEndpoint, scopes))) {
            if (response == null ) {
                throw new OutputEventAdapterRuntimeException("Error while getting access token. " +
                        "Null response received from the token endpoint");
            }
            if (response.getStatusLine().getStatusCode() / 100 != 2) {
                throw new OutputEventAdapterRuntimeException("Received unsuccessful response from the token endpoint." +
                        " Received response code: " + response.getStatusLine().getStatusCode() + ".");
            }

            String responseString = EntityUtils.toString(response.getEntity());
            if (responseString != null) {
                try {
                    JSONObject jsonResponse = new JSONObject(responseString);
                    if (jsonResponse.has(ACCESS_TOKEN_KEY) && !jsonResponse.isNull(ACCESS_TOKEN_KEY)) {
                        return jsonResponse.getString(ACCESS_TOKEN_KEY);
                    } else {
                        throw new OutputEventAdapterRuntimeException(
                                "Access Token is not available in the token response. Received response code: " +
                                        response.getStatusLine().getStatusCode() + ".");
                    }
                } catch (JSONException e) {
                    throw new OutputEventAdapterRuntimeException("Error while getting access token. " +
                            "Invalid JSON response received from the token endpoint. Received response code: " +
                            response.getStatusLine().getStatusCode() + ".");
                }
            } else {
                throw new OutputEventAdapterRuntimeException("Error while getting access token. " +
                        "Null response received from the token endpoint");
            }
        } catch (OutputEventAdapterRuntimeException e) {
            throw e;
        } catch (Exception e) {
            LOG.error("Error while getting access token", e);
            throw new OutputEventAdapterRuntimeException("Error while getting access token", e);
        }
    }

    /**
     * Triggers a diagnostic log event with the given diagnostic log builder.
     *
     * @param diagnosticLogBuilder The diagnostic log builder containing the details of the log event.
     */
    public static void triggerDiagnosticLogEvent(DiagnosticLog.DiagnosticLogBuilder diagnosticLogBuilder) {

        Map<String, Object> diagnosticLogProperties = new HashMap();
        DiagnosticLog diagnosticLog = diagnosticLogBuilder.build();
        diagnosticLogProperties.put("diagnosticLog", diagnosticLog);
        diagnosticLogProperties.put("tenantId", resolveTenantId());
        CarbonUtils.publishDiagnosticLog(diagnosticLogProperties);
    }

    /**
     * Resolves the tenant ID from the MDC context or CarbonContext.
     *
     * @return The resolved tenant ID.
     */
    private static int resolveTenantId() {

        String tenantDomain = MDC.get(TENANT_DOMAIN);
        if (StringUtils.isBlank(tenantDomain)) {
            tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain();
        }
        return IdentityTenantUtil.getTenantId(tenantDomain);
    }

    private static HttpPost createTokenRequest(String clientId, String secret, String tokenEndpoint, String scopes)
            throws UnsupportedEncodingException {

        HttpPost request = new HttpPost(tokenEndpoint);
        request.setHeader("Content-Type", "application/x-www-form-urlencoded");

        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("client_id", clientId));
        params.add(new BasicNameValuePair("client_secret", secret));
        params.add(new BasicNameValuePair("scope", scopes));
        params.add(new BasicNameValuePair("grant_type", "client_credentials"));

        request.setEntity(new UrlEncodedFormEntity(params));
        return request;
    }
}
