// Copyright 2000-2020 JetBrains s.r.o.

package com.goide.vgo;

import com.goide.GoConstants;
import com.goide.project.GoModuleLibrariesService;
import com.goide.vgo.configuration.VgoProjectSettings;
import com.goide.vgo.mod.psi.VgoFile;
import com.goide.vgo.project.VgoDependency;
import com.goide.vgo.project.VgoDependencyImpl;
import com.goide.vgo.project.VgoExcludeRootsPolicy;
import com.goide.vgo.project.VgoModule;
import com.goide.vgo.project.VgoModulesRegistry;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.TestModeFlags;
import com.intellij.testFramework.VfsTestUtil;
import com.intellij.testFramework.fixtures.CodeInsightTestFixture;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import java.io.File;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class VgoTestUtil {
    private static final String GOPATH = getGoTestDataPath("vgo/src/test/testData/mockGoPath").getAbsolutePath();
    private static final Map<String, VgoDependency> DEPENDENCIES =
        ContainerUtil.newHashMap(dependencyPair("vgoDep", "v1.5.2", null, null),
                                 dependencyPair("vgoTransitiveDep", "v1.0.0", null, null),
                                 dependencyPair("customDir", "v0.0.0", "customDirDep@v1.0.0", null),
                                 dependencyPair("originalDep", "v1.0.0", null, dependency("replacedDep", "v1.0.0", null, null)));
    public static final String DEFAULT_IMPORT_PATH = "jetbrains.com/hello";

    @NotNull
    private static Pair<String, VgoDependencyImpl> dependencyPair(@NotNull String importPath,
                                                                  @NotNull String version,
                                                                  @Nullable String dir,
                                                                  @Nullable VgoDependencyImpl replace) {
        VgoDependencyImpl dependency = dependency(importPath, version, dir, replace);
        return Pair.create(dependency.getDirPath(), dependency);
    }

    @NotNull
    private static VgoDependencyImpl dependency(@NotNull String importPath,
                                                @NotNull String version,
                                                @Nullable String dir,
                                                @Nullable VgoDependencyImpl replace) {
        String dirName = dir != null ? dir : String.format("%s@%s", importPath, version);
        String dirPath = FileUtil.join(GOPATH, "pkg", GoConstants.VGO_DIR_NAME, dirName);
        return new VgoDependencyImpl(importPath, version, null, PathUtil.toSystemIndependentName(FileUtil.toCanonicalPath(dirPath)), replace, false);
    }

    public static PsiFile setupVgoIntegration(@NotNull CodeInsightTestFixture fixture) {
        return setupVgoIntegration(null, DEFAULT_IMPORT_PATH, fixture, DEPENDENCIES);
    }

    public static PsiFile setupVgoIntegration(@Nullable String modulePath,
                                              @NotNull String importPath,
                                              @NotNull CodeInsightTestFixture fixture,
                                              @NotNull Map<String, VgoDependency> dependencies) {
        setupGopath(fixture, null);
        VgoModule module = createVgoModule(fixture, modulePath, importPath, dependencies);
        setupVgoIntegration(fixture, Collections.singletonList(module));
        return getGoModPsiFile(fixture, module);
    }

    @NotNull
    public static VirtualFile setupGopath(@NotNull CodeInsightTestFixture fixture, @Nullable String customGopath) {
        if (customGopath == null) {
            GoModuleLibrariesService.getInstance(fixture.getModule()).setLibraryRootUrls(VfsUtilCore.pathToUrl(GOPATH));
            return VfsTestUtil.findFileByCaseSensitivePath(GOPATH);
        }
        VirtualFile customGopathFile = VfsUtil.findFile(Paths.get(fixture.getTestDataPath(), customGopath), true);
        if (customGopathFile != null) {
            // Sometimes changes in custom gopath directory are not detected by tests, we explicitly refresh to overcome this.
            customGopathFile.refresh(false, true);
            GoModuleLibrariesService.getInstance(fixture.getModule()).setLibraryRootUrls(customGopathFile.getUrl());
            return customGopathFile;
        }
        throw new IllegalArgumentException("Cannot find custom gopath: " + customGopath);
    }

    @NotNull
    // made public
    public static VgoModule createVgoModule(@NotNull CodeInsightTestFixture fixture,
                                            @Nullable String modulePath,
                                            @NotNull String importPath,
                                            @NotNull Map<String, VgoDependency> dependencies) {
        String goModPath = modulePath != null ? FileUtil.join(modulePath, VgoUtil.GO_MOD) : VgoUtil.GO_MOD;
        PsiFile file = fixture.addFileToProject(goModPath, "module \"" + importPath + "\"");
        return new VgoModule(file.getVirtualFile().getParent(), importPath, dependencies);
    }

    public static void setupVgoIntegration(@NotNull CodeInsightTestFixture fixture, @NotNull List<VgoModule> modules) {
        VgoExcludeRootsPolicy.setPointersDisposable(fixture.getTestRootDisposable());
        // GoVendorExcludePolicy.setPointersDisposable(fixture.getTestRootDisposable());
        TestModeFlags.set(VgoIntegrationManager.DISABLE_TRACKERS, true, fixture.getTestRootDisposable());
        VgoProjectSettings.getInstance(fixture.getProject()).setIntegrationEnabled(true);
        setVgoModules(fixture, modules);
        Disposer.register(fixture.getTestRootDisposable(), () -> {
            VgoProjectSettings.getInstance(fixture.getProject()).setIntegrationEnabled(false);
            VgoModulesRegistry.getInstance(fixture.getProject()).updateAllModules(fixture.getModule(), Collections.emptySet());
            PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue();
        });
        PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue();
    }

    public static void setVgoModules(@NotNull CodeInsightTestFixture fixture, @NotNull List<VgoModule> modules) {
        for (VgoModule module : modules) {
            VgoModuleInfoProvider.VgoModuleInfoProviderForTests.putTestingVgoModule(module, fixture.getTestRootDisposable());
        }
        List<VgoRootToModule> vgoModules =
            ContainerUtil.map(modules, module -> new VgoRootToModule(module.getRoot(), module));
        VgoModulesRegistry.getInstance(fixture.getProject()).updateModules(fixture.getModule(), vgoModules);
    }

    @NotNull
    public static VgoFile getGoModPsiFile(@NotNull CodeInsightTestFixture fixture, @NotNull VgoModule vgoModule) {
        return (VgoFile)VgoTestUtil.getPsiFile(fixture, getGoModFile(vgoModule));
    }

    public static @NotNull VirtualFile getGoModFile(@NotNull VgoModule vgoModule) {
        return vgoModule.getRoot().findChild(VgoUtil.GO_MOD);
    }

    public static @Nullable PsiFile getPsiFile(@NotNull CodeInsightTestFixture fixture, @NotNull VirtualFile virtualFile) {
        return ReadAction.compute(() -> PsiManager.getInstance(fixture.getProject()).findFile(virtualFile));
    }

    @NotNull
    public static File getGoTestDataPath(@NotNull String path) {
        String homePath = PathManager.getHomePath();
        File testData = FileUtil.findFirstThatExist(homePath + "/goland/intellij-go/src/test/testData/" + path, // go tests in ide
                                                    homePath + "/src/test/testData/" + path, // ???
                                                    homePath + "/goland/intellij-go/" + path, // vgo tests in ide
                                                    "src/test/testData/" + path, // go tests
                                                    "../" + path, // vgo tests
                                                    "../src/test/testData/" + path); // go tests in vgo submodule
        return Objects.requireNonNull(testData);
    }
}
