/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.patching.runner;

import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.jboss.as.patching.HashUtils.bytesToHexString;
import static org.jboss.as.patching.HashUtils.hashFile;
import static org.jboss.as.patching.metadata.Patch.PatchType.CUMULATIVE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

import org.jboss.as.patching.ContentConflictsException;
import org.jboss.as.patching.DirectoryStructure;
import org.jboss.as.patching.IoUtils;
import org.jboss.as.patching.PatchInfo;
import org.jboss.as.patching.installation.Identity;
import org.jboss.as.patching.installation.PatchableTarget;
import org.jboss.as.patching.metadata.ContentItem;
import org.jboss.as.patching.metadata.Patch;
import org.jboss.as.patching.tool.PatchingResult;
import org.junit.Assert;

/**
 * @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2012, Red Hat Inc
 */
public class PatchingAssert {

    public static void assertContains(File f, File... files) {
        List<File> set = Arrays.asList(files);
        assertTrue(f + " not found in " + set, set.contains(f));
    }

    public static File assertDirExists(File rootDir, String... segments) {
        return assertFileExists(true, rootDir, segments);
    }

    public static void assertDirDoesNotExist(File rootDir, String... segments) {
        assertFileDoesNotExist(rootDir, segments);
    }

    public static File assertFileExists(File rootDir, String... segments) {
        return assertFileExists(false, rootDir, segments);
    }

    private static File assertFileExists(boolean isDir, File rootFile, String... segments) {
        assertTrue(rootFile + " does not exist", rootFile.exists());
        File f = rootFile;
        for (String segment : segments) {
            f = new File(f, segment);
            assertTrue(f + " does not exist", f.exists());
        }
        assertEquals(f + " is " + (isDir? "not":"") + " a directory", isDir, f.isDirectory());
        return f;
    }

    public static void assertFileDoesNotExist(File rootFile, String... segments) {
        File f = IoUtils.newFile(rootFile, segments);
        assertFalse(f + " exists", f.exists());
    }

    public static void assertFileContent(byte[] expected, File f) throws Exception {
        assertFileContent(null, expected, f);
    }

    public static void assertFileContent(String message, byte[] expected, File f) throws Exception {
        assertEquals(message, bytesToHexString(expected), bytesToHexString(hashFile(f)));
    }

    public static void assertDefinedModule(File[] modulesPath, String moduleName, byte[] expectedHash) throws Exception {
        for (File path : modulesPath) {
            final File modulePath = PatchContentLoader.getModulePath(path, moduleName, "main");
            final File moduleXml = new File(modulePath, "module.xml");
            if (moduleXml.exists()) {
                assertDefinedModuleWithRootElement(moduleXml, moduleName, "<module");
                if (expectedHash != null) {
                    byte[] actualHash = hashFile(modulePath);
                    assertTrue("content of module differs", Arrays.equals(expectedHash, actualHash));
                }
                return;
            }
        }
        fail("count not find module for " + moduleName + " in " + asList(modulesPath));
    }

    public static void assertDefinedModule(File moduleRoot, String moduleName, byte[] expectedHash) throws Exception {
        final File modulePath = PatchContentLoader.getModulePath(moduleRoot, moduleName, "main");
        final File moduleXml = new File(modulePath, "module.xml");
        if (moduleXml.exists()) {
            assertDefinedModuleWithRootElement(moduleXml, moduleName, "<module");
            if (expectedHash != null) {
                byte[] actualHash = hashFile(modulePath);
                assertTrue("content of module differs", Arrays.equals(expectedHash, actualHash));
            }
            return;
        }
        fail("count not find module for " + moduleName + " in " + moduleRoot);
    }

    static void assertDefinedBundle(File[] bundlesPath, String moduleName, byte[] expectedHash) throws Exception {
        for (File path : bundlesPath) {
            final File bundlePath = PatchContentLoader.getModulePath(path, moduleName, "main");
            if(bundlePath.exists()) {
                if(expectedHash != null) {
                    byte[] actualHash = hashFile(bundlePath);
                    assertTrue("content of bundle differs", Arrays.equals(expectedHash, actualHash));
                }
                return;
            }
        }
        fail("content not found bundle for " + moduleName + " in " + asList(bundlesPath));
    }

    static void assertDefinedBundle(File bundlesDir, String moduleName, byte[] expectedHash) throws Exception {
        final File bundlePath = PatchContentLoader.getModulePath(bundlesDir, moduleName, "main");
        if(bundlePath.exists()) {
            if(expectedHash != null) {
                byte[] actualHash = hashFile(bundlePath);
                assertTrue("content of bundle differs", Arrays.equals(expectedHash, actualHash));
            }
        } else {
            fail("content not found bundle for " + moduleName + " in " + bundlesDir);
        }
    }

    static void assertDefinedAbsentBundle(File[] bundlesPath, String moduleName) throws Exception {
        for (File path : bundlesPath) {
            final File bundlePath = PatchContentLoader.getModulePath(path, moduleName, "main");
            if(bundlePath.exists()) {
                final File[] children = bundlePath.listFiles();
                if(children.length == 0) {
                    return;
                }
            }
        }
        fail("content not found bundle for " + moduleName + " in " + asList(bundlesPath));
    }

    static void assertDefinedAbsentBundle(File bundlesDir, String bundleName) throws Exception {
        final File bundlePath = PatchContentLoader.getModulePath(bundlesDir, bundleName, "main");
        if(bundlePath.exists()) {
            final File[] children = bundlePath.listFiles();
            if(children.length == 0) {
                return;
            }
        }
        fail("content found for " + bundleName + " in " + bundlesDir);
    }

