package com.atlassian.bitbucket.mesh.app;

import com.atlassian.bitbucket.mesh.config.StorageConfig;
import com.atlassian.bitbucket.mesh.junit.TemporaryDirectory;
import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class StorageManagerTest {

    private static final LocalDateTime oldDateTime =
            LocalDateTime.of(1999, 9, 30, 10, 30, 22);

    @Rule
    public final TemporaryDirectory tempDir = new TemporaryDirectory();

    @Mock
    private StorageConfig storageConfig;
    @Mock
    private StorageLayout storageLayout;
    private DefaultStorageManager storageManager;
    private Path tmpDir;

    @Before
    public void setup() throws IOException {
        tmpDir = tempDir.newFolder("tmp");

        when(storageConfig.getTempExpiry()).thenReturn(Duration.ofMinutes(5L));
        when(storageLayout.getTempDir()).thenReturn(tmpDir);

        storageManager = new DefaultStorageManager(storageConfig, storageLayout);
    }

    /**
     * The file structure in the tmp directory:
     *
     * tmp / stale-1
     *     / stale-2
     *     / recent-1
     *     / stale-folder-1  / stale-3
     *                       / stale-4
     *     / recent-folder-1 / stale-5
     *                       / recent-2
     *                       / recent-folder-2 / recent-3
     *                       / stale-folder-2  / stale-6
     *                                         / stale-7
     *     / recent-folder-3
     *     / stale-folder-3
     *
     * After the clean up the folder structure should look like:
     *
     * tmp / recent-1
     *     / recent-folder-1 / recent-2
     *                       / recent-folder-2 / recent-3
     */
    @Test
    public void testCleanTmp() throws IOException {
        Path stale1 = createFile(tmpDir, true, "stale-1");
        Path stale2 = createFile(tmpDir, true, "stale-2");
        Path recent1 = createFile(tmpDir, false, "recent-1");

        Path staleFolder1 = createFolder(tmpDir, true, "stale-folder-1");
        Path stale3 = createFile(staleFolder1, true, "stale-3");
        Path stale4 = createFile(staleFolder1, true, "stale-4");

        Path recentFolder1 = createFolder(tmpDir, false, "recent-folder-1");
        Path stale5 = createFile(recentFolder1, true, "stale-5");
        Path recent2 = createFile(recentFolder1, false, "recent-2");

        Path recentFolder2 = createFolder(recentFolder1, false, "recent-folder-2");
        Path recent3 = createFile(recentFolder2, false, "recent-3");

        Path staleFolder2 = createFolder(recentFolder1, true, "stale-folder-2");
        Path stale6 = createFile(staleFolder2, true, "stale-6");
        Path stale7 = createFile(staleFolder2, true, "stale-7");

        Path recentFolder3 = createFolder(tmpDir, false, "recent-folder-3");
        Path staleFolder3 = createFolder(tmpDir, true, "stale-folder-3");

        storageManager.cleanTmp();

        assertFalse(Files.exists(stale1));
        assertFalse(Files.exists(stale2));
        assertTrue(Files.exists(recent1));

        assertFalse(Files.exists(staleFolder1));
        assertFalse(Files.exists(stale3));
        assertFalse(Files.exists(stale4));

        assertTrue(Files.exists(recentFolder1));
        assertFalse(Files.exists(stale5));
        assertTrue(Files.exists(recent2));

        assertTrue(Files.exists(recentFolder2));
        assertTrue(Files.exists(recent3));

        assertFalse(Files.exists(staleFolder2));
        assertFalse(Files.exists(stale6));
        assertFalse(Files.exists(stale7));

        assertFalse(Files.exists(recentFolder3));
        assertFalse(Files.exists(staleFolder3));
        assertTrue(Files.exists(tmpDir));
    }

    private Path createFile(Path path, boolean stale, String fileName) throws IOException {
        Path file = Files.createFile(path.resolve(fileName));
        if (stale) {
            Files.setLastModifiedTime(file, FileTime.from(Instant.from(oldDateTime.toInstant(ZoneOffset.UTC))));
        } else {
            //Deleting a folder is done by calculating the size of the folder and if we have empty files then we
            //delete the folder, so just add some junk.
            OutputStream os = new FileOutputStream(file.toFile(), true);
            os.write("content".getBytes(), 0, "content".length());
            os.close();
        }
        return file;
    }

    private Path createFolder(Path path, boolean stale, String folderName) throws IOException {
        Path folder = Files.createDirectory(path.resolve(folderName));
        if (stale) {
            Files.setLastModifiedTime(folder, FileTime.from(Instant.from(oldDateTime.toInstant(ZoneOffset.UTC))));
        }
        return folder;
    }

}
