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


import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.imported.ImportedLegalBases;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.imported.ImportedRedactionsPerPage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ComponentLogMongoService;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.iqser.red.service.redaction.v1.server.data.DocumentStructureProto;
import com.iqser.red.service.redaction.v1.server.data.DocumentStructureWrapper;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
@SuppressWarnings("PMD")
public class FileManagementStorageService {

    private final StorageService storageService;

    private final EntityLogMongoService entityLogMongoService;
    private final ComponentLogMongoService componentLogMongoService;


    @SneakyThrows
    public byte[] getStoredObjectBytes(String dossierId, String fileId, FileType fileType) {

        InputStream inputStream = getObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, fileType));
        byte[] storedObjectBytes = IOUtils.toByteArray(inputStream);
        inputStream.close();
        return storedObjectBytes;
    }


    @SneakyThrows
    public byte[] getStoredObjectBytes(String storageId) {

        InputStream inputStream = getObject(TenantContext.getTenantId(), storageId);
        byte[] storedObjectBytes = IOUtils.toByteArray(inputStream);
        inputStream.close();
        return storedObjectBytes;
    }


    @SneakyThrows
    public InputStream getObject(String tenantId, String storageId) {

        File tempFile = File.createTempFile("temp", ".data");
        storageService.downloadTo(tenantId, storageId, tempFile);
        return new BufferedInputStream(Files.newInputStream(Paths.get(tempFile.getPath()), StandardOpenOption.DELETE_ON_CLOSE));
    }


    public void storeObject(String dossierId, String fileId, FileType fileType, InputStream stream) {

        storageService.storeObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, fileType), stream);
    }


    public <T> void storeJSONObject(String dossierId, String fileId, FileType fileType, T jsonObject) {

        storageService.storeJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, fileType), jsonObject);
    }


    public void storeObject(String storageId, InputStream data) {

        storageService.storeObject(TenantContext.getTenantId(), storageId, data);
    }


    public EntityLog getEntityLog(String dossierId, String fileId) {

        return entityLogMongoService.findEntityLogByDossierIdAndFileId(dossierId, fileId)
                .orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));

    }


    public EntityLog getEntityLogExcludeTypes(String dossierId, String fileId, List<String> excludedTypes) {

        return entityLogMongoService.findEntityLogWithoutExcludedEntryTypes(dossierId, fileId, excludedTypes)
                .orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));

    }


    public EntityLog getEntityLogWithEntriesSinceAnalysisNumber(String dossierId, String fileId, Integer analysisNumber) {

        return entityLogMongoService.findEntityLogWithEntriesSinceAnalysisNumber(dossierId, fileId, analysisNumber)
                .orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));

    }


    public EntityLog getEntityLogStartingWithEntriesOnPages(String dossierId, String fileId, List<Integer> pageNumbers) {

        return entityLogMongoService.findEntityLogWithEntriesOnPages(dossierId, fileId, pageNumbers)
                .orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));

    }


    private static String getEntityLogNotFoundErrorMessage(String dossierId, String fileId) {

        return String.format("EntityLog does not exist for Dossier ID \"%s\" and File ID \"%s\"!", dossierId, fileId);
    }


    @SneakyThrows
    public void saveEntityLog(String dossierId, String fileId, EntityLog entityLog) {

        entityLogMongoService.saveEntityLog(dossierId, fileId, entityLog);
    }


    @SneakyThrows
    public void insertEntityLog(String dossierId, String fileId, EntityLog entityLog) {

        entityLogMongoService.insertEntityLog(dossierId, fileId, entityLog);
    }


    public ComponentLog getComponentLog(String dossierId, String fileId) {

        return componentLogMongoService.findComponentLogByDossierIdAndFileId(dossierId, fileId)
                .orElseThrow(() -> new NotFoundException(String.format("ComponentLog does not exist for Dossier ID \"%s\" and File ID \"%s\"!", dossierId, fileId)));
    }


    @SneakyThrows
    public void saveComponentLog(String dossierId, String fileId, ComponentLog componentLog) {

        componentLogMongoService.saveComponentLog(dossierId, fileId, componentLog);
    }


    @SneakyThrows
    public void insertComponentLog(String dossierId, String fileId, ComponentLog componentLog) {

        componentLogMongoService.insertComponentLog(dossierId, fileId, componentLog);
    }


    public boolean entityLogExists(String dossierId, String fileId) {

        return entityLogMongoService.entityLogDocumentExists(dossierId, fileId);
    }


    public boolean componentLogExists(String dossierId, String fileId) {

        return componentLogMongoService.componentLogDocumentExists(dossierId, fileId);
    }


    public SectionGrid getSectionGrid(String dossierId, String fileId) {

        try {
            return storageService.readJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.SECTION_GRID), SectionGrid.class);
        } catch (StorageObjectDoesNotExist e) {
            log.debug("SectionGrid not available.");
            throw new NotFoundException("SectionGrid does not exist");
        } catch (Exception e) {
            throw new RuntimeException("Could not convert SectionGrid", e);
        }
    }


    public ImportedRedactionsPerPage getImportedRedactions(String dossierId, String fileId) {

        try {
            return storageService.readJSONObject(TenantContext.getTenantId(),
                                                 StorageIdUtils.getStorageId(dossierId, fileId, FileType.IMPORTED_REDACTIONS),
                                                 ImportedRedactionsPerPage.class);
        } catch (StorageObjectDoesNotExist e) {
            throw new NotFoundException("ImportedRedactions does not exist");
        } catch (Exception e) {
            throw new RuntimeException("Could not convert ImportedRedactions", e);
        }
    }


    public ImportedLegalBases getImportedLegalBases(String dossierId, String fileId) {

        try {
            return storageService.readJSONObject(TenantContext.getTenantId(),
                                                 StorageIdUtils.getStorageId(dossierId, fileId, FileType.IMPORTED_LEGAL_BASES),
                                                 ImportedLegalBases.class);
        } catch (StorageObjectDoesNotExist e) {
            throw new NotFoundException("ImportedLegalBases does not exist");
        } catch (Exception e) {
            throw new RuntimeException("Could not convert ImportedLegalBases", e);
        }
    }


    public com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.imported.ImportedRedactions getOldImportedRedactions(String dossierId, String fileId) {

        try {
            return storageService.readJSONObject(TenantContext.getTenantId(),
                                                 StorageIdUtils.getStorageId(dossierId, fileId, FileType.IMPORTED_REDACTIONS),
                                                 com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.imported.ImportedRedactions.class);
        } catch (StorageObjectDoesNotExist e) {
            throw new NotFoundException("ImportedRedactions does not exist");
        } catch (Exception e) {
            throw new RuntimeException("Could not convert ImportedRedactions", e);
        }
    }


    public boolean objectExists(String dossierId, String fileId, String fileName, String fileExtension) {

        return storageService.objectExists(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, fileName, fileExtension));
    }


    public boolean objectExists(String dossierId, String fileId, FileType origin) {

        return storageService.objectExists(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, origin));
    }


    public boolean objectExists(String storageId) {

        return storageService.objectExists(TenantContext.getTenantId(), storageId);
    }


    public void deleteObject(String dossierId, String fileId, FileType fileType) {

        storageService.deleteObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, fileType));
    }


    public void deleteObject(String dossierId, String fileId, String fileName, String fileExtension) {

        storageService.deleteObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, fileName, fileExtension));
    }


    public void deleteDocumentAndNerObjects(String dossierId, String fileId) {

        deleteObject(dossierId, fileId, FileType.DOCUMENT_STRUCTURE);
        deleteObject(dossierId, fileId, FileType.DOCUMENT_TEXT);
        deleteObject(dossierId, fileId, FileType.DOCUMENT_PAGES);
        deleteObject(dossierId, fileId, FileType.DOCUMENT_POSITION);

        deleteObject(dossierId, fileId, FileType.DOCUMENT_STRUCTURE.name(), FileType.DOCUMENT_STRUCTURE_OLD.getExtension());
        deleteObject(dossierId, fileId, FileType.DOCUMENT_TEXT.name(), FileType.DOCUMENT_TEXT_OLD.getExtension());
        deleteObject(dossierId, fileId, FileType.DOCUMENT_PAGES.name(), FileType.DOCUMENT_PAGES_OLD.getExtension());
        deleteObject(dossierId, fileId, FileType.DOCUMENT_POSITION.name(), FileType.DOCUMENT_POSITION_OLD.getExtension());

        deleteObject(dossierId, fileId, FileType.DOCUMENT_CHUNKS);

        deleteObject(dossierId, fileId, FileType.NER_ENTITIES);
        deleteObject(dossierId, fileId, FileType.LLM_NER_ENTITIES);
        deleteObject(dossierId, fileId, FileType.AZURE_NER_ENTITIES);

    }


    public void deleteAllObjects(String dossierId, String fileId) {

        deleteObject(dossierId, fileId, FileType.VIEWER_DOCUMENT);

        deleteObject(dossierId, fileId, FileType.REDACTION_LOG);
        deleteEntityLog(dossierId, fileId);

        deleteDocumentAndNerObjects(dossierId, fileId);

        deleteObject(dossierId, fileId, FileType.IMAGE_INFO);
        deleteObject(dossierId, fileId, FileType.FIGURE);
        deleteObject(dossierId, fileId, FileType.TABLES);

    }


    public void deleteEntityLog(String dossierId, String fileId) {

        entityLogMongoService.deleteEntityLog(dossierId, fileId);
    }


    public void deleteComponentLog(String dossierId, String fileId) {

        componentLogMongoService.deleteComponentLog(dossierId, fileId);
    }


    public void deleteObject(String storageId) {

        storageService.deleteObject(TenantContext.getTenantId(), storageId);
    }


    public DocumentStructureWrapper getDocumentStructure(String dossierId, String fileId) {

        return new DocumentStructureWrapper(storageService.readProtoObject(TenantContext.getTenantId(),
                                                                           StorageIdUtils.getStorageId(dossierId, fileId, FileType.DOCUMENT_STRUCTURE),
                                                                           DocumentStructureProto.DocumentStructure.parser()));
    }

}
