import ballerina/log;
import ballerina/http;
import ballerina/config;
import ballerina/time;

{{#if containerConfig.hasKubernetes}}import ballerinax/kubernetes;{{/if}}

import wso2/gateway;

{{#equals endpointConfig.prodEndpoints.type "http"}}
    {{#endpointConfig.prodEndpoints}}{{>httpEndpoint}}{{/endpointConfig.prodEndpoints}}
{{/equals}}
{{#equals endpointConfig.sandEndpoints.type "http"}}
    {{#endpointConfig.sandEndpoints}}{{>httpEndpoint}}{{/endpointConfig.sandEndpoints}}
{{/equals}}
{{#equals endpointConfig.prodEndpoints.type "load_balance"}}
    {{#endpointConfig.prodEndpoints}}{{>lbEndpoint}}{{/endpointConfig.prodEndpoints}}
{{/equals}}
{{#equals endpointConfig.sandEndpoints.type "load_balance"}}
    {{#endpointConfig.sandEndpoints}}{{>lbEndpoint}}{{/endpointConfig.sandEndpoints}}
{{/equals}}
{{#equals endpointConfig.prodEndpoints.type "failover"}}
    {{#endpointConfig.prodEndpoints}}{{>failoverEndpoint}}{{/endpointConfig.prodEndpoints}}
{{/equals}}
{{#equals endpointConfig.sandEndpoints.type "failover"}}
    {{#endpointConfig.sandEndpoints}}{{>failoverEndpoint}}{{/endpointConfig.sandEndpoints}}
{{/equals}}

{{#paths}}{{#value}}{{#operations}}{{#value}}
    {{#equals epConfig.prodEndpoints.type "http"}}
        {{#epConfig.prodEndpoints}}{{>httpResourceEndpoint}}{{/epConfig.prodEndpoints}}
    {{/equals}}
    {{#equals epConfig.sandEndpoints.type "http"}}
        {{#epConfig.sandEndpoints}}{{>httpResourceEndpoint}}{{/epConfig.sandEndpoints}}
    {{/equals}}
    {{#equals endpointConfig.prodEndpoints.type "load_balance"}}
        {{#epConfig.prodEndpoints}}{{>lbResourceEndpoint}}{{/epConfig.prodEndpoints}}
    {{/equals}}
    {{#equals epConfig.sandEndpoints.type "load_balance"}}
        {{#epConfig.sandEndpoints}}{{>lbResourceEndpoint}}{{/epConfig.sandEndpoints}}
    {{/equals}}
    {{#equals epConfig.prodEndpoints.type "failover"}}
        {{#epConfig.prodEndpoints}}{{>failoverResourceEndpoint}}{{/epConfig.prodEndpoints}}
    {{/equals}}
    {{#equals epConfig.sandEndpoints.type "failover"}}
        {{#epConfig.sandEndpoints}}{{>failoverResourceEndpoint}}{{/epConfig.sandEndpoints}}
    {{/equals}}
{{/value}}{{/operations}}{{/value}}{{/paths}}


{{>kubernetes}}

@http:ServiceConfig {
    basePath: "{{basepath}}",{{!-- {{only one base path is allowed for all  endpoints}} --}}
    authConfig:{
    {{#if config.basicAuth.isOptional}}
        authProviders:["basic","oauth2","jwt"]
    {{else if config.basicAuth.isRequired}}
        authProviders:["basic"]
    {{else}}
        authProviders:["jwt","oauth2"]
    {{/if}}
    }{{#api.corsConfiguration.corsConfigurationEnabled}},
    cors: {
         allowOrigins: [{{#api.corsConfiguration.accessControlAllowOrigins}}"{{.}}"{{#unless @last}},{{/unless}}{{/api.corsConfiguration.accessControlAllowOrigins}}],
         allowCredentials: {{api.corsConfiguration.accessControlAllowCredentials}},
         allowHeaders: [{{#api.corsConfiguration.accessControlAllowHeaders}}"{{.}}"{{#unless @last}},{{/unless}}{{/api.corsConfiguration.accessControlAllowHeaders}}],
         allowMethods: [{{#api.corsConfiguration.accessControlAllowMethods}}"{{.}}"{{#unless @last}},{{/unless}}{{/api.corsConfiguration.accessControlAllowMethods}}]
    }
    {{/api.corsConfiguration.corsConfigurationEnabled}}
}

@gateway:API {
    publisher:"{{api.provider}}",
    name:"{{api.name}}",
    apiVersion: "{{api.version}}" {{#if api.authorizationHeader}},
    authorizationHeader : "{{api.authorizationHeader}}" {{/if}}
}
service {{cut qualifiedServiceName " "}} on {{#api.transport}}{{#equals . "http"}}apiListener{{#unless @last}},
{{/unless}}{{/equals}}{{#equals . "https"}}apiSecureListener{{/equals}}{{/api.transport}} {
{{#paths}}{{#value}}{{#operations}}{{#value}}

    @http:ResourceConfig {
        methods:["{{upper key}}"],
        path:"{{../../key}}",
        authConfig:{
    {{#if config.basicAuth.isOptional}}
        authProviders:["basic","oauth2","jwt"]
    {{else if config.basicAuth.isRequired}}
        authProviders:["basic"]
    {{else}}
        authProviders:["jwt","oauth2"]
    {{/if}} {{#unless isSecured}},
            authentication:{enabled:false} {{/unless}} {{#if scope}},
            scopes: ["{{scope}}"] {{/if}}

        }
    }
    @gateway:RateLimit{policy : "{{resourceTier}}"}
    resource function {{operationId}} (http:Caller outboundEp, http:Request req) {
        handleExpectHeaderFor{{cut qualifiedServiceName " "}}(outboundEp, req);
    {{#if apiRequestInterceptor}}{{apiRequestInterceptor}} (outboundEp, req);{{/if}}
    {{#if requestInterceptor}}{{requestInterceptor}} (outboundEp, req);{{/if}}
    {{#if api.isDefaultVersion}}
    string urlPostfix = untaint req.rawPath.replace("{{basepath}}","");
    {{else}}
    string urlPostfix = untaint req.rawPath.replace("{{basepath}}","");
    {{/if}}
        http:Response|error clientResponse;
        http:Response r = new;
        clientResponse = r;
        string destination_attribute;
        runtime:getInvocationContext().attributes["timeStampRequestOut"] = time:currentTime().time;
        {{#if etcd.etcdEnabled}}boolean reinitRequired = false;
        string etcdKey;
        string etcdKeyConfigValue;
        boolean|error hasUrlChanged;
        http:ClientEndpointConfig newConfig;
        boolean reinitFailed = false;
        string apiId = "{{api.id}}";{{/if}}
        {{#endpointConfig.prodEndpoints}}
            {{#endpointConfig.sandEndpoints}}
                if("PRODUCTION" == <string>runtime:getInvocationContext().attributes["KEY_TYPE"]) {
                    destination_attribute = "{{#if hasProdEpConfig}}{{epConfig.prodEndpoints.endpoints.0.endpointUrl}}{{else}}{{endpointConfig.prodEndpoints.endpoints.0.endpointUrl}}{{/if}}";
                {{#if etcd.etcdEnabled}}
                    etcdKeyConfigValue = apiId + "_prod_etcdKey";
                    {{>prodEndpointReinit}}
                {{else}}
                        clientResponse = {{#if hasProdEpConfig}}{{operationId}}{{else}}{{qualifiedServiceName}}{{/if}}_prod->forward(urlPostfix, req);
                    runtime:getInvocationContext().attributes["destination"] = destination_attribute;
                {{/if}}
                } else {
                    destination_attribute = "{{#if hasSandEpConfig}}{{epConfig.sandEndpoints.endpoints.0.endpointUrl}}{{else}}{{endpointConfig.sandEndpoints.endpoints.0.endpointUrl}}{{/if}}";
                {{#if etcd.etcdEnabled}}
                    etcdKeyConfigValue = apiId + "_sand_etcdKey";
                    {{>sandEndpointReinit}}
                {{else}}
                    clientResponse = {{#if hasSandEpConfig}}{{operationId}}{{else}}{{qualifiedServiceName}}{{/if}}_sand->forward(urlPostfix, req);
                    runtime:getInvocationContext().attributes["destination"] = destination_attribute;
                {{/if}}
                }
            {{/endpointConfig.sandEndpoints}}
            {{^endpointConfig.sandEndpoints}}
                if("PRODUCTION" == <string>runtime:getInvocationContext().attributes["KEY_TYPE"]) {
                    destination_attribute = "{{#if hasProdEpConfig}}{{epConfig.prodEndpoints.endpoints.0.endpointUrl}}{{else}}{{endpointConfig.prodEndpoints.endpoints.0.endpointUrl}}{{/if}}";
                {{#if etcd.etcdEnabled}}
                    etcdKeyConfigValue = apiId + "_prod_etcdKey";
                    {{>prodEndpointReinit}}
                {{else}}
                    clientResponse = {{#if hasProdEpConfig}}{{operationId}}{{else}}{{qualifiedServiceName}}{{/if}}_prod->forward(urlPostfix, req);
                    runtime:getInvocationContext().attributes["destination"] = destination_attribute;
                {{/if}}
                } else {
                    http:Response res = new;
                    res.statusCode = 403;
                    json payload = {
                        ERROR_CODE: "900901",
                        ERROR_MESSAGE: "Sandbox key offered to the API with no sandbox endpoint"
                    };
                    runtime:getInvocationContext().attributes["error_code"] = "900901";
                    res.setPayload(payload);
                    clientResponse = res;
                }
            {{/endpointConfig.sandEndpoints}}
        {{/endpointConfig.prodEndpoints}}
        {{^endpointConfig.prodEndpoints}}
            if("SANDBOX" == <string>runtime:getInvocationContext().attributes["KEY_TYPE"]) {
                destination_attribute = "{{#if hasSandEpConfig}}{{epConfig.sandEndpoints.endpoints.0.endpointUrl}}{{else}}{{endpointConfig.sandEndpoints.endpoints.0.endpointUrl}}{{/if}}";
            {{#if etcd.etcdEnabled}}
                etcdKeyConfigValue = apiId + "_sand_etcdKey";
                {{>sandEndpointReinit}}
            {{else}}
                clientResponse = {{#if hasProdEpConfig}}{{operationId}}{{else}}{{qualifiedServiceName}}{{/if}}_sand->forward(urlPostfix, req);
                runtime:getInvocationContext().attributes["destination"] = destination_attribute;
            {{/if}}
            } else {
                http:Response res = new;
                res.statusCode = 403;
                json payload = {
                    ERROR_CODE: "900901",
                    ERROR_MESSAGE: "Production key offered to the API with no production endpoint"
                };
                runtime:getInvocationContext().attributes["error_code"] = "900901";
                res.setPayload(payload);
                clientResponse = res;
            }
        {{/endpointConfig.prodEndpoints}}
        runtime:getInvocationContext().attributes["timeStampResponseIn"] = time:currentTime().time;


        if(clientResponse is http:Response) {
            {{#if responseInterceptor}}{{responseInterceptor}} (outboundEp, clientResponse);{{/if}}
            {{#if apiResponseInterceptor}}{{apiResponseInterceptor}} (outboundEp, clientResponse);{{/if}}
            var outboundResult = outboundEp->respond(clientResponse);
            if (outboundResult is error) {
                log:printError("Error when sending response", err = outboundResult);
            }
        }

        else {
            http:Response res = new;
            res.statusCode = 500;
            string errorMessage = clientResponse.reason();
            int errorCode = 101503;
            string errorDescription = "Error connecting to the back end";

            if(errorMessage.contains("connection timed out") || errorMessage.contains("Idle timeout triggered")) {
                errorCode = 101504;
                errorDescription = "Connection timed out";
            }
            if(errorMessage.contains("Malformed URL")) {
                errorCode = 101505;
                errorDescription = "Malformed URL";
            }
            // Todo: error is not type any -> runtime:getInvocationContext().attributes["error_response"] =  clientResponse;
            runtime:getInvocationContext().attributes["error_response_code"] = errorCode;
            json payload = {fault : {
                code : errorCode,
                message : "Runtime Error",
                description : errorDescription
            }};
            res.setPayload(payload);
            log:printError("Error in client response", err = clientResponse);
            var outboundResult = outboundEp->respond(res);
            if (outboundResult is error) {
                log:printError("Error when sending response", err = outboundResult);
            }
        }
    }
{{/value}}{{/operations}}{{/value}}{{/paths}}
}

    function handleExpectHeaderFor{{cut qualifiedServiceName " "}} (http:Caller outboundEp, http:Request req ) {
        if (req.expects100Continue()) {
            req.removeHeader("Expect");
            var result = outboundEp->continue();
            if (result is error) {
            log:printError("Error while sending 100 continue response", err = result);
            }
        }
    }