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

import com.airlock.iam.admin.application.service.token.password.AdminPasswordAdminService;
import com.airlock.iam.admin.infrastructure.transfer.request.user.tokens.PasswordChangeRequest;
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.mapper.ValidationToErrorMapper;
import com.airlock.iam.common.application.service.password.PasswordPolicyValidator;
import com.airlock.iam.common.application.service.password.command.ChangePassword;
import com.airlock.iam.common.infrastructure.exception.IamRestErrorCode;
import com.airlock.iam.core.misc.authen.PasswordService.PasswordChangeContext;
import com.airlock.iam.core.misc.authen.result.PasswordChangeResult;
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.Schema;
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.security.Principal;

@Path("/my/password")
public class AdminPasswordAdminResource {
   private final HelpdeskLogger helpdeskLogger;
   private final Provider<AdminPasswordAdminService> adminPasswordServiceProvider;
   private final Provider<Principal> principalProvider;
   private final PasswordPolicyValidator passwordPolicyValidator = new PasswordPolicyValidator();
   private final Provider<ExternalUriInfo> uriInfo;

   @Inject
   public AdminPasswordAdminResource(
      Provider<AdminPasswordAdminService> adminPasswordServiceProvider,
      Provider<ExternalUriInfo> uriInfo,
      HelpdeskLogger logger,
      Provider<Principal> principalProvider
   ) {
      this.adminPasswordServiceProvider = adminPasswordServiceProvider;
      this.uriInfo = uriInfo;
      this.helpdeskLogger = logger;
      this.principalProvider = principalProvider;
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "The password change was successful."),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>EXISTING_PASSWORD_WRONG</td>\n<td>The existing password is wrong.</td>\n</tr>\n<tr>\n<td>PASSWORD_POLICY_VIOLATED</td>\n<td>The new password violates the password policy. See <a href=\"#passwordPolicyDetailCodes\">password policy violation detail codes</a>.</td>\n</tr>\n</table>\n</div>\n",
               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 = "Changes the administrator's password.\n<div class=\"iam-resource-return\">Information about the password change.</div>",
      tags = "Password",
      summary = "Change admin password"
   )
   @Path("/change")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response changePassword(@Parameter(description = "Contains the necessary data to change the password.") @NotNull @Valid PasswordChangeRequest request) {
      PasswordChangeResult result = ((AdminPasswordAdminService)this.adminPasswordServiceProvider.get())
         .changePassword(
            ChangePassword.builder()
               .userId(this.getAuthUser())
               .oldPassword(request.getOldPassword())
               .newPassword(request.getNewPassword())
               .context(PasswordChangeContext.VOLUNTARY)
               .build()
         );
      ValidationResult validationResult = this.passwordPolicyValidator.validate(result);
      if (!validationResult.isValid()) {
         ValidationToErrorMapper mapper = new ValidationToErrorMapper(this.uriInfo);
         if (result.isExistingPasswordWrong()) {
            this.helpdeskLogger
               .log(
                  this.getAuthUser(),
                  "Administrator ${authentee} could not change his password because the provided old password did not match the existing one."
               );
            return mapper.useErrorCode(IamRestErrorCode.EXISTING_PASSWORD_WRONG).mapToResponse(validationResult.getErrors());
         } else {
            this.helpdeskLogger
               .log(this.getAuthUser(), "Administrator ${authentee} could not change his password because the new password violates a password policy.");
            return mapper.useErrorCode(IamRestErrorCode.PASSWORD_POLICY_VIOLATED).mapToResponse(validationResult.getErrors());
         }
      } else {
         this.helpdeskLogger.log(this.getAuthUser(), "Administrator ${authentee} changed his password.");
         return Response.noContent().build();
      }
   }

   private String getAuthUser() {
      return ((Principal)this.principalProvider.get()).getName();
   }
}
