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

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.AnalyseStatus;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.websocket.WebsocketService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.BulkLocalResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FoundTerm;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualAnnotationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddCommentRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

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

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

    public static final String SEARCH_BULK_LOCAL_TERM_RESPONSE_LISTENER = "search-bulk-local-term-response-listener";

    ObjectMapper objectMapper;

    DossierManagementService dossierManagementService;
    ManualRedactionService manualRedactionService;
    AuditPersistenceService auditPersistenceService;
    FileStatusPersistenceService fileStatusPersistenceService;
    WebsocketService webSocketService;
    EntityLogMongoService entityLogMongoService;


    @RabbitHandler
    @RabbitListener(id = SEARCH_BULK_LOCAL_TERM_RESPONSE_LISTENER)
    public void receive(Message message) throws IOException {

        BulkLocalResponse response = objectMapper.readValue(message.getBody(), new TypeReference<>() {
        });

        if (message.getMessageProperties().isRedelivered()) {
            throw new AmqpRejectAndDontRequeueException(String.format("Error during last processing of bulk local term search for file %s in dossier %s, do not retry.",
                                                                      response.getFileId(),
                                                                      response.getDossierId()));
        }

        receive(response);

    }


    public void receive(BulkLocalResponse response) {

        Authentication authentication = new UsernamePasswordAuthenticationToken(response.getUserId(), null, null);
        SecurityContextImpl securityContext = new SecurityContextImpl();
        securityContext.setAuthentication(authentication);
        SecurityContextHolder.setContext(securityContext);

        var dossier = dossierManagementService.getDossierById(response.getDossierId(), false, false);

        var foundValue = response.getFoundTerms()
                .get(0).value();
        List<List<Position>> positionsList = response.getFoundTerms()
                .stream()
                .map(FoundTerm::positions)
                .collect(Collectors.toList());
        var entityLogEntries = entityLogMongoService.findDictionaryEntityLogEntriesByValueAndPositions(response.getDossierId(), response.getFileId(), foundValue, positionsList);

        Set<AddRedactionRequestModel> addRedactionRequests = response.getFoundTerms()
                .stream()
                .map(term -> AddRedactionRequestModel.builder()
                        .type(response.getType())
                        .value(term.value())
                        .reason(response.getReason())
                        .legalBasis(response.getLegalBasis())
                        .positions(convertPositions(term.positions()))
                        .section(response.getSection())
                        .comment(response.getComment() == null ? null : new AddCommentRequestModel(response.getComment()))
                        .sourceId(getAnnotationId(entityLogEntries, term))
                        .build())
                .collect(Collectors.toSet());

        log.info("Received manual redaction requests for file {} in dossier {} after term search", response.getFileId(), dossier.getId());

        List<ManualAnnotationResponse> manualAnnotationResponses = manualRedactionService.addAddRedaction(response.getDossierId(),
                                                                                                          response.getFileId(),
                                                                                                          addRedactionRequests,
                                                                                                          dossier);
        manualAnnotationResponses.forEach(manualAddResponse -> auditPersistenceService.audit(AuditRequest.builder()
                                                                                                     .userId(KeycloakSecurity.getUserId())
                                                                                                     .objectId(response.getFileId())
                                                                                                     .category(AuditCategory.DOCUMENT.name())
                                                                                                     .message("Manual annotation was added.")
                                                                                                     .details(Map.of("dossierId",
                                                                                                                     response.getDossierId(),
                                                                                                                     "fileId",
                                                                                                                     response.getFileId(),
                                                                                                                     "annotationId",
                                                                                                                     manualAddResponse.getAnnotationId()))
                                                                                                     .build()));

        fileStatusPersistenceService.updateProcessingStatus(response.getFileId(), ProcessingStatus.PROCESSED);
        int analysisVersion = fileStatusPersistenceService.getStatus(response.getFileId()).getAnalysisVersion();
        webSocketService.sendAnalysisEvent(response.getDossierId(), response.getFileId(), AnalyseStatus.BULK_LOCAL_REDACTIONS_PROCESSED, analysisVersion);
        log.info("Finished adding all manual redactions, the file is processed again");

    }


    private String getAnnotationId(Set<EntityLogEntry> entityLogEntries, FoundTerm term) {

        var optionalEntry = entityLogEntries.stream()
                .filter(entry -> entry.getPositions().equals(term.positions()))
                .findFirst();
        if (optionalEntry.isPresent()) {
            return optionalEntry.get().getId();
        }
        return null;
    }


    private List<Rectangle> convertPositions(List<Position> positions) {

        return positions.stream()
                .map(position -> Rectangle.builder().page(position.getPageNumber()).height(position.h()).width(position.w()).topLeftX(position.x()).topLeftY(position.y()).build())
                .toList();
    }

}
