/**
 * detectable
 *
 * Copyright (c) 2020 Synopsys, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 com.synopsys.integration.detectable.detectables.docker.unit;

import static junit.framework.TestCase.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.condition.OS.WINDOWS;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.List;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import com.google.gson.Gson;
import com.synopsys.integration.bdio.BdioTransformer;
import com.synopsys.integration.bdio.model.externalid.ExternalIdFactory;
import com.synopsys.integration.detectable.Extraction;
import com.synopsys.integration.detectable.detectable.executable.Executable;
import com.synopsys.integration.detectable.detectable.executable.ExecutableRunner;
import com.synopsys.integration.detectable.detectable.executable.ExecutableRunnerException;
import com.synopsys.integration.detectable.detectable.file.FileFinder;
import com.synopsys.integration.detectable.detectables.docker.DockerExtractor;
import com.synopsys.integration.detectable.detectables.docker.DockerInspectorInfo;
import com.synopsys.integration.detectable.detectables.docker.DockerProperties;
import com.synopsys.integration.detectable.detectables.docker.ImageIdentifierType;

public class DockerExtractorTest {
    private static File fakeContainerFileSystemFile;
    private static File fakeSquashedImageFile;
    private static File fakeDockerTarFile;

    @BeforeAll
    public static void setup() throws IOException {
        fakeContainerFileSystemFile = Files.createTempFile("DockerExtractorTest", "_containerfilesystem.tar.gz").toFile();
        fakeSquashedImageFile = Files.createTempFile("DockerExtractorTest", "_squashedimage.tar.gz").toFile();
        fakeDockerTarFile = Files.createTempFile("DockerExtractorTest", "_testDockerTarfile.tar").toFile();
    }

    @AfterAll
    public static void tearDown() {
        fakeContainerFileSystemFile.delete();
        fakeDockerTarFile.delete();
    }

    @Test
    @DisabledOnOs(WINDOWS)
    public void testExtractImageReturningContainerFileSystem() throws ExecutableRunnerException {

        final String image = "ubuntu:latest";
        final String imageId = null;
        final String tar = null;
        final ExecutableRunner executableRunner = Mockito.mock(ExecutableRunner.class);

        final Extraction extraction = extract(image, imageId, tar, fakeContainerFileSystemFile, null, executableRunner);

        assertEquals("ubuntu:latest", extraction.getMetaData(DockerExtractor.DOCKER_IMAGE_NAME_META_DATA).get());
        assertTrue(extraction.getMetaData(DockerExtractor.DOCKER_TAR_META_DATA).get().getName().endsWith("_containerfilesystem.tar.gz"));

        final ArgumentCaptor<Executable> executableArgumentCaptor = ArgumentCaptor.forClass(Executable.class);
        Mockito.verify(executableRunner).execute(executableArgumentCaptor.capture());
        final Executable executableToVerify = executableArgumentCaptor.getValue();
        final List<String> command = executableToVerify.getCommand();
        assertTrue(command.get(0).endsWith("/fake/test/java"));
        assertEquals("-jar", command.get(1));
        assertTrue(command.get(2).endsWith("/fake/test/dockerinspector.jar"));
        assertTrue(command.get(3).startsWith("--spring.config.location="));
        assertTrue(command.get(3).endsWith("/application.properties"));
        assertEquals("--docker.image=ubuntu:latest", command.get(4));
    }

    @Test
    @DisabledOnOs(WINDOWS)
    public void testExtractImageReturningSquashedImage() throws ExecutableRunnerException {

        final String image = "ubuntu:latest";
        final String imageId = null;
        final String tar = null;
        final ExecutableRunner executableRunner = Mockito.mock(ExecutableRunner.class);

        final Extraction extraction = extract(image, imageId, tar, fakeContainerFileSystemFile, fakeSquashedImageFile, executableRunner);

        assertEquals("ubuntu:latest", extraction.getMetaData(DockerExtractor.DOCKER_IMAGE_NAME_META_DATA).get());
        // When Detect gets both .tar.gz files back, should prefer the squashed image
        assertTrue(extraction.getMetaData(DockerExtractor.DOCKER_TAR_META_DATA).get().getName().endsWith("_squashedimage.tar.gz"));

        final ArgumentCaptor<Executable> executableArgumentCaptor = ArgumentCaptor.forClass(Executable.class);
        Mockito.verify(executableRunner).execute(executableArgumentCaptor.capture());
        final Executable executableToVerify = executableArgumentCaptor.getValue();
        final List<String> command = executableToVerify.getCommand();
        assertTrue(command.get(0).endsWith("/fake/test/java"));
        assertEquals("-jar", command.get(1));
        assertTrue(command.get(2).endsWith("/fake/test/dockerinspector.jar"));
        assertTrue(command.get(3).startsWith("--spring.config.location="));
        assertTrue(command.get(3).endsWith("/application.properties"));
        assertEquals("--docker.image=ubuntu:latest", command.get(4));
    }

    @Test
    @DisabledOnOs(WINDOWS)
    public void testExtractTarReturningContainerFileSystem() throws ExecutableRunnerException {

        final String image = null;
        final String imageId = null;
        final String tar = fakeDockerTarFile.getAbsolutePath();
        final ExecutableRunner executableRunner = Mockito.mock(ExecutableRunner.class);

        final Extraction extraction = extract(image, imageId, tar, fakeContainerFileSystemFile, null, executableRunner);

        assertTrue(extraction.getMetaData(DockerExtractor.DOCKER_IMAGE_NAME_META_DATA).get().endsWith("testDockerTarfile.tar"));
        assertTrue(extraction.getMetaData(DockerExtractor.DOCKER_TAR_META_DATA).get().getName().endsWith("_containerfilesystem.tar.gz"));

        final ArgumentCaptor<Executable> executableArgumentCaptor = ArgumentCaptor.forClass(Executable.class);
        Mockito.verify(executableRunner).execute(executableArgumentCaptor.capture());
        final Executable executableToVerify = executableArgumentCaptor.getValue();
        final List<String> command = executableToVerify.getCommand();
        assertTrue(command.get(0).endsWith("/fake/test/java"));
        assertEquals("-jar", command.get(1));
        assertTrue(command.get(2).endsWith("/fake/test/dockerinspector.jar"));
        assertTrue(command.get(3).startsWith("--spring.config.location="));
        assertTrue(command.get(3).endsWith("/application.properties"));
        assertTrue(command.get(4).startsWith("--docker.tar="));
        assertTrue(command.get(4).endsWith("testDockerTarfile.tar"));
    }

    @Test
    @DisabledOnOs(WINDOWS)
    public void testExtractTarReturningOriginalTar() throws ExecutableRunnerException {

        final String image = null;
        final String imageId = null;
        final String tar = fakeDockerTarFile.getAbsolutePath();
        final ExecutableRunner executableRunner = Mockito.mock(ExecutableRunner.class);

        final Extraction extraction = extract(image, imageId, tar, null, null, executableRunner);

        // No returned .tar.gz: scan given docker tar instead
        assertTrue(extraction.getMetaData(DockerExtractor.DOCKER_IMAGE_NAME_META_DATA).get().endsWith("testDockerTarfile.tar"));
        assertTrue(extraction.getMetaData(DockerExtractor.DOCKER_TAR_META_DATA).get().getName().endsWith("testDockerTarfile.tar"));

        final ArgumentCaptor<Executable> executableArgumentCaptor = ArgumentCaptor.forClass(Executable.class);
        Mockito.verify(executableRunner).execute(executableArgumentCaptor.capture());
        final Executable executableToVerify = executableArgumentCaptor.getValue();
        final List<String> command = executableToVerify.getCommand();
        assertTrue(command.get(0).endsWith("/fake/test/java"));
        assertEquals("-jar", command.get(1));
        assertTrue(command.get(2).endsWith("/fake/test/dockerinspector.jar"));
        assertTrue(command.get(3).startsWith("--spring.config.location="));
        assertTrue(command.get(3).endsWith("/application.properties"));
        assertTrue(command.get(4).startsWith("--docker.tar="));
        assertTrue(command.get(4).endsWith("testDockerTarfile.tar"));
    }

    @Test
    @DisabledOnOs(WINDOWS)
    public void testGetImageIdentifierFromOutputDirectoryIfImageIdPresent() throws URISyntaxException {
        String testString = "test";
        String imageIdArgument = "--docker.image.id=";
        String imageName = "ubuntu:latest";
        final File outputDirectoryWithPopulatedResultsFile = new File(DockerExtractorTest.class.getClassLoader().getSystemResource("detectables/functional/docker/unit/outputDirectoryWithPopulatedResultsFile").toURI());
        final File outputDirectoryWithNonPopulatedResultsFile = new File(DockerExtractorTest.class.getClassLoader().getSystemResource("detectables/functional/docker/unit/outputDirectoryWithNonPopulatedResultsFile").toURI());

        ExecutableRunner executableRunner = Mockito.mock(ExecutableRunner.class);
        FileFinder fileFinder = Mockito.mock(FileFinder.class);
        Mockito.when(fileFinder.findFile(outputDirectoryWithPopulatedResultsFile, DockerExtractor.RESULTS_FILENAME_PATTERN)).thenReturn(new File(outputDirectoryWithPopulatedResultsFile, "results.json"));
        Mockito.when(fileFinder.findFile(outputDirectoryWithNonPopulatedResultsFile, DockerExtractor.RESULTS_FILENAME_PATTERN)).thenReturn(new File(outputDirectoryWithNonPopulatedResultsFile, "results.json"));

        DockerExtractor dockerExtractor = getMockDockerExtractor(executableRunner, fileFinder);

        assertEquals(imageName, dockerExtractor.getImageIdentifierFromOutputDirectoryIfImageIdPresent(outputDirectoryWithPopulatedResultsFile, testString, ImageIdentifierType.IMAGE_ID));
        assertEquals(testString, dockerExtractor.getImageIdentifierFromOutputDirectoryIfImageIdPresent(outputDirectoryWithPopulatedResultsFile, testString, ImageIdentifierType.IMAGE_NAME));
        assertEquals(testString, dockerExtractor.getImageIdentifierFromOutputDirectoryIfImageIdPresent(outputDirectoryWithNonPopulatedResultsFile, testString, ImageIdentifierType.IMAGE_ID));
    }

    private DockerExtractor getMockDockerExtractor(ExecutableRunner executableRunner, FileFinder fileFinder) {
        final BdioTransformer bdioTransformer = Mockito.mock(BdioTransformer.class);
        final ExternalIdFactory externalIdFactory = Mockito.mock(ExternalIdFactory.class);
        final Gson gson = new Gson();

        return new DockerExtractor(fileFinder, executableRunner, bdioTransformer, externalIdFactory, gson);
    }

    private Extraction extract(final String image, final String imageId, final String tar,
        File returnedContainerFileSystemFile,
        File returnedSquashedImageFile,
        final ExecutableRunner executableRunner) {
        final FileFinder fileFinder = Mockito.mock(FileFinder.class);
        final DockerExtractor dockerExtractor = getMockDockerExtractor(executableRunner, fileFinder);

        final File directory = new File(".");
        final File outputDirectory = new File("build/tmp/test/DockerExtractorTest");
        final File bashExe = null;
        final File javaExe = new File("fake/test/java");

        final DockerInspectorInfo dockerInspectorInfo = Mockito.mock(DockerInspectorInfo.class);
        Mockito.when(fileFinder.findFile(outputDirectory, DockerExtractor.CONTAINER_FILESYSTEM_FILENAME_PATTERN)).thenReturn(returnedContainerFileSystemFile);
        Mockito.when(fileFinder.findFile(outputDirectory, DockerExtractor.SQUASHED_IMAGE_FILENAME_PATTERN)).thenReturn(returnedSquashedImageFile);
        Mockito.when(dockerInspectorInfo.getDockerInspectorJar()).thenReturn(new File("fake/test/dockerinspector.jar"));

        return dockerExtractor.extract(directory, outputDirectory, bashExe, javaExe, image, imageId, tar, dockerInspectorInfo, Mockito.mock(DockerProperties.class));
    }

}