package manager

import (
	"strings"
	"testing"

	"github.com/docker/cli/cli/config"
	"github.com/docker/cli/cli/config/configfile"
	"github.com/docker/cli/internal/test"
	"gotest.tools/v3/assert"
	"gotest.tools/v3/fs"
)

func TestListPluginCandidates(t *testing.T) {
	// Populate a selection of directories with various shadowed and bogus/obscure plugin candidates.
	// For the purposes of this test no contents is required and permissions are irrelevant.
	dir := fs.NewDir(t, t.Name(),
		fs.WithDir(
			"plugins1",
			fs.WithFile("docker-plugin1", ""),                        // This appears in each directory
			fs.WithFile("not-a-plugin", ""),                          // Should be ignored
			fs.WithFile("docker-symlinked1", ""),                     // This and ...
			fs.WithSymlink("docker-symlinked2", "docker-symlinked1"), // ... this should both appear
			fs.WithDir("ignored1"),                                   // A directory should be ignored
		),
		fs.WithDir(
			"plugins2",
			fs.WithFile("docker-plugin1", ""),
			fs.WithFile("also-not-a-plugin", ""),
			fs.WithFile("docker-hardlink1", ""),                     // This and ...
			fs.WithHardlink("docker-hardlink2", "docker-hardlink1"), // ... this should both appear
			fs.WithDir("ignored2"),
		),
		fs.WithDir(
			"plugins3-target", // Will be referenced as a symlink from below
			fs.WithFile("docker-plugin1", ""),
			fs.WithDir("ignored3"),
			fs.WithSymlink("docker-brokensymlink", "broken"),           // A broken symlink is still a candidate (but would fail tests later)
			fs.WithFile("non-plugin-symlinked", ""),                    // This shouldn't appear, but ...
			fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
		),
		fs.WithSymlink("plugins3", "plugins3-target"),
		fs.WithFile("/plugins4", ""),
		fs.WithSymlink("plugins5", "plugins5-nonexistent-target"),
	)
	defer dir.Remove()

	var dirs []string
	for _, d := range []string{"plugins1", "nonexistent", "plugins2", "plugins3", "plugins4", "plugins5"} {
		dirs = append(dirs, dir.Join(d))
	}

	candidates, err := listPluginCandidates(dirs)
	assert.NilError(t, err)
	exp := map[string][]string{
		"plugin1": {
			dir.Join("plugins1", "docker-plugin1"),
			dir.Join("plugins2", "docker-plugin1"),
			dir.Join("plugins3", "docker-plugin1"),
		},
		"symlinked1": {
			dir.Join("plugins1", "docker-symlinked1"),
		},
		"symlinked2": {
			dir.Join("plugins1", "docker-symlinked2"),
		},
		"hardlink1": {
			dir.Join("plugins2", "docker-hardlink1"),
		},
		"hardlink2": {
			dir.Join("plugins2", "docker-hardlink2"),
		},
		"brokensymlink": {
			dir.Join("plugins3", "docker-brokensymlink"),
		},
		"symlinked": {
			dir.Join("plugins3", "docker-symlinked"),
		},
	}

	assert.DeepEqual(t, candidates, exp)
}

func TestErrPluginNotFound(t *testing.T) {
	var err error = errPluginNotFound("test")
	err.(errPluginNotFound).NotFound()
	assert.Error(t, err, "Error: No such CLI plugin: test")
	assert.Assert(t, IsNotFound(err))
	assert.Assert(t, !IsNotFound(nil))
}

func TestGetPluginDirs(t *testing.T) {
	cli := test.NewFakeCli(nil)

	pluginDir, err := config.Path("cli-plugins")
	assert.NilError(t, err)
	expected := append([]string{pluginDir}, defaultSystemPluginDirs...)

	var pluginDirs []string
	pluginDirs, err = getPluginDirs(cli)
	assert.Equal(t, strings.Join(expected, ":"), strings.Join(pluginDirs, ":"))
	assert.NilError(t, err)

	extras := []string{
		"foo", "bar", "baz",
	}
	expected = append(extras, expected...)
	cli.SetConfigFile(&configfile.ConfigFile{
		CLIPluginsExtraDirs: extras,
	})
	pluginDirs, err = getPluginDirs(cli)
	assert.DeepEqual(t, expected, pluginDirs)
	assert.NilError(t, err)
}
