package com.airlock.iam.admin.infrastructure.service;

import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.document.error.ErrorDocument;
import ch.ergon.restal.jsonapi.parameter.FilterParameter;
import ch.ergon.restal.jsonapi.parameter.PageParameter;
import ch.ergon.restal.jsonapi.parameter.SortParameter;
import com.airlock.iam.admin.application.service.logs.LogSearchProperties;
import com.airlock.iam.admin.application.service.logs.LogService;
import com.airlock.iam.admin.application.service.logs.LogSort;
import com.airlock.iam.admin.domain.model.logs.LogFileInfo;
import com.airlock.iam.admin.infrastructure.transfer.response.LogsData;
import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.infrastructure.error.GenericRestErrorCode;
import com.airlock.iam.base.infrastructure.mapper.ValidationToErrorMapper;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.logs.Log;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.TimeZone;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;

public final class RestResponseLogsService {
   private static final ELogger LOG = ELoggerFactory.getLogger(RestResponseLogsService.class);
   private static final FastDateFormat FILE_DATE_FORMAT = FastDateFormat.getInstance("yyyyMMddHHmmss");
   private static final Pattern DOUBLE_EQUALS = Pattern.compile("==");
   private final Provider<LogService> logService;
   private final MapperFacade mapper;

   @Inject
   public RestResponseLogsService(Provider<LogService> logService, MapperFacade mapper) {
      this.logService = logService;
      this.mapper = mapper;
   }

   public Response getLogs(
      String logKey,
      FilterParameter filterParameter,
      SortParameter sortParameter,
      PageParameter pageParameter,
      RestResponseLogsService.LogResponseFormat responseFormat,
      int defaultPageSize
   ) {
      Optional<LogFileInfo> logFile = ((LogService)this.logService.get()).findLogFile(logKey);
      if (logFile.isEmpty()) {
         LOG.debug(String.format("No log file found for key %s", LogUtil.logSafeQuote(logKey)));
         ErrorDocument error = ValidationToErrorMapper.error(GenericRestErrorCode.VALIDATION_FAILED, GenericError.INVALID_VALUE, "logFile");
         return Response.status(Status.NOT_FOUND).entity(error).build();
      }

      LogSearchProperties search = this.createLogSearch(logFile.get(), filterParameter, sortParameter, pageParameter, defaultPageSize);

      return switch (responseFormat) {
         case JSON -> this.getJsonLogs(search);
         case PLAIN_TEXT -> this.getPlaintextLogs(search);
      };
   }

   private Response getJsonLogs(LogSearchProperties search) {
      if (!((LogService)this.logService.get()).areValidPatterns(search)) {
         ErrorDocument error = ValidationToErrorMapper.error(GenericRestErrorCode.VALIDATION_FAILED, GenericError.INVALID_VALUE, "filter[content]");
         return Response.status(Status.BAD_REQUEST).entity(error).build();
      } else {
         LogsData.LogsDataBuilder logs = LogsData.builder();
         List<Log> logLines = ((LogService)this.logService.get()).getLogs(search);
         logLines.stream().map(log -> (LogsData.LogData)this.mapper.map(log, LogsData.LogData.class)).forEach(logs::logLine);
         return Response.ok(ResourceObject.builder(search.logFile().key(), logs.build()).document()).build();
      }
   }

   private Response getPlaintextLogs(LogSearchProperties search) {
      List<String> logLines = ((LogService)this.logService.get()).getRawLogs(search);
      String logs = String.join("\r\n", logLines);
      return Response.ok()
         .header("Content-Type", "text/plain")
         .header("Content-Disposition", constructContentDispositionHeaderValue(search.logFile()))
         .header("Pragma", "")
         .header("Cache-Control", "")
         .entity(logs)
         .build();
   }

   private LogSearchProperties createLogSearch(
      LogFileInfo logFileInfo, FilterParameter filterParameter, SortParameter sortParameter, PageParameter pageParameter, int defaultPageSize
   ) {
      LogSearchProperties.LogSearchPropertiesBuilder builder = LogSearchProperties.builder()
         .logFile(logFileInfo)
         .numberOfMessages(limit(pageParameter, defaultPageSize))
         .filterPattern(filter(filterParameter));
      addSorting(builder, sortParameter);
      return builder.build();
   }

   private static int limit(PageParameter pageParameter, int defaultPageSize) {
      return pageParameter.getLimit() > 0 ? pageParameter.getLimit() : defaultPageSize;
   }

   private static String filter(FilterParameter filterParameter) {
      if (filterParameter.getFilters().isEmpty()) {
         return null;
      }

      String contentFilter = (String)filterParameter.getFilters().getFirst();
      String[] split = DOUBLE_EQUALS.split(contentFilter);
      return split.length > 1 ? split[1] : null;
   }

   private static void addSorting(LogSearchProperties.LogSearchPropertiesBuilder search, SortParameter sortParameter) {
      String criteria = sortParameter.getCriteria();
      search.sortBy(LogSort.NONE);
      if (StringUtils.isNotEmpty(criteria)) {
         boolean descending = criteria.charAt(0) == '-';
         if (descending) {
            criteria = criteria.substring(1);
         }

         search.sortBy(LogSort.ofString(criteria));
         search.sortAscending(!descending);
      }
   }

   private static String constructContentDispositionHeaderValue(LogFileInfo logFile) {
      String dateString = FILE_DATE_FORMAT.format(new Date());
      String timeZoneId = TimeZone.getDefault().getID().replace("/", "_");
      return String.format("attachment; filename=%s-timestampsIn-%s-%s.log", logFile.key(), timeZoneId, dateString);
   }

   public static final class HasPlaintextExportParameter implements Predicate<ExternalUriInfo> {
      public boolean test(ExternalUriInfo uriInfo) {
         return "plaintext".equals(uriInfo.getQueryParameters().getFirst("export"));
      }
   }

   public enum LogResponseFormat {
      JSON,
      PLAIN_TEXT;
   }
}
