/*******************************************************************************
 * Copyright 2018 572682
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 ******************************************************************************/
package us.dot.its.jpo.ode.storage;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.UrlResource;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;

import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import us.dot.its.jpo.ode.OdeProperties;
import us.dot.its.jpo.ode.eventlog.EventLogger;

public class FileSystemStorageServiceTest {

    @Mocked
    OdeProperties mockOdeProperties;

    @Before
    public void setupOdePropertiesExpectations() {
        new Expectations() {
            {
                mockOdeProperties.getUploadLocationRoot();
                result = anyString;
                mockOdeProperties.getUploadLocationObuLog();
                result = anyString;
            }
        };
    }

    @Test
    public void shouldConstruct(@Mocked final Logger mockLogger, @Mocked LoggerFactory unused) {

        FileSystemStorageService testFileSystemStorageService = new FileSystemStorageService(mockOdeProperties);

        assertNotNull(testFileSystemStorageService.getRootLocation());

    }

    @Test @Ignore
    public void storeShouldThrowExceptionUnknownType(@Mocked MultipartFile mockMultipartFile) {

        String unknownType = "test123";

        try {
            new FileSystemStorageService(mockOdeProperties).store(mockMultipartFile, unknownType);
            fail("Expected StorageException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown", StorageException.class, e.getClass());
            assertTrue("Incorrect message received", e.getMessage().startsWith("File type unknown:"));
        }

        new Verifications() {
            {
                EventLogger.logger.info(anyString, any, any);
            }
        };

    }

    @Test @Ignore
    public void storeShouldTryToResolveBsmFilename(@Mocked MultipartFile mockMultipartFile) {

        String testType = "obulog";

        new Expectations() {
            {
                mockMultipartFile.getOriginalFilename();
                result = anyString;
                mockMultipartFile.isEmpty();
                result = true;
            }
        };

        try {
            new FileSystemStorageService(mockOdeProperties).store(mockMultipartFile, testType);
            fail("Expected StorageException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown", StorageException.class, e.getClass());
            assertTrue("Incorrect message received", e.getMessage().startsWith("File is empty:"));
        }

        new Verifications() {
            {
                EventLogger.logger.info(anyString);
            }
        };
    }

    @Test @Ignore
    public void storeShouldThrowAnErrorEmptyFile(@Mocked MultipartFile mockMultipartFile) {

        String testType = "obulog";

        new Expectations() {
            {
                mockMultipartFile.getOriginalFilename();
                result = anyString;
                mockMultipartFile.isEmpty();
                result = true;
            }
        };

        try {
            new FileSystemStorageService(mockOdeProperties).store(mockMultipartFile, testType);
            fail("Expected StorageException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown", StorageException.class, e.getClass());
            assertTrue("Incorrect message received", e.getMessage().startsWith("File is empty:"));
        }

        new Verifications() {
            {
                EventLogger.logger.info(anyString);
            }
        };
    }

    @Test @Ignore
    public void storeShouldRethrowDeleteException(@Mocked MultipartFile mockMultipartFile, @Mocked Files unused) {

        String testType = "obulog";

        new Expectations() {
            {
                mockMultipartFile.getOriginalFilename();
                result = anyString;
                mockMultipartFile.isEmpty();
                result = false;
            }
        };

        try {
            new Expectations() {
                {
                    Files.deleteIfExists((Path) any);
                    result = new IOException("testException123");
                }
            };
        } catch (IOException e1) {
            fail("Unexpected exception on Files.deleteIfExists() expectation creation");
        }

        try {
            new FileSystemStorageService(mockOdeProperties).store(mockMultipartFile, testType);
            fail("Expected StorageException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown", StorageException.class, e.getClass());
            assertTrue("Incorrect message received", e.getMessage().startsWith("Failed to delete existing file:"));
        }

        new Verifications() {
            {
                EventLogger.logger.info("Deleting existing file: {}", any);
                EventLogger.logger.info("Failed to delete existing file: {} ", any);
            }
        };
    }

