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

import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.SaasMigrationStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.utils.TenantUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.TenantProvider;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
@DisallowConcurrentExecution
public class AutomaticAnalysisJob implements Job {

    private final AmqpAdmin amqpAdmin;
    private final FileManagementServiceSettings fileManagementServiceSettings;
    private final FileStatusService fileStatusService;
    private final TenantProvider tenantProvider;
    private final ObservationRegistry observationRegistry;
    private final SaasMigrationStatusPersistenceService saasMigrationStatusPersistenceService;

    @Setter
    private boolean schedulingStopped;

    private Set<String> stoppedTenants = new HashSet<>();


    @Override
    public void execute(JobExecutionContext jobExecutionContext) {

        if (fileManagementServiceSettings.isMigrateOnly() || schedulingStopped) {
            log.info("Skipping scheduling during migration");
            return;
        }

        tenantProvider.getTenants()
                .forEach(tenant -> {

                    if (!TenantUtils.isTenantReadyForPersistence(tenant)) {
                        log.info("[Tenant:{}] Skipping scheduling since tenant is not ready.", tenant.getTenantId());
                        return;
                    }

                    if (stoppedTenants.contains(tenant.getTenantId())) {
                        log.info("[Tenant:{}] Skipping scheduling as automatic reanalysis is disabled for tenant.", tenant.getTenantId());
                        return;
                    }

                    TenantContext.setTenantId(tenant.getTenantId());

                    if (!saasMigrationStatusPersistenceService.migrationFinishedForTenant()) {
                        log.info("[Tenant:{}] Skipping scheduling as there are files that require migration.", tenant.getTenantId());
                        return;
                    }

                    String queueName = MessagingConfiguration.REDACTION_REQUEST_QUEUE_PREFIX + "_" + tenant.getTenantId();
                    var redactionQueueInfo = amqpAdmin.getQueueInfo(queueName);
                    if (redactionQueueInfo != null) {
                        log.debug("[Tenant:{}] Checking queue status to see if background analysis can happen. Currently {} holds {} elements and has {} consumers",
                                  tenant.getTenantId(),
                                  queueName,
                                  redactionQueueInfo.getMessageCount(),
                                  redactionQueueInfo.getConsumerCount());
                        // only 1 file in queue
                        var consumerCount = redactionQueueInfo.getConsumerCount();
                        if (redactionQueueInfo.getMessageCount() <= consumerCount * 5) {
                            // queue up 5 files
                            List<FileModel> allStatuses = getAllRelevantStatuses();

                            allStatuses.sort(Comparator.comparing(FileModel::getLastUpdated));

                            Iterator<FileModel> allStatusesIterator = allStatuses.iterator();
                            log.debug("[Tenant:{}] Files that require reanalysis: {}", TenantContext.getTenantId(), allStatuses.size());

                            int queuedFiles = 0;

                            while (queuedFiles < (consumerCount * 5) && allStatusesIterator.hasNext()) {
                                FileModel next = allStatusesIterator.next();
                                // in case the file doesn't have numberOfPages set, we assume an average.

                                reanalyseFile(next);

                                queuedFiles++;
                            }
                        }

                    } else {
                        log.info("[Tenant:{}] Failed to obtain queue info for queue: {}", TenantContext.getTenantId(), queueName);
                    }

                });
    }


    private void reanalyseFile(FileModel file) {

        Observation.createNotStarted("AutomaticAnalysisJob", observationRegistry)
                .contextualName("scheduled-analysis")
                .highCardinalityKeyValue("dossierTemplateId", file.getDossierTemplateId())
                .highCardinalityKeyValue("dossierId", file.getDossierId())
                .highCardinalityKeyValue("fileId", file.getId())
                .lowCardinalityKeyValue("tenantId", TenantContext.getTenantId())
                .observe(() -> {

                    if (file.isFullAnalysisRequired()) {
                        log.info("[Tenant:{}] Queued file: {} for automatic full analysis! ", TenantContext.getTenantId(), file.getId());
                        fileStatusService.setStatusFullReprocess(file.getDossierId(), file.getId(), false, false, false);
                    } else if (file.isReanalysisRequired()) {
                        log.info("[Tenant:{}] Queued file: {} for automatic reanalysis! ", TenantContext.getTenantId(), file.getId());
                        fileStatusService.setStatusReprocess(file.getDossierId(), file.getId(), false);
                    } else if (file.isComponentReanalysisRequired()) {
                        log.info("[Tenant:{}] Queued file: {} for automatic reanalysis of components only! ", TenantContext.getTenantId(), file.getId());
                        fileStatusService.setStatusReprocessForComponentsOnly(file.getDossierId(), file.getId(), false);
                    }
                });
    }


    private List<FileModel> getAllRelevantStatuses() {

        return fileStatusService.getAllRelevantStatusesForReanalysisScheduler();
    }


    public void stopForTenant(String tenantId) {

        log.info("Stopping automatic analysis for tenant {}", tenantId);
        stoppedTenants.add(tenantId);
    }


    public void startForTenant(String tenantId) {

        log.info("Starting automatic analysis for tenant {}", tenantId);
        stoppedTenants.remove(tenantId);
    }

}