    static void assertDefinedAbsentModule(File[] modulesPath, String moduleName) throws Exception {
        for (File path : modulesPath) {
            final File modulePath = PatchContentLoader.getModulePath(path, moduleName, "main");
            final File moduleXml = new File(modulePath, "module.xml");
            if (moduleXml.exists()) {
                assertDefinedModuleWithRootElement(moduleXml, moduleName, "<module-absent ");
                return;
            }
        }
        fail("count not found module for " + moduleName + " in " + asList(modulesPath));
    }

    public static void assertDefinedAbsentModule(File modulesDir, String moduleName) throws Exception {
            final File modulePath = PatchContentLoader.getModulePath(modulesDir, moduleName, "main");
            final File moduleXml = new File(modulePath, "module.xml");
            if (moduleXml.exists()) {
                assertDefinedModuleWithRootElement(moduleXml, moduleName, "<module-absent ");
            } else {
                fail("count not found module for " + moduleName + " in " + modulesDir);
        }
    }

    private static void assertDefinedModuleWithRootElement(File moduleXMLFile, String moduleName, String rootElement) throws Exception {
        assertFileExists(moduleXMLFile);
        assertFileContains(moduleXMLFile,rootElement);
        assertFileContains(moduleXMLFile, format("name=\"%s\"", moduleName));
    }

    private static void assertFileContains(File f, String string) throws Exception {
        try (Scanner scanner = new Scanner(f, "UTF-8").useDelimiter("\\Z")) {
            String content = scanner.next();
            assertTrue(string + " not found in " + f + " with content=" + content, content.contains(string));
        }
    }

    public static void assertPatchHasBeenApplied(PatchingResult result, Patch patch) {
        if (CUMULATIVE == patch.getIdentity().getPatchType()) {
            assertEquals(patch.getPatchId(), result.getPatchInfo().getCumulativePatchID());
            assertTrue(result.getPatchInfo().getPatchIDs().isEmpty());
            assertEquals(patch.getIdentity().forType(CUMULATIVE, org.jboss.as.patching.metadata.Identity.IdentityUpgrade.class).getResultingVersion(), result.getPatchInfo().getVersion());
        } else {
            assertTrue(result.getPatchInfo().getPatchIDs().contains(patch.getPatchId()));
            // applied one-off patch is at the top of the patchIDs
            assertEquals(patch.getPatchId(), result.getPatchInfo().getPatchIDs().get(0));
        }
    }

    static void assertPatchHasNotBeenApplied(ContentConflictsException result, Patch patch, ContentItem problematicItem, DirectoryStructure structure) {
        assertFalse("patch should have failed", result.getConflicts().isEmpty());
        assertTrue(problematicItem + " is not reported in the problems " + result.getConflicts(), result.getConflicts().contains(problematicItem));

        assertDirDoesNotExist(structure.getInstalledImage().getPatchHistoryDir(patch.getPatchId()));
    }

    public static void assertPatchHasBeenRolledBack(PatchingResult result, Patch patch, PatchInfo expectedPatchInfo) {
        assertEquals(expectedPatchInfo.getVersion(), result.getPatchInfo().getVersion());
        assertEquals(expectedPatchInfo.getCumulativePatchID(), result.getPatchInfo().getCumulativePatchID());
        assertEquals(expectedPatchInfo.getPatchIDs(), result.getPatchInfo().getPatchIDs());

        // assertNoResourcesForPatch(result.getPatchInfo(), patch);
    }

    public static void assertPatchHasBeenRolledBack(PatchingResult result, Patch patch, PatchableTarget.TargetInfo expectedPatchInfo) {
        assertEquals(expectedPatchInfo.getCumulativePatchID(), result.getPatchInfo().getCumulativePatchID());
        assertEquals(expectedPatchInfo.getPatchIDs(), result.getPatchInfo().getPatchIDs());

        // assertNoResourcesForPatch(result.getPatchInfo(), patch);
    }

    public static void assertPatchHasBeenRolledBack(PatchingResult result, Identity expectedIdentity) throws IOException {
        assertEquals(expectedIdentity.getVersion(), result.getPatchInfo().getVersion());
        assertEquals(expectedIdentity.loadTargetInfo().getCumulativePatchID(), result.getPatchInfo().getCumulativePatchID());
        assertEquals(expectedIdentity.loadTargetInfo().getPatchIDs(), result.getPatchInfo().getPatchIDs());
    }

    static void assertNoResourcesForPatch(DirectoryStructure structure, Patch patch) {
        assertDirDoesNotExist(structure.getModulePatchDirectory(patch.getPatchId()));
        assertDirDoesNotExist(structure.getBundlesPatchDirectory(patch.getPatchId()));
        assertDirDoesNotExist(structure.getInstalledImage().getPatchHistoryDir(patch.getPatchId()));
    }

    public static void assertInstallationIsPatched(Patch patch, PatchableTarget.TargetInfo targetInfo) {
        if (CUMULATIVE == patch.getIdentity().getPatchType()) {
            assertEquals(patch.getPatchId(), targetInfo.getCumulativePatchID());
        } else {
            Assert.assertTrue(targetInfo.getPatchIDs().contains(patch.getPatchId()));
            // applied one-off patch is at the top of the patchIDs
            assertEquals(patch.getPatchId(), targetInfo.getPatchIDs().get(0));
        }
    }
}