    @Test @Ignore
    public void storeShouldRethrowCopyException(@Mocked MultipartFile mockMultipartFile, @Mocked Files unusedFiles,
            @Mocked final Logger mockLogger, @Mocked LoggerFactory unusedLogger, @Mocked InputStream mockInputStream) {

        String testType = "obulog";

        try {
            new Expectations() {
                {
                    mockMultipartFile.getOriginalFilename();
                    result = anyString;

                    mockMultipartFile.isEmpty();
                    result = false;

                    mockMultipartFile.getInputStream();
                    result = mockInputStream;

                    Files.deleteIfExists((Path) any);

                    Files.copy((InputStream) any, (Path) any);
                    result = new IOException("testException123");
                }
            };
        } catch (IOException e1) {
            fail("Unexpected exception creating test Expectations: " + e1);
        }

        try {
            new FileSystemStorageService(mockOdeProperties).store(mockMultipartFile, testType);
            fail("Expected StorageException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown", StorageException.class, e.getClass());
            assertTrue("Incorrect message received",
                    e.getMessage().startsWith("Failed to store file in shared directory"));
        }

        new Verifications() {
            {
                EventLogger.logger.info("Copying file {} to {}", anyString, (Path) any);
                EventLogger.logger.info("Failed to store file in shared directory {}", (Path) any);
            }
        };
    }

    @Test @Ignore
    public void loadAllShouldRethrowException(/**@Mocked Files unused**/) {
        try {
            new Expectations() {
                {
                    Files.walk((Path) any, anyInt);// .filter(null).map(null);
                    result = new IOException("testException123");
                }
            };
        } catch (IOException e) {
            fail("Unexpected exception creating Expectations: " + e);
        }

        try {
            new FileSystemStorageService(mockOdeProperties).loadAll();
            fail("Expected StorageException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown", StorageException.class, e.getClass());
            assertTrue("Incorrect message received", e.getMessage().startsWith("Failed to read files stored in"));
        }

        new Verifications() {
            {
                EventLogger.logger.info("Failed to read files stored in {}", (Path) any);
            }
        };
    }

    @Test @Ignore
    public void loadAsResourceShouldThrowExceptionWhenFileNotExists(
          @Mocked Path mockRootPath,
          @Mocked Path mockResolvedPath,
          @Mocked UrlResource mockUrlResource) {

        try {
            new Expectations() {
                {
                    mockRootPath.resolve(anyString);
                    result = mockResolvedPath;

                    mockResolvedPath.toUri();

                    new UrlResource((URI) any); 
                    result = mockUrlResource;
                    
                    mockUrlResource.exists();
                    result = false;
                }
            };
        } catch (MalformedURLException e) {
            fail("Unexpected exception creating Expectations");
        }

        try {
            FileSystemStorageService testFileSystemStorageService = new FileSystemStorageService(mockOdeProperties);
            testFileSystemStorageService.setRootLocation(mockRootPath);
            testFileSystemStorageService.loadAsResource("testFile");
            fail("Expected StorageFileNotFoundException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown.", StorageFileNotFoundException.class, e.getClass());
            assertTrue("Incorrect exception message", e.getMessage().startsWith("Could not read file:"));
        }
    }

    @Test @Ignore
    public void loadAsResourceShouldThrowExceptionWhenFileNotReadable(@Mocked Path mockRootPath,
            @Mocked Path mockResolvedPath, @Mocked UrlResource mockUrlResource) {

        try {
            new Expectations() {
                {
                    mockRootPath.resolve(anyString);
                    result = mockResolvedPath;

                    mockResolvedPath.toUri();

                    new UrlResource((URI) any);
                    result = mockUrlResource;

                    mockUrlResource.exists();
                    result = true;
                    
                    mockUrlResource.isReadable();
                    result = false;
                }
            };
        } catch (MalformedURLException e) {
            fail("Unexpected exception creating Expectations");
        }

        try {
            FileSystemStorageService testFileSystemStorageService = new FileSystemStorageService(mockOdeProperties);
            testFileSystemStorageService.setRootLocation(mockRootPath);
            testFileSystemStorageService.loadAsResource("testFile");
            fail("Expected StorageFileNotFoundException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown.", StorageFileNotFoundException.class, e.getClass());
            assertTrue("Incorrect exception message", e.getMessage().startsWith("Could not read file:"));
        }
    }

