content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF 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.apache.camel.quarkus.component.aws2.lambda.it;
  
  import java.net.URI;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  
  import jakarta.enterprise.context.ApplicationScoped;
  import jakarta.inject.Inject;
  import jakarta.ws.rs.Consumes;
  import jakarta.ws.rs.DELETE;
  import jakarta.ws.rs.GET;
  import jakarta.ws.rs.POST;
  import jakarta.ws.rs.PUT;
  import jakarta.ws.rs.Path;
  import jakarta.ws.rs.PathParam;
  import jakarta.ws.rs.Produces;
  import jakarta.ws.rs.QueryParam;
  import jakarta.ws.rs.core.MediaType;
  import jakarta.ws.rs.core.Response;
  import jakarta.ws.rs.core.Response.Status;
  import jakarta.ws.rs.ext.Provider;
  import org.apache.camel.CamelExecutionException;
  import org.apache.camel.ProducerTemplate;
  import org.apache.camel.component.aws2.lambda.Lambda2Constants;
  import org.apache.camel.component.aws2.lambda.Lambda2Operations;
  import org.apache.camel.quarkus.test.support.aws2.BaseAws2Resource;
  import org.eclipse.microprofile.config.inject.ConfigProperty;
  import org.jboss.logging.Logger;
  import software.amazon.awssdk.core.SdkBytes;
  import software.amazon.awssdk.services.lambda.model.AliasConfiguration;
  import software.amazon.awssdk.services.lambda.model.CreateEventSourceMappingResponse;
  import software.amazon.awssdk.services.lambda.model.EventSourceMappingConfiguration;
  import software.amazon.awssdk.services.lambda.model.FunctionConfiguration;
  import software.amazon.awssdk.services.lambda.model.GetAliasRequest;
  import software.amazon.awssdk.services.lambda.model.GetAliasResponse;
  import software.amazon.awssdk.services.lambda.model.GetFunctionResponse;
  import software.amazon.awssdk.services.lambda.model.InvalidParameterValueException;
  import software.amazon.awssdk.services.lambda.model.LastUpdateStatus;
  import software.amazon.awssdk.services.lambda.model.ListAliasesRequest;
  import software.amazon.awssdk.services.lambda.model.ListAliasesResponse;
  import software.amazon.awssdk.services.lambda.model.ListEventSourceMappingsResponse;
  import software.amazon.awssdk.services.lambda.model.ListFunctionsResponse;
  import software.amazon.awssdk.services.lambda.model.ListTagsResponse;
  import software.amazon.awssdk.services.lambda.model.ListVersionsByFunctionResponse;
  import software.amazon.awssdk.services.lambda.model.Runtime;
  import software.amazon.awssdk.services.lambda.model.UpdateFunctionCodeRequest;
  import software.amazon.awssdk.services.lambda.model.UpdateFunctionCodeResponse;
  
  @Path("/aws2-lambda")
  @ApplicationScoped
  public class Aws2LambdaResource extends BaseAws2Resource {
  
      private static final Logger LOG = Logger.getLogger(Aws2LambdaResource.class);
  
      @ConfigProperty(name = "aws-lambda.role-arn")
      String roleArn;
  
      @ConfigProperty(name = "aws-lambda.event-source-arn")
      String eventSourceArn;
  
      @Inject
      ProducerTemplate producerTemplate;
  
      public Aws2LambdaResource() {
          super("lambda");
      }
  
      @Path("/function/create/{functionName}")
      @POST
      @Consumes("application/zip")
      @Produces(MediaType.TEXT_PLAIN)
      public Response createFunction(byte[] zipFunctionBytes, @PathParam("functionName") String functionName) throws Exception {
          final String response = producerTemplate.requestBodyAndHeaders(
                  componentUri(functionName, Lambda2Operations.createFunction),
                  zipFunctionBytes,
                  new LinkedHashMap<String, Object>() {
                      {
                          put(Lambda2Constants.ROLE, roleArn);
                          put(Lambda2Constants.RUNTIME, Runtime.PYTHON3_9);
                          put(Lambda2Constants.HANDLER, "index.handler");
                      }
                  },
                  String.class);
          return Response
                  .created(new URI("https://camel.apache.org/"))
                  .entity(response)
                  .build();
      }
  
      @Path("/function/getState/{functionName}")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public String getFunction(@PathParam("functionName") String functionName) {
          return producerTemplate
                  .requestBody(componentUri(functionName, Lambda2Operations.getFunction), null, GetFunctionResponse.class)
                  .configuration().stateAsString();
      }
  
      @Path("/function/getArn/{functionName}")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public String getFunctionArn(@PathParam("functionName") String functionName) {
          return producerTemplate
                  .requestBody(componentUri(functionName, Lambda2Operations.getFunction), null, GetFunctionResponse.class)
                  .configuration().functionArn();
      }
  
      @Path("/function/update/{functionName}")
      @PUT
      @Consumes("application/zip")
      @Produces(MediaType.TEXT_PLAIN)
      public Response updateFunction(byte[] zipFunctionBytes, @PathParam("functionName") String functionName) {
          String uri = componentUri(functionName, Lambda2Operations.updateFunction) + "&pojoRequest=true";
          UpdateFunctionCodeRequest ufcRequest = UpdateFunctionCodeRequest.builder().functionName(functionName)
                  .zipFile(SdkBytes.fromByteArray(zipFunctionBytes)).build();
          UpdateFunctionCodeResponse ufcResponse = producerTemplate.requestBody(uri, ufcRequest,
                  UpdateFunctionCodeResponse.class);
  
          if (ufcResponse.lastUpdateStatus() == LastUpdateStatus.SUCCESSFUL
                  || ufcResponse.lastUpdateStatus() == LastUpdateStatus.IN_PROGRESS) {
              return Response.ok().build();
          }
          throw new IllegalStateException(
                  ufcResponse.lastUpdateStatusReasonCodeAsString() + ": " + ufcResponse.lastUpdateStatusReason());
      }
  
      @Path("/function/list")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<String> listFunctions() {
          return producerTemplate.requestBody(
                  componentUri("foo", Lambda2Operations.listFunctions),
                  null,
                  ListFunctionsResponse.class)
                  .functions().stream()
                  .map(FunctionConfiguration::functionName)
                  .collect(Collectors.toList());
      }
  
      @Path("/function/invoke/{functionName}")
      @POST
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      public String invokeFunction(byte[] message, @PathParam("functionName") String functionName) {
          return producerTemplate.requestBody(
                  componentUri(functionName, Lambda2Operations.invokeFunction),
                  message,
                  String.class);
      }
  
      @Path("/function/delete/{functionName}")
      @DELETE
      public void deleteFunction(@PathParam("functionName") String functionName) {
          producerTemplate.requestBody(
                  componentUri(functionName, Lambda2Operations.deleteFunction),
                  null,
                  Object.class);
      }
  
      @Path("/alias/create")
      @POST
      @Produces(MediaType.TEXT_PLAIN)
      public Response createAlias(@QueryParam("functionName") String functionName,
              @QueryParam("functionVersion") String functionVersion, @QueryParam("aliasName") String aliasName) throws Exception {
          try {
              final String response = producerTemplate.requestBodyAndHeaders(
                      componentUri(functionName, Lambda2Operations.createAlias),
                      null,
                      new LinkedHashMap<String, Object>() {
                          {
                              put(Lambda2Constants.FUNCTION_ALIAS_NAME, aliasName);
                              put(Lambda2Constants.FUNCTION_VERSION, functionVersion);
                          }
                      },
                      String.class);
              return Response
                      .created(new URI("https://camel.apache.org/"))
                      .entity(response)
                      .build();
          } catch (Exception e) {
              LOG.info("Exception caught in alias/create", e);
              LOG.info("Exception cause in alias/create", e.getCause());
              throw e;
          }
      }
  
      @Path("/alias/get")
      @GET
      @Produces(MediaType.TEXT_PLAIN)
      public String getAlias(@QueryParam("functionName") String functionName, @QueryParam("aliasName") String aliasName) {
          try {
              GetAliasRequest getAliasRequest = GetAliasRequest.builder().functionName(functionName).name(aliasName).build();
              String endpointUri = componentUri(functionName, Lambda2Operations.getAlias) + "&pojoRequest=true";
  
              return producerTemplate
                      .requestBody(endpointUri, getAliasRequest, GetAliasResponse.class)
                      .functionVersion();
          } catch (Exception e) {
              LOG.info("Exception caught in alias/get", e);
              LOG.info("Exception cause in alias/get", e.getCause());
              throw e;
          }
      }
  
      @Path("/alias/delete")
      @DELETE
      public void deleteAlias(@QueryParam("functionName") String functionName, @QueryParam("aliasName") String aliasName) {
          try {
              producerTemplate.requestBodyAndHeader(
                      componentUri(functionName, Lambda2Operations.deleteAlias),
                      null,
                      Lambda2Constants.FUNCTION_ALIAS_NAME,
                      aliasName,
                      Object.class);
          } catch (Exception e) {
              LOG.info("Exception caught in alias/delete", e);
              LOG.info("Exception cause in alias/delete", e.getCause());
              throw e;
          }
      }
  
      @Path("/alias/list")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<String> listAliases(@QueryParam("functionName") String functionName) {
          try {
              ListAliasesRequest listAliasesReq = ListAliasesRequest.builder().functionName(functionName).build();
              return producerTemplate.requestBody(
                      componentUri(functionName, Lambda2Operations.listAliases) + "&pojoRequest=true",
                      listAliasesReq,
                      ListAliasesResponse.class)
                      .aliases().stream()
                      .map(AliasConfiguration::name)
                      .collect(Collectors.toList());
          } catch (Exception e) {
              LOG.info("Exception caught in alias/list", e);
              LOG.info("Exception cause in alias/list", e.getCause());
              throw e;
          }
      }
  
      @Path("/tag/create")
      @POST
      @Produces(MediaType.TEXT_PLAIN)
      public Response tagLambdaFunction(@QueryParam("functionArn") String functionArn,
              @QueryParam("tagResourceKey") String tagResourceKey, @QueryParam("tagResourceValue") String tagResourceValue)
              throws Exception {
          Map<String, String> resourceTags = Map.of(tagResourceKey, tagResourceValue);
          final String response = producerTemplate.requestBodyAndHeaders(
                  componentUri(null, Lambda2Operations.tagResource),
                  null,
                  new LinkedHashMap<String, Object>() {
                      {
                          put(Lambda2Constants.RESOURCE_ARN, functionArn);
                          put(Lambda2Constants.RESOURCE_TAGS, resourceTags);
                      }
                  },
                  String.class);
          return Response
                  .created(new URI("https://camel.apache.org/"))
                  .entity(response)
                  .build();
      }
  
      @Path("/tag/list")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public Map<String, String> listLambdaFunctionTags(@QueryParam("functionArn") String functionArn) {
          return producerTemplate.requestBodyAndHeaders(
                  componentUri(null, Lambda2Operations.listTags),
                  null,
                  new LinkedHashMap<String, Object>() {
                      {
                          put(Lambda2Constants.RESOURCE_ARN, functionArn);
                      }
                  },
                  ListTagsResponse.class)
                  .tags();
      }
  
      @Path("/tag/delete")
      @DELETE
      public void untagLambdaFunction(@QueryParam("functionArn") String functionArn,
              @QueryParam("tagResourceKey") String tagResourceKey) {
          producerTemplate.requestBodyAndHeaders(
                  componentUri(null, Lambda2Operations.untagResource),
                  null,
                  new LinkedHashMap<String, Object>() {
                      {
                          put(Lambda2Constants.RESOURCE_ARN, functionArn);
                          put(Lambda2Constants.RESOURCE_TAG_KEYS, List.of(tagResourceKey));
                      }
                  });
      }
  
      @Path("/version/publish")
      @POST
      @Produces(MediaType.TEXT_PLAIN)
      public Response publishVersion(@QueryParam("functionName") String functionName,
              @QueryParam("versionDescription") String versionDescription) throws Exception {
          final String response = producerTemplate.requestBody(
                  componentUri(functionName, Lambda2Operations.publishVersion),
                  null,
                  String.class);
          return Response
                  .created(new URI("https://camel.apache.org/"))
                  .entity(response)
                  .build();
      }
  
      @Path("/version/list")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<String> listVersions(@QueryParam("functionName") String functionName) {
          try {
              return producerTemplate.requestBody(
                      componentUri(functionName, Lambda2Operations.listVersions),
                      null,
                      ListVersionsByFunctionResponse.class)
                      .versions().stream()
                      .map(FunctionConfiguration::version)
                      .collect(Collectors.toList());
          } catch (Exception e) {
              LOG.info("Exception caught in version/list", e);
              LOG.info("Exception cause in version/list", e.getCause());
              throw e;
          }
      }
  
      @Path("/event-source-mapping/create")
      @POST
      @Produces(MediaType.TEXT_PLAIN)
      public Response createEventSourceMapping(@QueryParam("functionName") String functionName) throws Exception {
          try {
              CreateEventSourceMappingResponse response = producerTemplate
                      .requestBodyAndHeader(
                              componentUri(functionName, Lambda2Operations.createEventSourceMapping),
                              null,
                              Lambda2Constants.EVENT_SOURCE_ARN,
                              eventSourceArn,
                              CreateEventSourceMappingResponse.class);
  
              return Response.created(new URI("https://camel.apache.org/")).entity(response.uuid()).build();
          } catch (Exception e) {
              LOG.info("Exception caught in event-source-mapping/create", e);
              LOG.info("Exception cause in event-source-mapping/create", e.getCause());
              throw e;
          }
      }
  
      @Path("/event-source-mapping/list")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public Map<String, String> listEventSourceMappings(@QueryParam("functionName") String functionName) {
          try {
              ListEventSourceMappingsResponse response = producerTemplate.requestBody(
                      componentUri(functionName, Lambda2Operations.listEventSourceMapping),
                      null,
                      ListEventSourceMappingsResponse.class);
              return response.eventSourceMappings().stream()
                      .collect(Collectors.toMap(EventSourceMappingConfiguration::uuid, EventSourceMappingConfiguration::state));
          } catch (Exception e) {
              LOG.info("Exception caught in event-source-mapping/list", e);
              LOG.info("Exception cause in event-source-mapping/list", e.getCause());
              throw e;
          }
      }
  
      @Path("/event-source-mapping/delete")
      @DELETE
      public void deleteEventSourceMapping(@QueryParam("eventSourceMappingUuid") String eventSourceMappingUuid) {
          try {
              producerTemplate.requestBodyAndHeader(
                      componentUri(null, Lambda2Operations.deleteEventSourceMapping),
                      null,
                      Lambda2Constants.EVENT_SOURCE_UUID,
                      eventSourceMappingUuid);
          } catch (Exception e) {
              LOG.info("Exception caught in event-source-mapping/delete", e);
              LOG.info("Exception cause in event-source-mapping/delete", e.getCause());
              throw e;
          }
      }
  
      private String componentUri(String functionName, Lambda2Operations operation) {
          return "aws2-lambda:" + functionName + "?operation=" + operation + "&useDefaultCredentialsProvider="
                  + isUseDefaultCredentials();
      }
  
      @Provider
      public static class ExceptionMapper implements jakarta.ws.rs.ext.ExceptionMapper<CamelExecutionException> {
          @Override
          public Response toResponse(CamelExecutionException exception) {
              if (exception.getCause() instanceof InvalidParameterValueException) {
                  return Response.status(Status.BAD_REQUEST).entity(exception.getCause().getMessage()).build();
              }
              return Response.status(Status.INTERNAL_SERVER_ERROR).entity(exception.getMessage()).build();
          }
      }
  
  }
  