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

import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.document.request.RequestDocument;
import com.airlock.iam.admin.application.service.user.UserAdminService;
import com.airlock.iam.admin.domain.model.user.UserSpecificSettings;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.user.UserContextDataResourceDocument;
import com.airlock.iam.admin.infrastructure.mapper.prot.ContextDataDateConverter;
import com.airlock.iam.admin.infrastructure.service.user.UserIdResolver;
import com.airlock.iam.admin.infrastructure.transfer.request.user.UserContextDataRequest;
import com.airlock.iam.admin.infrastructure.transfer.response.user.UserContextData;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.infrastructure.mapper.ValidationToErrorMapper;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.application.exception.RequestDocumentAttributesMissingException;
import com.airlock.iam.common.infrastructure.resource.Resources;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.Map;

@Path("/users/{userId}/context-data")
public class UserContextDataResource {
   private static final String USER_CONTEXT_DATA_REQ_EXAMPLE = "{\n\t\"data\": {\n\t  \"attributes\": {\n\t\t\"birthdate\": null,\n\t\t\"company\": null,\n\t\t\"surname\": \"Newsurname\",\n\t\t\"self_registered\": false,\n\t\t\"valid\": true,\n\t\t\"inactivity_reminder_sent_date\": \"2021-10-27\",\n\t\t\"self_registration_date\": \"2021-08-15\",\n\t\t\"salutation\": \"\",\n\t\t\"language\": \"en\",\n\t\t\"mtan_number\": null\n\t  },\n\t  \"relationships\": {},\n\t  \"type\": \"user.context-data\"\n\t}\n}";
   private static final String USER_CONTEXT_DATA_REQ_EXAMPLE_NAME = "User context data";
   private final Provider<ExternalUriInfo> uriInfo;
   private final Provider<UserAdminService> userService;
   private final Provider<UserSpecificSettings> settings;
   private final Provider<ContextDataDateConverter> dateConverter;
   private final HelpdeskLogger helpdeskLogger;
   private final ValidationToErrorMapper errorMapper;
   private final Provider<UserIdResolver> userIdResolver;

   @Inject
   public UserContextDataResource(
      Provider<ExternalUriInfo> uriInfo,
      Provider<UserAdminService> userService,
      Provider<UserSpecificSettings> settings,
      Provider<ContextDataDateConverter> dateConverter,
      HelpdeskLogger helpdeskLogger,
      ValidationToErrorMapper errorMapper,
      Provider<UserIdResolver> userIdResolver
   ) {
      this.uriInfo = uriInfo;
      this.userService = userService;
      this.settings = settings;
      this.dateConverter = dateConverter;
      this.helpdeskLogger = helpdeskLogger;
      this.errorMapper = errorMapper;
      this.userIdResolver = userIdResolver;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The response contains the context data fields of this user.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = UserContextDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = UserContextDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "The user is not editable.",
               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 = "404",
               description = "The user 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"))
               }
            )
      }
   )
   @PATCH
   @Operation(
      tags = "User Context Data",
      description = "Updates selective context data fields of this user.\nPermission settings from 'Access Control' apply.\nIf profile items have been defined on a context data field,\nthe validation of the profile does also apply.\n<div class=\"iam-resource-return\">The context data fields after applying the update or an error document.</div>",
      summary = "Update user context data"
   )
   @Consumes({"application/vnd.api+json", "application/json"})
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response updateContextData(
      @PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam,
      @NotNull @Valid @RequestBody(
   description = "The document containing the attributes that should be updated. Omitted attributes are left unchanged. See example for details.",
   content = {
         @Content(
            mediaType = "application/vnd.api+json",
            examples = @ExampleObject(
               name = "User context data",
               value = "{\n\t\"data\": {\n\t  \"attributes\": {\n\t\t\"birthdate\": null,\n\t\t\"company\": null,\n\t\t\"surname\": \"Newsurname\",\n\t\t\"self_registered\": false,\n\t\t\"valid\": true,\n\t\t\"inactivity_reminder_sent_date\": \"2021-10-27\",\n\t\t\"self_registration_date\": \"2021-08-15\",\n\t\t\"salutation\": \"\",\n\t\t\"language\": \"en\",\n\t\t\"mtan_number\": null\n\t  },\n\t  \"relationships\": {},\n\t  \"type\": \"user.context-data\"\n\t}\n}"
            )
         ),
         @Content(
            mediaType = "application/json",
            examples = @ExampleObject(
               name = "User context data",
               value = "{\n\t\"data\": {\n\t  \"attributes\": {\n\t\t\"birthdate\": null,\n\t\t\"company\": null,\n\t\t\"surname\": \"Newsurname\",\n\t\t\"self_registered\": false,\n\t\t\"valid\": true,\n\t\t\"inactivity_reminder_sent_date\": \"2021-10-27\",\n\t\t\"self_registration_date\": \"2021-08-15\",\n\t\t\"salutation\": \"\",\n\t\t\"language\": \"en\",\n\t\t\"mtan_number\": null\n\t  },\n\t  \"relationships\": {},\n\t  \"type\": \"user.context-data\"\n\t}\n}"
            )
         )
   }
) RequestDocument<UserContextDataRequest> document
   ) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      ((UserSpecificSettings)this.settings.get()).failIfUserNotEditable();
      UserContextDataRequest attributes = (UserContextDataRequest)RequestDocumentAttributesMissingException.checkAttributesPresent(document);
      Map<String, Object> contextData = attributes.getValues();
      ((ContextDataDateConverter)this.dateConverter.get()).transformDates(contextData);
      Result<List<String>, List<ValidatorError>> result = ((UserAdminService)this.userService.get()).updateContextData(userId, contextData);
      if (result.isFailure()) {
         return this.errorMapper.withJsonApiAttributesMapper().mapToResponse((List)result.getFailure());
      }

      this.helpdeskLogger.log(userId, "Administrator ${authentee} updated context-data %s of user ${affectedUser}.", new Object[]{result.get()});
      UserAdminService.AdminappUser user = ((UserAdminService)this.userService.get()).loadUser(userId.getValue());
      UserContextData data = new UserContextData();
      data.setValues(user.filteredContextData());
      return Response.ok(
            ResourceObject.builder(userId.getValue(), data)
               .linkSelf(Resources.uri((ExternalUriInfo)this.uriInfo.get(), UserContextDataResource.class).build(new Object[]{userId}))
               .document()
         )
         .build();
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The response contains the context data fields of this user.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = UserContextDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = UserContextDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The user 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"))
               }
            )
      }
   )
   @GET
   @Operation(
      tags = "User Context Data",
      description = "Get configured context data fields of this user.\nPermission settings from 'Access Control' apply.\n<div class=\"iam-resource-return\">The context data fields.</div>",
      summary = "Get user context data"
   )
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response getContextData(@PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      this.helpdeskLogger.log(userId, "Administrator ${authentee} retrieved context-data of user ${affectedUser}.");
      return Response.ok(
            ResourceObject.builder(userId.getValue(), this.retrieveFilteredContextData(userId.getValue()))
               .linkSelf(Resources.uri((ExternalUriInfo)this.uriInfo.get(), UserContextDataResource.class).build(new Object[]{userId}))
               .document()
         )
         .build();
   }

   private UserContextData retrieveFilteredContextData(String username) {
      UserAdminService.AdminappUser user = ((UserAdminService)this.userService.get()).loadUser(username);
      UserContextData data = new UserContextData();
      data.setValues(user.filteredContextData());
      return data;
   }
}
