package com.airlock.iam.admin.infrastructure.resource.prot.techclient;

import ch.ergon.restal.jsonapi.document.data.ResourceDocument;
import ch.ergon.restal.jsonapi.document.request.RequestDocument;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.techclient.PlanUsageDataResourceDocument;
import com.airlock.iam.admin.infrastructure.transfer.request.techclient.PlanUsageCreateRequest;
import com.airlock.iam.admin.infrastructure.transfer.response.techclient.PlanUsageData;
import com.airlock.iam.base.api.domain.model.exception.EntityNotFoundException;
import com.airlock.iam.base.api.domain.model.exception.EntityNotUniqueException;
import com.airlock.iam.base.api.domain.model.identity.Identity;
import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.base.infrastructure.mapper.ValidationToErrorMapper;
import com.airlock.iam.base.infrastructure.mapper.entity.RestMapper;
import com.airlock.iam.base.infrastructure.validation.RelationshipValidator;
import com.airlock.iam.base.infrastructure.validation.RelationshipValidator.Result;
import com.airlock.iam.common.application.exception.RequestDocumentAttributesMissingException;
import com.airlock.iam.common.infrastructure.resource.Resources;
import com.airlock.iam.techclient.admin.application.service.PlanUsageAttributes;
import com.airlock.iam.techclient.admin.application.service.PlanUsageService;
import com.airlock.iam.techclient.admin.domain.model.PlanUsage;
import com.airlock.iam.techclient.admin.domain.model.Subscription;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import java.net.URI;

@Path("/plan-usages")
public class PlanUsagesResource {
   private static final String PLAN_USAGE_CREATE_REQ_EXAMPLE = "{\n\t\"data\": {\n\t\t\"type\": \"plan-usage\",\n\t\t\"attributes\": {\n\t\t\t\"name\": \"some-plan-usage\",\n\t\t\t\"rateLimit\": 100\n\t\t},\n\t\t\"relationships\": {\n\t\t\t\"subscription\": {\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"type\": \"subscription\",\n\t\t\t\t\t\"id\": \"bdd64913-48b1-46a5-ab87-d83b0f0634e3\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n";
   private final Provider<PlanUsageService> planUsageService;
   private final RestMapper restMapper;
   private final ValidationToErrorMapper errorMapper;
   private final Provider<ExternalUriInfo> uriInfo;

   @Inject
   public PlanUsagesResource(
      Provider<PlanUsageService> planUsageService, RestMapper restMapper, ValidationToErrorMapper errorMapper, Provider<ExternalUriInfo> uriInfo
   ) {
      this.planUsageService = planUsageService;
      this.restMapper = restMapper;
      this.uriInfo = uriInfo;
      this.errorMapper = errorMapper;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "201",
               description = "A new Plan-Usage has been created.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PlanUsageDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PlanUsageDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The related subscription does not exist.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse")),
                     @Content(mediaType = "application/json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse"))
               }
            ),
            @ApiResponse(
               responseCode = "409",
               description = "The associated subscription already has a Plan-Usage with the given name.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse")),
                     @Content(mediaType = "application/json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse"))
               }
            )
      }
   )
   @POST
   @Operation(
      description = "Creates a new Plan-Usage for the specified subscription.\n<p>\nNote that the request body must contain a resource identifier referring to an existing subscription that the\nnew Plan-Usage should be assigned to. See example request for more details.\n</p>\n<div class=\"iam-resource-return\">The created Plan-Usage.</div>",
      tags = "Technical Clients",
      summary = "Create plan usage"
   )
   @Consumes({"application/vnd.api+json", "application/json"})
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response create(
      @NotNull @Valid @RequestBody(
   description = "A request containing Plan-Usage attributes and a relationship to the associated subscription.",
   content = {
         @Content(
            mediaType = "application/vnd.api+json",
            examples = @ExampleObject(
               name = "Plan usage with reference to subscription",
               value = "{\n\t\"data\": {\n\t\t\"type\": \"plan-usage\",\n\t\t\"attributes\": {\n\t\t\t\"name\": \"some-plan-usage\",\n\t\t\t\"rateLimit\": 100\n\t\t},\n\t\t\"relationships\": {\n\t\t\t\"subscription\": {\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"type\": \"subscription\",\n\t\t\t\t\t\"id\": \"bdd64913-48b1-46a5-ab87-d83b0f0634e3\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
            )
         ),
         @Content(
            mediaType = "application/json",
            examples = @ExampleObject(
               name = "Plan usage with reference to subscription",
               value = "{\n\t\"data\": {\n\t\t\"type\": \"plan-usage\",\n\t\t\"attributes\": {\n\t\t\t\"name\": \"some-plan-usage\",\n\t\t\t\"rateLimit\": 100\n\t\t},\n\t\t\"relationships\": {\n\t\t\t\"subscription\": {\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"type\": \"subscription\",\n\t\t\t\t\t\"id\": \"bdd64913-48b1-46a5-ab87-d83b0f0634e3\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
            )
         )
   }
) RequestDocument<PlanUsageCreateRequest> requestDocument
   ) {
      if (requestDocument.getData().getId() != null) {
         return ErrorResponse.forbidden().log("Cannot accept client-generated ID.").build().toResponse();
      }

      Result validationResult = this.validateRelationship(requestDocument);
      if (validationResult.hasErrors()) {
         return this.errorMapper.mapToResponse(validationResult.getErrors());
      }

      Identity<Subscription> subscriptionId = Subscription.subscriptionId(validationResult.getResourceIdentifier().getId());
      PlanUsageAttributes attributes = this.extractAttributes(requestDocument);

      PlanUsage planUsage;
      try {
         planUsage = this.planUsageService().addPlanUsage(subscriptionId, attributes);
      } catch (EntityNotUniqueException e) {
         return this.errorMapper.mapToResponse(ValidationResult.singleError("name", GenericError.NOT_UNIQUE).getErrors());
      } catch (EntityNotFoundException e) {
         return ErrorResponse.notFound().build().toResponse();
      }

      ResourceDocument<PlanUsageData> document = this.restMapper.forEntity(planUsage, PlanUsageData.class).toResourceDocument();
      URI resourceUri = Resources.uri((ExternalUriInfo)this.uriInfo.get(), PlanUsagesResource.class).path(planUsage.getId().asString()).build(new Object[0]);
      return Response.created(resourceUri).entity(document).build();
   }

   private Result validateRelationship(RequestDocument<PlanUsageCreateRequest> requestDocument) {
      RelationshipValidator<PlanUsageCreateRequest> validator = RelationshipValidator.builder().type("subscription").build();
      return validator.validate(requestDocument);
   }

   private PlanUsageAttributes extractAttributes(RequestDocument<PlanUsageCreateRequest> requestDocument) {
      PlanUsageCreateRequest request = (PlanUsageCreateRequest)RequestDocumentAttributesMissingException.checkAttributesPresent(requestDocument);
      return PlanUsageAttributes.builder().name(request.getName()).rateLimit(request.getRateLimit()).build();
   }

   private PlanUsageService planUsageService() {
      return (PlanUsageService)this.planUsageService.get();
   }
}
