package com.airlock.iam.login.rest.infrastructure.resource.pub;

import ch.ergon.restal.jsonapi.document.data.CollectionDocument;
import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.parameter.FilterParameter.Condition;
import ch.ergon.restal.jsonapi.parameter.FilterParameter.Disjunction;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.base.infrastructure.validation.DocumentedFilterParameter;
import com.airlock.iam.common.api.infrastructure.annotation.DisableSystemAvailabilityCheck;
import com.airlock.iam.common.api.infrastructure.annotation.RequiresWritableSession;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.login.application.service.maintenancemessage.LoginMaintenanceMessageService;
import com.airlock.iam.login.domain.model.maintenancemessage.CurrentMaintenanceMessage;
import com.airlock.iam.login.rest.infrastructure.generated.transfer.shared.response.MaintenanceMessageDataCollectionDocument;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.MaintenanceMessageData;
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 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.Produces;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import ma.glasnost.orika.MapperFacade;

@DisableSystemAvailabilityCheck
@Path("/maintenance-messages")
public class MaintenanceMessageResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(MaintenanceMessageResource.class);
   private final Provider<Optional<LoginMaintenanceMessageService>> maintenanceMessageService;
   private final MapperFacade mapper;

   @Inject
   public MaintenanceMessageResource(Provider<Optional<LoginMaintenanceMessageService>> maintenanceMessageService, MapperFacade mapper) {
      this.maintenanceMessageService = maintenanceMessageService;
      this.mapper = mapper;
   }

   @ApiResponse(
      responseCode = "200",
      description = "Returns the list of current maintenance messages. Can be empty.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = MaintenanceMessageDataCollectionDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = MaintenanceMessageDataCollectionDocument.class))
      }
   )
   @Operation(
      tags = "Maintenance Messages",
      description = "Retrieves the current maintenance message(s) (if any) for the specified location(s). The maintenance messages can be filtered by location. For each location at most one message is retrieved.\nIf no filter is present, the current maintenance message for every location is returned.\n<p>\nThe values of the 'text' and 'language' attributes are determined as follows:\n<ul>\n<li>\nIf the maintenance message is available in a translation as specified by the Accept-Language header:<br>\nthe text attribute contains the message in the desired language. The language attribute is set to the desired language.\n</li>\n<li>\nIf the maintenance message is not available in a translation as specified by the Accept-Language header and a translation to the default language is available:<br>\nthe text attribute contains the message in the default language. The language attribute is set to the default language.\n</li>\n<li>\nIf the maintenance message is not available in a translation as specified by the Accept-Language header and no translation to the default language is available:<br>\nthe response does not contain the text and language attributes.\n</li>\n<li>\nIf no Accept-Language header is set and a translation to the default language is available:<br>\nthe text attribute contains the message in the default language. The language attribute is set to the default language.\n</li>\n<li>\nIf no Accept-Language header is set and no translation to the default language is available:<br>\nthe response does not contain the text and language attributes.\n</li>\n</ul>\n</p>\n<p>\n<ul>\n<li>\n<b>filter</b>: Restricts the set of all messages to those matching the location specified by the filter condition.\nThe allowed filter parameter is <tt>location</tt>. It is a String parameter. OR filtering is supported.\nAND filtering is supported by specifying multiple filter clauses with location parameter.\n</li>\n</ul>\n</p>\n<p>If multiple maintenance messages are active for the same location, the message with the longest remaining validity\nperiod is returned (latest 'validTo' date).</p>\n<div class=\"iam-resource-return\">The current maintenance message(s).</div>",
      summary = "List messages"
   )
   @GET
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @RequiresWritableSession
   public Response retrieveMaintenanceMessages(
      @Parameter(description = "Restricts the collection of maintenance messages to the supplied locations.") @BeanParam DocumentedFilterParameter filterParameter
   ) {
      List<Disjunction> filter = filterParameter.parse();
      Set<String> filteredProperties = filter.stream().flatMap(d -> d.getConditions().stream()).<String>map(Condition::getProperty).collect(Collectors.toSet());
      if (filteredProperties.stream().anyMatch(s -> !"location".equals(s))) {
         LOG.info("Invalid filter parameter: Found " + filteredProperties + ", but only \"location\" is allowed.");
         return ErrorResponse.badRequest().build().toResponse();
      }

      if (((Optional)this.maintenanceMessageService.get()).isEmpty()) {
         return ErrorResponse.notImplemented().build().toResponse();
      }

      List<LoginMaintenanceMessageService.LocationFilter> filterList = filter.stream().map(MaintenanceMessageResource::disjunctionToLocationFilter).toList();
      List<CurrentMaintenanceMessage> messages = ((LoginMaintenanceMessageService)((Optional)this.maintenanceMessageService.get()).orElseThrow())
         .retrieveMaintenanceMessages(filterList);
      CollectionDocument<MaintenanceMessageData> document = new CollectionDocument();

      for (CurrentMaintenanceMessage currentMaintenanceMessage : messages) {
         MaintenanceMessageData to = (MaintenanceMessageData)this.mapper.map(currentMaintenanceMessage, MaintenanceMessageData.class);
         ResourceObject<MaintenanceMessageData> resource = ResourceObject.builder(currentMaintenanceMessage.getId(), to).build();
         document.add(resource);
      }

      return Response.ok().entity(document).build();
   }

   private static LoginMaintenanceMessageService.LocationFilter disjunctionToLocationFilter(Disjunction disjunction) {
      LoginMaintenanceMessageService.LocationFilter.LocationFilterBuilder builder = LoginMaintenanceMessageService.LocationFilter.builder();

      for (Condition condition : disjunction.getConditions()) {
         switch (condition.getOperator()) {
            case EQUALS:
               builder.locationEquals(condition.getValue());
               break;
            case CONTAINS:
               builder.locationContains(condition.getValue());
               break;
            default:
               throw new IllegalStateException("Unknown filter condition operator enum type");
         }
      }

      return builder.build();
   }
}
