/*
 *  Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. 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.apimgt.gateway.handlers.graphQL.analyzer;

import graphql.analysis.FieldComplexityCalculator;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.parser.ParseException;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.common.gateway.dto.QueryAnalyzerResponseDTO;
import org.wso2.carbon.apimgt.common.gateway.graphql.FieldComplexityCalculatorImpl;
import org.wso2.carbon.apimgt.common.gateway.graphql.QueryAnalyzer;
import org.wso2.carbon.apimgt.gateway.handlers.graphQL.GraphQLConstants;
import org.wso2.carbon.apimgt.impl.APIConstants;

import java.util.ArrayList;
import java.util.Base64;
import java.util.Set;

/**
 * QueryAnalyzer class extension for GraphQL subscription operations.
 */
public class SubscriptionAnalyzer extends QueryAnalyzer {

    private static final Log log = LogFactory.getLog(SubscriptionAnalyzer.class);

    public SubscriptionAnalyzer(GraphQLSchema schema) {
        super(schema);
    }

    /**
     * This method analyses the query depth.
     *
     * @param payload       payload of the request
     * @param maxQueryDepth Maximum query depth
     * @return true, if the query depth does not exceed the maximum value or false, if query depth exceeds the maximum
     */
    public QueryAnalyzerResponseDTO analyseSubscriptionQueryDepth(int maxQueryDepth, String payload) {

        int updatedMaxQueryDepth = getMaxQueryDepth(maxQueryDepth);
        QueryAnalyzerResponseDTO analyzerResponseDTO = analyseQueryDepth(updatedMaxQueryDepth, payload);
        if (!analyzerResponseDTO.isSuccess() && !analyzerResponseDTO.getErrorList().isEmpty()) {
            analyzerResponseDTO.addErrorToList(GraphQLConstants.GRAPHQL_QUERY_TOO_DEEP_MESSAGE);
            log.error(analyzerResponseDTO.getErrorList().toString());
            return analyzerResponseDTO;
        }
        return analyzerResponseDTO;
    }

    /**
     * This method returns the maximum query complexity value.
     *
     * @param maxQueryDepth max query depth property from context.
     * @return maximum query depth value if exists, or -1 to denote no complexity limitation
     */
    private int getMaxQueryDepth(int maxQueryDepth) {

        if (maxQueryDepth > 0) {
            return maxQueryDepth;
        } else {
            log.debug("Maximum query depth value is 0");
            return 0;
        }
    }

    /**
     * This method analyses the query complexity.
     *
     * @param payload            Payload of the request
     * @param maxQueryComplexity Maximum query complexity
     * @return true, if query complexity does not exceed the maximum or false, if query complexity exceeds the maximum
     */
    public QueryAnalyzerResponseDTO analyseSubscriptionQueryComplexity(String payload, int maxQueryComplexity)
            throws APIManagementException {

        FieldComplexityCalculator fieldComplexityCalculator;
        try {
            //get access control policy
            String accessControlInfo = getGraphQLAccessControlInfo();
            fieldComplexityCalculator = new FieldComplexityCalculatorImpl(accessControlInfo);
        } catch (ParseException e) {
            throw new APIManagementException("Error while parsing policy definition.");
        }
        int updatedMaxQueryComplexity = getMaxQueryComplexity(maxQueryComplexity);
        QueryAnalyzerResponseDTO responseDTO = analyseQueryComplexity(updatedMaxQueryComplexity, payload,
                fieldComplexityCalculator);
        if (!responseDTO.isSuccess() && !responseDTO.getErrorList().isEmpty()) {
            responseDTO.addErrorToList(GraphQLConstants.GRAPHQL_QUERY_TOO_COMPLEX_MESSAGE);
            log.error(responseDTO.getErrorList().toString());
            return responseDTO;
        }
        return responseDTO;
    }

    /**
     * This method returns the maximum query complexity value.
     *
     * @param maxComplexity Max complexity
     * @return maximum query complexity value if exists, or -1 to denote no complexity limitation
     */
    private int getMaxQueryComplexity(int maxComplexity) {
        if (maxComplexity > 0) {
            return maxComplexity;
        } else {
            log.debug("Maximum query complexity value is 0");
            return 0;
        }
    }

    /**
     * Get GraphQL complexity access control information from schema.
     *
     * @return Access Control policy
     * @throws APIManagementException if an error occurs
     */
    private String getGraphQLAccessControlInfo() throws APIManagementException {
        String graphQLAccessControlPolicy;
        Set<GraphQLType> additionalTypes = getSchema().getAdditionalTypes();
        for (GraphQLType additionalType : additionalTypes) {
            if (additionalType.getName().startsWith(APIConstants.GRAPHQL_ADDITIONAL_TYPE_PREFIX)) {
                for (GraphQLType type : additionalType.getChildren()) {
                    if (additionalType.getName().contains(APIConstants.GRAPHQL_ACCESS_CONTROL_POLICY)) {
                        graphQLAccessControlPolicy = new String(Base64.getUrlDecoder().decode(type.getName()));
                        return graphQLAccessControlPolicy;
                    }
                }
            }
        }
        throw new APIManagementException(APIConstants.GRAPHQL_ACCESS_CONTROL_POLICY + " not found in schema");
    }
}

@@ -2350,322 +2350,8 @@
 d);%0A
-        if (!analyzerResponseDTO.isSuccess() && !analyzerResponseDTO.getErrorList().isEmpty()) %7B%0A            analyzerResponseDTO.addErrorToList(GraphQLConstants.GRAPHQL_QUERY_TOO_DEEP_MESSAGE);%0A            log.error(analyzerResponseDTO.getErrorList().toString());%0A            return analyzerResponseDTO;%0A        %7D%0A
     
@@ -3841,46 +3841,14 @@
     
-QueryAnalyzerResponseDTO responseDTO =
+return
  ana
@@ -3951,313 +3951,8 @@
 r);%0A
-        if (!responseDTO.isSuccess() && !responseDTO.getErrorList().isEmpty()) %7B%0A            responseDTO.addErrorToList(GraphQLConstants.GRAPHQL_QUERY_TOO_COMPLEX_MESSAGE);%0A            log.error(responseDTO.getErrorList().toString());%0A            return responseDTO;%0A        %7D%0A        return responseDTO;%0A
     

fix returning same error in error response twice.