package com.iqser.red.service.persistence.management.v1.processor.service.download;

import static com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration.X_ERROR_INFO_HEADER;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.pdftron.redaction.v1.api.model.RedactionMessage;
import com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadStatusValue;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;

@Slf4j
@Service
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class RedactionDlqMessageReceiver {

    ObjectMapper objectMapper;
    DownloadStatusPersistenceService downloadStatusPersistenceService;
    DownloadPreparationService downloadPreparationService;
    FileManagementServiceSettings settings;
    RetryTemplate retryTemplate;
    RabbitTemplate rabbitTemplate;

    private final static int MAX_ERROR_CAUSE_LENGTH = 1024;


    @RabbitHandler
    @RabbitListener(queues = MessagingConfiguration.PDFTRON_DLQ)
    public void receive(Message message) throws IOException {

        // Download packages will always be build on the pod that runs the scheduler, as we plan to replace the entire logic by downloading directory direct from storage, we leave it like that for now.

        RedactionMessage redactionMessage = objectMapper.readValue(message.getBody(), RedactionMessage.class);
        String errorCause = message.getMessageProperties().getHeader(X_ERROR_INFO_HEADER);
        if (errorCause == null) {
            errorCause = "Unkown error occured in pdftron service!";
        } else if (errorCause.length() > MAX_ERROR_CAUSE_LENGTH) {
            errorCause = errorCause.substring(0, MAX_ERROR_CAUSE_LENGTH - 1);
        }

        var fileEntryOptional = downloadPreparationService.getRedactionFileStatusEntry(redactionMessage.getDownloadId(), redactionMessage.getFileId());

        if (fileEntryOptional.isPresent()) {
            var entry = fileEntryOptional.get();
            int numErrors = entry.getProcessingErrorCounter() + 1;
            if (numErrors >= settings.getMaxRedactionFileErrorRetries()) {
                setDownloadFailed(redactionMessage.getDownloadId(), errorCause);
                downloadPreparationService.clearRedactionStatusEntries(redactionMessage.getDownloadId());
            } else {
                downloadPreparationService.increaseProcessingErrorCounter(redactionMessage, numErrors);
                rabbitTemplate.convertAndSend(MessagingConfiguration.PDFTRON_REQUEST_EXCHANGE, TenantContext.getTenantId(), message);
            }
        } else {
            downloadPreparationService.markFileAsProcessingError(redactionMessage);
            rabbitTemplate.convertAndSend(MessagingConfiguration.PDFTRON_REQUEST_EXCHANGE, TenantContext.getTenantId(), message);
        }
    }


    public void setDownloadFailed(String downloadId, String errorCause) {

        retryTemplate.execute(retryContext -> {
            log.warn("Retrying {} time to set FAILED status for downloadJob with storageId: {}", retryContext.getRetryCount(), downloadId);
            downloadStatusPersistenceService.updateStatus(downloadId, DownloadStatusValue.FAILED, errorCause);
            return null;
        });
    }

}
