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

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.application.configuration.AdminappRestConfigSpec;
import com.airlock.iam.admin.application.service.permission.AdminappPermissionsService;
import com.airlock.iam.admin.application.service.settings.AdminappSettingsService;
import com.airlock.iam.admin.application.service.settings.UserDetailSettings;
import com.airlock.iam.admin.application.service.token.generic.GenericTokenUiService;
import com.airlock.iam.admin.application.system.AdminappPage;
import com.airlock.iam.admin.application.system.PageUrlMappings;
import com.airlock.iam.admin.domain.model.AdminappSettings;
import com.airlock.iam.admin.domain.model.realm.RealmAdministrationSettings;
import com.airlock.iam.admin.domain.model.token.generic.GenericTokenIdentifier;
import com.airlock.iam.admin.infrastructure.service.user.UserIdResolver;
import com.airlock.iam.admin.infrastructure.transfer.response.AdminData;
import com.airlock.iam.admin.infrastructure.transfer.response.AdminappData;
import com.airlock.iam.admin.infrastructure.transfer.response.AdminappLicenseOverview;
import com.airlock.iam.admin.infrastructure.transfer.response.AdminappSystem;
import com.airlock.iam.admin.infrastructure.transfer.response.ui.generic.GenericTokenUiData;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.plugin.framework.application.service.license.LicenseManager;
import com.airlock.iam.system.property.IamSystemProperties;
import com.airlock.iam.system.version.CurrentVersion;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
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.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import java.util.Optional;
import java.util.TimeZone;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.StringUtils;

@Path("/adminapp")
public class AdminappResource {
   private final Provider<AdminappSettings> adminappSettings;
   private final Provider<Optional<RealmAdministrationSettings>> realmAdministrationSettings;
   private final Provider<AdminappRestConfigSpec> adminappRestConfigSpec;
   private final Provider<AdminappPermissionsService> permissionService;
   private final Provider<LicenseManager> licenseManager;
   private final Provider<AdminappSettingsService> settingsService;
   private final Provider<GenericTokenUiService> genericTokenUiService;
   private final Provider<CurrentAdministrator> currentAdministrator;
   private final MapperFacade mapperFacade;
   private final Provider<UserIdResolver> userIdResolver;

   @Inject
   public AdminappResource(
      Provider<AdminappSettings> adminappSettings,
      Provider<Optional<RealmAdministrationSettings>> realmAdministrationSettings,
      Provider<AdminappRestConfigSpec> adminappRestConfigSpec,
      Provider<AdminappPermissionsService> permissionService,
      Provider<LicenseManager> licenseManager,
      Provider<AdminappSettingsService> settingsService,
      Provider<GenericTokenUiService> genericTokenUiService,
      Provider<CurrentAdministrator> currentAdministrator,
      MapperFacade mapperFacade,
      Provider<UserIdResolver> userIdResolver
   ) {
      this.adminappSettings = adminappSettings;
      this.realmAdministrationSettings = realmAdministrationSettings;
      this.adminappRestConfigSpec = adminappRestConfigSpec;
      this.permissionService = permissionService;
      this.licenseManager = licenseManager;
      this.settingsService = settingsService;
      this.genericTokenUiService = genericTokenUiService;
      this.currentAdministrator = currentAdministrator;
      this.mapperFacade = mapperFacade;
      this.userIdResolver = userIdResolver;
   }