    @Test @Ignore
    public void loadAsResourceShouldRethrowMalformedURLException(@Mocked Path mockRootPath,
            @Mocked Path mockResolvedPath, @Mocked UrlResource mockUrlResource) {

        try {
            new Expectations() {
                {
                    mockRootPath.resolve(anyString);
                    result = mockResolvedPath;

                    mockResolvedPath.toUri();

                    new UrlResource((URI) any);
                    result = new MalformedURLException("testException123");
                }
            };
        } catch (MalformedURLException e) {
            fail("Unexpected exception creating Expectations");
        }

        try {
            FileSystemStorageService testFileSystemStorageService = new FileSystemStorageService(mockOdeProperties);
            testFileSystemStorageService.setRootLocation(mockRootPath);
            testFileSystemStorageService.loadAsResource("testFile");
            fail("Expected StorageFileNotFoundException");
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown.", StorageFileNotFoundException.class, e.getClass());
            assertTrue("Incorrect exception message", e.getMessage().startsWith("Could not read file:"));
        }
    }

    @Test @Ignore
    public void loadAsResourceShouldReturnResource(@Mocked Path mockRootPath, @Mocked Path mockResolvedPath,
            @Mocked UrlResource mockUrlResource) {

        try {
            new Expectations() {
                {
                    mockRootPath.resolve(anyString);
                    result = mockResolvedPath;

                    mockResolvedPath.toUri();

                    new UrlResource((URI) any);
                    result = mockUrlResource;

                    mockUrlResource.exists();
                    result = true;
                    mockUrlResource.isReadable();
                    result = true;
                }
            };
        } catch (MalformedURLException e) {
            fail("Unexpected exception creating Expectations");
        }

        try {
            FileSystemStorageService testFileSystemStorageService = new FileSystemStorageService(mockOdeProperties);
            testFileSystemStorageService.setRootLocation(mockRootPath);
            assertEquals(UrlResource.class, testFileSystemStorageService.loadAsResource("testFile").getClass());
        } catch (Exception e) {
            fail("Unexpected exception: " + e);
        }
    }

    @Test @Ignore
    public void initShouldCreateDirectories(@Mocked final Files unused) {

        new FileSystemStorageService(mockOdeProperties).init();

        try {
            new Verifications() {
                {
                    Files.createDirectory((Path) any);
                    times = 1;
                }
            };
        } catch (IOException e) {
            fail("Unexpected exception in verifications block: " + e);
        }
    }

    @Test @Ignore
    public void initShouldRethrowAndLogException(@Mocked final Files unused) {

        try {
            new Expectations() {
                {
                    Files.createDirectory((Path) any);
                    result = new IOException("testException123");
                }
            };
        } catch (IOException e) {
            fail("Unexpected exception in expectations block: " + e);
        }

        try {
            new FileSystemStorageService(mockOdeProperties).init();
        } catch (Exception e) {
            assertEquals("Incorrect exception thrown.", StorageException.class, e.getClass());
            assertTrue("Incorrect exception message",
                    e.getMessage().startsWith("Failed to initialize storage service"));
        }

        new Verifications() {
            {
                EventLogger.logger.info("Failed to initialize storage service {}", (Path) any);
            }
        };
    }

    @Test @Ignore
    public void deleteAllShouldDeleteRecursivelyAndLog(@Mocked final FileSystemUtils unused) {

        new FileSystemStorageService(mockOdeProperties).deleteAll();

        new Verifications() {
            {
                FileSystemUtils.deleteRecursively((File) any);
                EventLogger.logger.info("Deleting {}", (Path) any);
            }
        };

    }

}
