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

import ch.ergon.restal.jsonapi.LinkObject;
import ch.ergon.restal.jsonapi.Links;
import ch.ergon.restal.jsonapi.document.data.ResourceDocument;
import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.parameter.Pager;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.LogsDataResourceDocument;
import com.airlock.iam.admin.infrastructure.service.LogsSearchResult;
import com.airlock.iam.admin.infrastructure.service.UserTrailLogsRestResponseService;
import com.airlock.iam.admin.infrastructure.service.user.UserIdResolver;
import com.airlock.iam.admin.infrastructure.transfer.response.LogsData;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.infrastructure.validation.SanitizedPageParameter;
import com.airlock.iam.base.infrastructure.validation.ValidPageParameter;
import com.airlock.iam.common.infrastructure.resource.Resources;
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 jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.ws.rs.BeanParam;
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 jakarta.ws.rs.core.UriBuilder;
import java.util.HashMap;
import java.util.Map;
import ma.glasnost.orika.MapperFacade;

@Path("/users/{userId}/logs")
public final class UserLogsResource {
   private static final String USER_TRAIL_KEY = "logViewer.usertrail.name";
   private final Provider<UserTrailLogsRestResponseService> userTrailLogsService;
   private final MapperFacade mapper;
   private final Provider<ExternalUriInfo> uriInfo;
   private final Provider<UserIdResolver> userIdResolver;

   @Inject
   public UserLogsResource(
      Provider<ExternalUriInfo> uriInfo, Provider<UserTrailLogsRestResponseService> logService, MapperFacade mapper, Provider<UserIdResolver> userIdResolver
   ) {
      this.uriInfo = uriInfo;
      this.userTrailLogsService = logService;
      this.mapper = mapper;
      this.userIdResolver = userIdResolver;
   }

   private static Map<String, LinkObject> createPageLinks(LogsSearchResult searchResult, Pager pager, int pageLimit, int pageOffset) {
      Map<String, LinkObject> linkMap = new HashMap<>();
      linkMap.put("self", pager.buildLink(pageOffset, pageLimit));
      if (pageOffset > 0) {
         linkMap.put("first", pager.buildLink(0, pageLimit));
         linkMap.put("prev", pager.buildLink(Math.max(0, pageOffset - pageLimit), pageLimit));
      }

      if (searchResult.hasMoreLogsInSource()) {
         linkMap.put("next", pager.buildLink(pageOffset + pageLimit, pageLimit));
      }

      return linkMap;
   }

   @ApiResponse(
      responseCode = "200",
      description = "The response contains logs found.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = LogsDataResourceDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = LogsDataResourceDocument.class))
      }
   )
   @GET
   @Operation(
      description = "Retrieve the user trail log of a particular user.\nTo limit the number of logs returned, use 'page[limit]=...'.\n<div class=\"iam-resource-return\">The logs from the User Trail Log database.</div>",
      extensions = @Extension(properties = @ExtensionProperty(name = "x-internal", value = "true")),
      tags = "Logs",
      summary = "Get user log"
   )
   @Path("/usertrail")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response getUsertrailLogs(
      @PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam,
      @Parameter(description = "Restricts the logs to those on the requested page. Currently, only 'page[limit]' is supported.") @ValidPageParameter @BeanParam SanitizedPageParameter pageParameter
   ) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      int pageLimit = pageParameter.getLimit();
      int pageOffset = pageParameter.getOffset();
      LogsSearchResult searchResult = ((UserTrailLogsRestResponseService)this.userTrailLogsService.get()).getLogs(userId, pageLimit, pageOffset);
      return Response.ok(this.prepareLogPagingResult(searchResult, pageLimit, pageOffset)).build();
   }

   private ResourceDocument<LogsData> prepareLogPagingResult(LogsSearchResult searchResult, int pageLimit, int pageOffset) {
      ResourceDocument<LogsData> document = new ResourceDocument(ResourceObject.builder("logViewer.usertrail.name", this.prepareLogsData(searchResult)).build());
      document.setLinks(this.preparePageLinks(searchResult, pageLimit, pageOffset));
      return document;
   }

   private LogsData prepareLogsData(LogsSearchResult searchResult) {
      LogsData.LogsDataBuilder logBuilder = LogsData.builder();
      searchResult.logs().forEach(log -> logBuilder.logLine((LogsData.LogData)this.mapper.map(log, LogsData.LogData.class)));
      return logBuilder.build();
   }

   private Links preparePageLinks(LogsSearchResult searchResult, int pageLimit, int pageOffset) {
      Links links = new Links();
      Pager pager = new Pager(this.prepareNormalizedUserQueryLink());
      links.setLinks(createPageLinks(searchResult, pager, pageLimit, pageOffset));
      return links;
   }

   private UriBuilder prepareNormalizedUserQueryLink() {
      return Resources.uri((ExternalUriInfo)this.uriInfo.get(), UserLogsResource.class)
         .replacePath(((ExternalUriInfo)this.uriInfo.get()).getAbsolutePath().getPath())
         .replaceQuery(((ExternalUriInfo)this.uriInfo.get()).getRequestUri().getQuery())
         .replaceQueryParam("page[limit]", new Object[0])
         .replaceQueryParam("page[offset]", new Object[0]);
   }
}
