package org.innovateuk.ifs.file.controller;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.innovateuk.ifs.commons.error.Error;
import org.innovateuk.ifs.commons.rest.RestErrorResponse;
import org.innovateuk.ifs.commons.rest.RestResult;
import org.innovateuk.ifs.commons.service.ServiceFailure;
import org.innovateuk.ifs.commons.service.ServiceResult;
import org.innovateuk.ifs.file.resource.FileAndContents;
import org.innovateuk.ifs.file.resource.FileEntryResource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StreamUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.BiFunction;
import java.util.function.Supplier;

import static org.innovateuk.ifs.commons.error.CommonFailureKeys.FILES_EXCEPTION_WHILE_RETRIEVING_FILE;
import static org.innovateuk.ifs.commons.service.ServiceResult.serviceFailure;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.OK;

/**
 * Helpful utility methods for dealing with file uploads within Controllers
 */
@Slf4j
public class FileControllerUtils {

    /**
     * A convenience method to create a response to a file download request, given a supplier of a FileAndContents
     */
    public static ResponseEntity<Object> handleFileDownload(Supplier<ServiceResult<? extends FileAndContents>> fileResultSupplier) {

        try {

            ServiceResult<? extends FileAndContents> getFileResult = fileResultSupplier.get();

            return getFileResult.handleSuccessOrFailure(
                    failure -> {
                        RestErrorResponse errorResponse = new RestErrorResponse(failure.getErrors());
                        return new ResponseEntity<>(errorResponse, errorResponse.getStatusCode());
                    },
                    fileResult -> {
                        FileEntryResource fileEntry = fileResult.getFileEntry();
                        Supplier<InputStream> inputStreamSupplier = fileResult.getContentsSupplier();
                        InputStream inputStream = inputStreamSupplier.get();
                        ByteArrayResource inputStreamResource = new ByteArrayResource(StreamUtils.copyToByteArray(inputStream));
                        HttpHeaders httpHeaders = new HttpHeaders();
                        httpHeaders.setContentLength(fileEntry.getFilesizeBytes());
                        httpHeaders.setContentType(MediaType.parseMediaType(fileEntry.getMediaType()));
                        return new ResponseEntity<>(inputStreamResource, httpHeaders, OK);
                    }
            );

        } catch (Exception e) {

            log.error("Error retrieving file", e);
            return new ResponseEntity<>(new RestErrorResponse(new Error(FILES_EXCEPTION_WHILE_RETRIEVING_FILE)), INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * A convenience method to process a file upload request given a standard pattern of header validation and processing,
     * given a function that can perform the actual file upload.
     * <p>
     * The {@link MediaTypesContext} generic type refers to a context from which a valid set of Media Types can be established,
     * as used by the supplied {@link FilesizeAndTypeFileValidator}.
     */
    public static <T, MediaTypesContext> RestResult<T> handleFileUpload(String contentType, String contentLength, String originalFilename,
                                                                 FilesizeAndTypeFileValidator<MediaTypesContext> fileValidator, MediaTypesContext mediaTypeContext, long maxFileSizeBytes,
                                                                 HttpServletRequest request, BiFunction<FileHeaderAttributes, Supplier<InputStream>, ServiceResult<T>> uploadFileActionFn) {

        return handleFileUploadWithServiceCall(contentType, contentLength, originalFilename, fileValidator, mediaTypeContext, maxFileSizeBytes, request, uploadFileActionFn).toPostCreateResponse();
    }

    /**
     * A convenience method to process a file upload (as an update) request given a standard pattern of header
     * validation and processing, given a function that can perform the actual file upload
     */
    public <MediaTypesContext> RestResult<Void> handleFileUpdate(String contentType, String contentLength, String originalFilename,
                                                                 FilesizeAndTypeFileValidator<MediaTypesContext> fileValidator, MediaTypesContext mediaTypesContext, long maxFileSizeBytes,
                                                                 HttpServletRequest request, BiFunction<FileHeaderAttributes, Supplier<InputStream>, ServiceResult<?>> uploadFileActionFn) {

        BiFunction<FileHeaderAttributes, Supplier<InputStream>, ServiceResult<Void>> voidReturner = (fileAttributes, inputStreamSupplier) ->
                uploadFileActionFn.apply(fileAttributes, inputStreamSupplier).andOnSuccessReturnVoid();

        return handleFileUploadWithServiceCall(contentType, contentLength, originalFilename, fileValidator, mediaTypesContext, maxFileSizeBytes, request, voidReturner).toPutResponse();
    }

    private static <T, MediaTypesContext> ServiceResult<T> handleFileUploadWithServiceCall(String contentType, String contentLength, String originalFilename, FilesizeAndTypeFileValidator<MediaTypesContext> fileValidator, MediaTypesContext mediaTypesContext, long maxFileSizeBytes, HttpServletRequest request, BiFunction<FileHeaderAttributes, Supplier<InputStream>, ServiceResult<T>> uploadFileActionFn) {

        return fileValidator.validateFileHeaders(contentType, contentLength, originalFilename, mediaTypesContext, maxFileSizeBytes).handleSuccessOrFailure(
                failure -> failureView(request, failure),
                success -> uploadFileActionFn.apply(success, inputStreamSupplier(request))
        );
    }

    private static Supplier<InputStream> inputStreamSupplier(HttpServletRequest request) {
        return () -> inputStream(request);
    }

    private static InputStream inputStream(HttpServletRequest request) {
            try {
                byte[] array = IOUtils.toByteArray(request.getInputStream());
                return new ByteArrayInputStream(array);
            } catch (IOException e) {
                log.error("Unable to open an input stream from request", e);
                throw new RuntimeException("Unable to open an input stream from request", e);
            }
    }

    private static <T> ServiceResult<T> failureView(HttpServletRequest request, ServiceFailure result) {
        // Must be called on failure to wait for upload to finish otherwise we get a connection reset error
        inputStream(request);
        return serviceFailure(result.getErrors());
    }
}

@@ -1527,23 +1527,16 @@
  public 
-static 
 Response
@@ -3540,23 +3540,16 @@
  public 
-static 
 %3CT, Medi
@@ -6363,22 +6363,14 @@
     
-    
 try %7B%0A
-    
     
@@ -6443,36 +6443,32 @@
 ));%0A            
-    
 return new ByteA
@@ -6495,28 +6495,24 @@
 y);%0A        
-  
-  
 %7D catch (IOE
@@ -6521,28 +6521,24 @@
 eption e) %7B%0A
-    
             
@@ -6610,20 +6610,16 @@
         
- 
-   
 throw ne
@@ -6680,36 +6680,32 @@
 m request%22, e);%0A
-    
         %7D%0A    %7D%0A

revert file util changes