/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.installer.core;

import org.jboss.installer.SystemProperties;
import org.junit.After;
import org.junit.Test;
import org.mockito.Mockito;

import java.beans.PropertyChangeSupport;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@SuppressWarnings("OptionalGetWithoutIsPresent")
public class MavenRepositoryLoaderTest {

    public static final String TEST_ZIP_ENTRY_NAME      = "test-artifact.jar";
    public static final String TEST_ZIP_ENTRY_CONTENT   = "test artifact content";

    private PropertyChangeSupport propertyChangeSupport= new PropertyChangeSupport(this);
    private UnarchiveProgressCallback unarchiveProgressCallback = new UnarchiveProgressCallbackImpl(propertyChangeSupport);

    @After
    public void tearDown() {
        System.clearProperty(SystemProperties.INSTALLER_OVERRIDE_MAVEN_REPOSITORIES);
    }

    @Test
    public void setOfflineRepositoryZipAsDefaultRepositoryTest() throws Exception {
        final Path tempZip = createTempZipFile();
        final Map<String, URL> defaultRepositories = MavenRepositoryLoader.getDefaultRepositories(Optional.of(tempZip));

        final String offlineRepo = defaultRepositories.values().stream().map(Object::toString).collect(Collectors.joining(","));

        final Path offlineRepoPath = Path.of(new URL(offlineRepo).toURI());
        assertEquals(tempZip, offlineRepoPath);
    }

    @Test
    public void extractOfflineRepositoryZipTest() throws Exception {
        final Path tempZip = createTempZipFile();
        final URL extracted = MavenRepositoryLoader.extractIfNeeded(tempZip.toUri().toURL(),unarchiveProgressCallback);
        final Path entryFilePath = Paths.get(extracted.toURI()).resolve(TEST_ZIP_ENTRY_NAME);

        assertTrue(entryFilePath.toFile().exists());
        assertTrue(entryFilePath.toFile().isFile());

        final String entryContent = Files.readString(entryFilePath);
        assertEquals(TEST_ZIP_ENTRY_CONTENT, entryContent);
    }

    @Test
    public void offlineRepositoryDirectoryIsNotExtractedTest() throws IOException {
        final Path tempDir = FileUtils.createTempDirectory("offline-test-repo");
        final URL offlineRepoUrl = tempDir.toAbsolutePath().toUri().toURL();
        final URL extracted = MavenRepositoryLoader.extractIfNeeded(offlineRepoUrl,unarchiveProgressCallback);

        assertEquals(offlineRepoUrl, extracted);
    }

    @Test
    public void onlineRepositoryDirectoryIsNotExtractedTest() throws IOException {
        final URL offlineRepoUrl = new URL("http://test.te");
        final URL extracted = MavenRepositoryLoader.extractIfNeeded(offlineRepoUrl,unarchiveProgressCallback);

        assertEquals(offlineRepoUrl, extracted);
    }

    @Test
    public void setDefaultRepositoryWithProperty() throws Exception {
        System.setProperty(SystemProperties.INSTALLER_OVERRIDE_MAVEN_REPOSITORIES, "http://test.te");

        final URL url = MavenRepositoryLoader.getDefaultRepositories(Optional.empty()).values().stream().findFirst().get();
        assertEquals(new URL("http://test.te"), url);
    }

    @Test
    public void setDefaultRepositoryWithFile() throws Exception {
        assertThat(MavenRepositoryLoader.getDefaultRepositories(Optional.empty()).values())
                .containsExactlyInAnyOrderElementsOf(BuildProperties.DEFAULT_PRODUCT_REPOSITORIES.values());
    }

    @Test
    public void mapFileToUrl() throws Exception {
        final Map<String, URL> mapped = MavenRepositoryLoader.parseToUrls(List.of("test.file", "http://test.te"));

        assertThat(mapped)
                .containsEntry("repo-0", Path.of("test.file").toUri().toURL())
                .containsEntry("repo-1", new URL("http://test.te"));
    }

    private Path createTempZipFile() throws IOException {
        final Path zipFile = Files.createTempFile("offline-repo", ".zip");
        zipFile.toFile().deleteOnExit();
        try(ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile.toFile()))) {
            out.putNextEntry(new ZipEntry("jboss-repo-zip/"));
            out.putNextEntry(new ZipEntry("jboss-repo-zip/maven-repository/"));
            out.putNextEntry(new ZipEntry("jboss-repo-zip/maven-repository/" + TEST_ZIP_ENTRY_NAME));
            byte[] data = TEST_ZIP_ENTRY_CONTENT.getBytes();
            out.write(data, 0, data.length);
            out.closeEntry();
        }
        return zipFile;
    }

    @Test
    public void testUnzipCallbackCalled() throws Exception {
        UnarchiveProgressCallback mockCallback = Mockito.mock(UnarchiveProgressCallback.class);
        final Path tempZip = createTempZipFile();
        Path tempDir = FileUtils.createTempDirectory("eap-installer-offline-repo");
        FileUtils.unzip(tempZip.toFile(), tempDir.toFile(), mockCallback);

        // Verify that the callback methods were called
        Mockito.verify(mockCallback).unarchiveStarted();
        Mockito.verify(mockCallback, Mockito.atLeastOnce()).unarchiveUpdate(Mockito.anyInt(), Mockito.anyInt());
        Mockito.verify(mockCallback).unarchiveFinished();

    }

}
