package com.knecon.fforesight.service.layoutparser.processor;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Optional;

import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.SimplifiedText;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.ResearchDocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingRequest;
import com.knecon.fforesight.service.layoutparser.processor.python_api.model.image.ImageServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.python_api.model.table.TableServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.python_api.model.table.VisualLayoutParsingResponse;
import com.knecon.fforesight.tenantcommons.TenantContext;

import io.micrometer.observation.annotation.Observed;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class LayoutParsingStorageService {

    private final StorageService storageService;
    private final ObjectMapper objectMapper;

    @Observed(name = "LayoutParsingStorageService", contextualName = "get-origin-file")
    public File getOriginFile(String storageId) throws IOException {

        File tempFile = createTempFile("document", ".pdf");
        storageService.downloadTo(TenantContext.getTenantId(), storageId, tempFile);
        return tempFile;
    }


    @Observed(name = "LayoutParsingStorageService", contextualName = "get-viewer-doc-file")
    public Optional<File> getViewerDocFile(String storageId) throws IOException {

        if (!storageService.objectExists(TenantContext.getTenantId(), storageId)) {
            return Optional.empty();
        }
        File tempFile = createTempFile("viewerDocument", ".pdf");
        storageService.downloadTo(TenantContext.getTenantId(), storageId, tempFile);
        return Optional.of(tempFile);
    }


    public ImageServiceResponse getImagesFile(String storageId) throws IOException {

        try (InputStream inputStream = getObject(storageId)) {

            ImageServiceResponse imageServiceResponse = objectMapper.readValue(inputStream, ImageServiceResponse.class);
            inputStream.close();
            return imageServiceResponse;
        }
    }


    public TableServiceResponse getTablesFile(String storageId) throws IOException {

        try (var tableClassificationStream = getObject(storageId)) {

            TableServiceResponse tableServiceResponse = objectMapper.readValue(tableClassificationStream, TableServiceResponse.class);
            tableClassificationStream.close();
            return tableServiceResponse;
        }
    }

    public VisualLayoutParsingResponse getVisualLayoutParsingFile(String storageId) throws IOException {

        try (InputStream inputStream = getObject(storageId)) {
            VisualLayoutParsingResponse visualLayoutParsingResponse = objectMapper.readValue(inputStream, VisualLayoutParsingResponse.class);
            return visualLayoutParsingResponse;
        }
    }


    @Observed(name = "LayoutParsingStorageService", contextualName = "store-document-data")
    public void storeDocumentData(LayoutParsingRequest layoutParsingRequest, DocumentData documentData) {

        storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.structureFileStorageId(), documentData.getDocumentStructure());
        storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.textBlockFileStorageId(), documentData.getDocumentTextData());
        storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.positionBlockFileStorageId(), documentData.getDocumentPositions());
        storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.pageFileStorageId(), documentData.getDocumentPages());
    }


    public void storeResearchDocumentData(LayoutParsingRequest layoutParsingRequest, ResearchDocumentData researchDocumentData) {

        storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.researchDocumentStorageId(), researchDocumentData);
    }


    private File createTempFile(String filenamePrefix, String filenameSuffix) throws IOException {

        File tempFile = Files.createTempFile(filenamePrefix, filenameSuffix).toFile();
        setRWPermissionsOnlyForOwner(tempFile);

        return tempFile;
    }


    // We don't need to check the results of the permission setters below,
    // since we're manipulating a file we created ourselves.
    @SuppressWarnings({"ResultOfMethodCallIgnored", "squid:S899"})
    private void setRWPermissionsOnlyForOwner(File tempFile) {

        try {
            tempFile.setReadable(true, true);
            tempFile.setWritable(true, true);
            tempFile.setExecutable(false);
        } catch (SecurityException ex) {
            // This should never happen since we're creating a temp file ourselves.
            log.warn("Caught an exception during temp file creation.  This should not happend.  Check the code.", ex);
        }
    }


    @Observed(name = "LayoutParsingStorageService", contextualName = "store-simplified-text")
    public void storeSimplifiedText(LayoutParsingRequest layoutParsingRequest, SimplifiedText simplifiedText) {

        storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.simplifiedTextStorageId(), simplifiedText);
    }


    @SneakyThrows
    private InputStream getObject(String storageId) {

        File tempFile = File.createTempFile("temp", ".data");
        storageService.downloadTo(TenantContext.getTenantId(), storageId, tempFile);
        Path path = Paths.get(tempFile.getPath());
        return Files.newInputStream(path, StandardOpenOption.DELETE_ON_CLOSE);
    }


    @SneakyThrows
    @Observed(name = "LayoutParsingStorageService", contextualName = "store-viewer-document")
    public void storeViewerDocument(LayoutParsingRequest layoutParsingRequest, File out) {

        try (var in = new FileInputStream(out)) {
            storageService.storeObject(TenantContext.getTenantId(), layoutParsingRequest.viewerDocumentStorageId(), in);
        }
    }

}