   @ApiResponses(
      {
            @ApiResponse(
               description = "Retrieved general Adminapp settings.",
               responseCode = "200",
               content = @Content(mediaType = "application/json", schema = @Schema(implementation = AdminappData.class))
            ),
            @ApiResponse(
               description = "Bad Request",
               responseCode = "400",
               content = @Content(mediaType = "application/json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse"))
            )
      }
   )
   @GET
   @Operation(extensions = @Extension(properties = @ExtensionProperty(name = "x-internal", value = "true")))
   @Produces("application/json")
   public Response generalSettings() {
      if (this.isUnauthorized()) {
         return this.unauthorized();
      }

      AdminappPermissionsService.SystemPermissions permissions = ((AdminappPermissionsService)this.permissionService.get()).getPermissions();
      boolean canManageLicense = permissions.getAdminappPermissions().isPageAccessible(AdminappPage.LICENSE);
      AdminappLicenseOverview.AdminappLicenseOverviewBuilder license = AdminappLicenseOverview.builder();
      if (canManageLicense) {
         license.licensedUsers((Integer)((LicenseManager)this.licenseManager.get()).getLicense().getNrOfLicensedUsers().orElse(null));
      }

      AdminappSystem system = AdminappSystem.builder()
         .instance(IamSystemProperties.getInstanceName())
         .startPage(permissions.getStartPage().map(PageUrlMappings::getPageUrl).orElse(PageUrlMappings.getPageUrl(AdminappPage.LICENSE)))
         .recoveryMode(canManageLicense && ((AdminappSettings)this.adminappSettings.get()).recoveryMode())
         .oldConfigEditorEnabled(IamSystemProperties.isOldConfigEditorEnabled())
         .version(CurrentVersion.getDisplayString())
         .license(license.build())
         .defaultLanguage(((AdminappSettings)this.adminappSettings.get()).defaultLanguage())
         .timeZoneId(TimeZone.getDefault().getID())
         .supportedLanguages(((AdminappSettings)this.adminappSettings.get()).validLanguages())
         .settings(((AdminappSettingsService)this.settingsService.get()).getSettings())
         .skin(((AdminappSettings)this.adminappSettings.get()).skin())
         .instanceTag(((AdminappSettings)this.adminappSettings.get()).instanceTag())
         .afterLogoutUrl(((AdminappSettings)this.adminappSettings.get()).afterLogoutUrl())
         .maxPageSize(((AdminappRestConfigSpec)this.adminappRestConfigSpec.get()).getMaxPageSize())
         .build();
      AdminappData adminappData = AdminappData.builder().admin(this.adminData()).permissions(permissions.getAdminappPermissions()).system(system).build();
      return Response.ok(adminappData).build();
   }

   @ApiResponse(
      description = "Retrieved generic user detail settings.",
      responseCode = "200",
      content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDetailSettings.class))
   )
   @GET
   @Operation(extensions = @Extension(properties = @ExtensionProperty(name = "x-internal", value = "true")))
   @Produces("application/json")
   @Path("/user-detail")
   public Response userDetailSettings() {
      return this.userDetailSettingsInternal(Optional.empty());
   }

   @ApiResponse(
      description = "Retrieved user detail settings for specified user ID.",
      responseCode = "200",
      content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDetailSettings.class))
   )
   @GET
   @Operation(extensions = @Extension(properties = @ExtensionProperty(name = "x-internal", value = "true")))
   @Produces("application/json")
   @Path("/user-detail/{userId}")
   public Response userDetailSettings(@PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      return this.userDetailSettingsInternal(Optional.of(userId));
   }

   @ApiResponse(
      description = "Retrieved generic token controller UI for specified ID.",
      responseCode = "200",
      content = @Content(mediaType = "application/json", schema = @Schema(implementation = GenericTokenUiData.class))
   )
   @GET
   @Operation(extensions = @Extension(properties = @ExtensionProperty(name = "x-internal", value = "true")))
   @Produces("application/json")
   @Path("/ui/tokens/generic/{id}/")
   public Response genericTokenControllerUi(@PathParam("id") String id) {
      return this.isUnauthorized()
         ? this.unauthorized()
         : ((GenericTokenUiService)this.genericTokenUiService.get())
            .getUserSpecificGenericTokenUi(new GenericTokenIdentifier(id))
            .map(ui -> (GenericTokenUiData)this.mapperFacade.map(ui, GenericTokenUiData.class))
            .map(data -> Response.ok(data).build())
            .orElse(ErrorResponse.notFound().build().toResponse());
   }

   private AdminData adminData() {
      return AdminData.builder()
         .name(((CurrentAdministrator)this.currentAdministrator.get()).getName())
         .providedName(((CurrentAdministrator)this.currentAdministrator.get()).getProvidedName())
         .roles(((CurrentAdministrator)this.currentAdministrator.get()).getRoles())
         .realm(
            ((Optional)this.realmAdministrationSettings.get())
               .map(RealmAdministrationSettings::adminRealmContextDataName)
               .flatMap(c -> ((CurrentAdministrator)this.currentAdministrator.get()).contextData(c))
               .map(String.class::cast)
               .filter(StringUtils::isNotBlank)
               .orElse(null)
         )
         .build();
   }

   private Response userDetailSettingsInternal(Optional<UserId> userId) {
      if (this.isUnauthorized()) {
         return this.unauthorized();
      } else {
         return userId.isPresent()
            ? Response.ok(((AdminappSettingsService)this.settingsService.get()).getUserDetailSettings(userId.get())).build()
            : Response.ok(((AdminappSettingsService)this.settingsService.get()).getGenericUserSettings()).build();
      }
   }

   private boolean isUnauthorized() {
      AdminappPermissionsService.SystemPermissions permissions = ((AdminappPermissionsService)this.permissionService.get()).getPermissions();
      return permissions.getStartPage().isEmpty();
   }

   private Response unauthorized() {
      return ErrorResponse.unauthorized().header("WWW-Authenticate", "None").log(this.getUnauthorizedReason()).build().toResponse();
   }

   private String getUnauthorizedReason() {
      AdminappPermissionsService.SystemPermissions permissions = ((AdminappPermissionsService)this.permissionService.get()).getPermissions();
      return permissions.getStartPage().isEmpty()
         ? "The current administrator has no permissions to access any of the Adminapp pages. This happens if the administrator is lacking the required roles."
         : "Access is not allowed";
   }
}
