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.SubscriptionDataResourceDocument;
import com.airlock.iam.admin.infrastructure.transfer.request.techclient.SubscriptionCreateRequest;
import com.airlock.iam.admin.infrastructure.transfer.response.techclient.SubscriptionData;
import com.airlock.iam.base.api.domain.model.identity.Identity;
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.PlainTechClientService;
import com.airlock.iam.techclient.admin.application.service.SubscriptionAttributes;
import com.airlock.iam.techclient.admin.application.service.SubscriptionService;
import com.airlock.iam.techclient.admin.domain.model.Subscription;
import com.airlock.iam.techclient.api.domain.model.TechnicalClient;
import com.airlock.iam.techclient.domain.model.identity.TechClientIdentityFactory;
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("/subscriptions")
public class SubscriptionsResource {
   private static final String SUBSCRIPTION_REQ_EXAMPLE = "{\n\t\"data\": {\n\t\t\"type\": \"subscription\",\n\t\t\"attributes\": {\n\t\t\t\"displayName\": \"My subscription description\"\n\t\t},\n\t\t\"relationships\": {\n\t\t\t\"tech-client\": {\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"type\": \"tech-client\",\n\t\t\t\t\t\"id\": \"2bb1df60-a074-4b7a-83ed-50c66f773151\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n";
   private final Provider<SubscriptionService> subscriptionService;
   private final Provider<PlainTechClientService> techClientService;
   private final RestMapper restMapper;
   private final ValidationToErrorMapper errorMapper;
   private final Provider<ExternalUriInfo> uriInfo;

   @Inject
   public SubscriptionsResource(
      Provider<SubscriptionService> subscriptionService,
      Provider<PlainTechClientService> techClientService,
      RestMapper restMapper,
      ValidationToErrorMapper errorMapper,
      Provider<ExternalUriInfo> uriInfo
   ) {
      this.subscriptionService = subscriptionService;
      this.techClientService = techClientService;
      this.restMapper = restMapper;
      this.errorMapper = errorMapper;
      this.uriInfo = uriInfo;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "201",
               description = "A new subscription has been created.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = SubscriptionDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = SubscriptionDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The related tech-client 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 tech-client already has a related subscription.",
               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 subscription for the specified tech-client.\n<p>\nNote that the request body must contain a resource identifier referring to an existing tech-client that the\nnew subscription should be assigned to. See examples for more details.\n</p>\n<div class=\"iam-resource-return\">The created subscription.</div>",
      tags = "Technical Clients",
      summary = "Create subscription"
   )
   @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 subscription attributes and a relationship to the associated tech-client.",
   content = {
         @Content(
            mediaType = "application/vnd.api+json",
            examples = @ExampleObject(
               name = "A subscription with relationships",
               value = "{\n\t\"data\": {\n\t\t\"type\": \"subscription\",\n\t\t\"attributes\": {\n\t\t\t\"displayName\": \"My subscription description\"\n\t\t},\n\t\t\"relationships\": {\n\t\t\t\"tech-client\": {\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"type\": \"tech-client\",\n\t\t\t\t\t\"id\": \"2bb1df60-a074-4b7a-83ed-50c66f773151\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
            )
         ),
         @Content(
            mediaType = "application/json",
            examples = @ExampleObject(
               name = "A subscription with relationships",
               value = "{\n\t\"data\": {\n\t\t\"type\": \"subscription\",\n\t\t\"attributes\": {\n\t\t\t\"displayName\": \"My subscription description\"\n\t\t},\n\t\t\"relationships\": {\n\t\t\t\"tech-client\": {\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"type\": \"tech-client\",\n\t\t\t\t\t\"id\": \"2bb1df60-a074-4b7a-83ed-50c66f773151\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
            )
         )
   }
) RequestDocument<SubscriptionCreateRequest> 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<TechnicalClient> techClientId = TechClientIdentityFactory.techClientId(validationResult.getResourceIdentifier().getId());
      if (((PlainTechClientService)this.techClientService.get()).findTechClient(techClientId).isEmpty()) {
         return ErrorResponse.notFound().build().toResponse();
      }

      SubscriptionAttributes attributes = this.extractAttributes(requestDocument);
      Subscription subscription = this.apiGatewaySubscriptionService().addSubscription(techClientId, attributes);
      ResourceDocument<SubscriptionData> document = this.restMapper.forEntity(subscription, SubscriptionData.class).toResourceDocument();
      URI resourceUri = Resources.uri((ExternalUriInfo)this.uriInfo.get(), SubscriptionsResource.class)
         .path(subscription.getId().asString())
         .build(new Object[0]);
      return Response.created(resourceUri).entity(document).build();
   }

   private Result validateRelationship(RequestDocument<SubscriptionCreateRequest> requestDocument) {
      RelationshipValidator<SubscriptionCreateRequest> validator = RelationshipValidator.builder().type("tech-client").build();
      return validator.validate(requestDocument);
   }

   private SubscriptionAttributes extractAttributes(RequestDocument<SubscriptionCreateRequest> requestDocument) {
      SubscriptionCreateRequest attributes = (SubscriptionCreateRequest)RequestDocumentAttributesMissingException.checkAttributesPresent(requestDocument);
      return SubscriptionAttributes.builder().displayName(attributes.getDisplayName()).build();
   }

   private SubscriptionService apiGatewaySubscriptionService() {
      return (SubscriptionService)this.subscriptionService.get();
   }
}
