package pkg

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"gitlab.com/M0M097/pkg/internal/must"
)

const runFatalSettingsErrorEnv = "PKG_TEST_RUN_FATAL_SETTINGS_ERROR"

func TestRunWrapper(t *testing.T) {
	var (
		testFlags = []string{VIEW_FLAG, VIEWBUILD_FLAG, SEARCH_FLAG, TRACK_FLAG,
			UPDATE_FLAG, VERSION_FLAG}
		ntests      = len(testFlags)
		output_pkg1 = make([]string, ntests)
		output_pkg2 = make([]string, ntests)
	)
	for i, cmd := range testFlags {
		output_pkg1[i], _, _ = testRunHelperFull(t, "-"+cmd, DUMMY_PKG1, "")
		output_pkg2[i], _, _ = testRunHelperFull(t, "-"+cmd, DUMMY_PKG2, "")
	}

	mustContain(t, output_pkg1[0], "displaying", "README", DUMMY_PKG1)
	mustContain(t, output_pkg2[0], "displaying", "README", DUMMY_PKG2)
	mustContain(t, output_pkg1[1], "displaying", DUMMY_PKG1+".SlackBuild", DUMMY_PKG1)
	mustContain(t, output_pkg2[1], "displaying", DUMMY_PKG2+".SlackBuild", DUMMY_PKG2)
	mustBeEqual(t, output_pkg1[2], DUMMY_PKG1+"\n")
	mustBeEqual(t, output_pkg2[2], DUMMY_PKG2+"\n")
	mustBeEqual(t, output_pkg1[3], DUMMY_PKG2+"\n"+DUMMY_PKG1+"\n")
	mustBeEqual(t, output_pkg2[3], DUMMY_PKG2+"\n")
	_, errPkg1, _ := testRunHelperFull(t, "-"+UPDATE_FLAG, DUMMY_PKG1, "")
	mustContain(t, output_pkg1[4],
		"mocking sync",
		"mocking clean",
		"Space separated list of packages to exclude:",
		DUMMY_PKG2,
		DUMMY_PKG1,
	)
	mustContain(t, errPkg1,
		DUMMY_PKG2+": Failed Build:",
		filepath.Join(DUMMY_PKG2, "Build.log"),
		DUMMY_PKG1+": Skipped; depends on "+DUMMY_PKG2,
	)
	_, errPkg2, _ := testRunHelperFull(t, "-"+UPDATE_FLAG, DUMMY_PKG2, "")
	mustContain(t, output_pkg2[4],
		"mocking sync",
		"mocking clean",
		"Space separated list of packages to exclude:",
		"1 "+DUMMY_PKG2,
	)
	mustContain(t, errPkg2,
		DUMMY_PKG2+": Failed Build:",
		filepath.Join(DUMMY_PKG2, "Build.log"),
	)
	mustNotContain(t, output_pkg2[4], DUMMY_PKG1)
	mustBeEqual(t, output_pkg1[5], VERSION+"\n")
	mustBeEqual(t, output_pkg2[5], VERSION+"\n")

	outputPkg3, _, _ := testRunHelperFull(t, "-"+UPDATE_FLAG, DUMMY_PKG3, "")
	mustContain(t, outputPkg3, "mocking download", "All packages installed successfully")
}

func TestDisplayDependents(t *testing.T) {
	output, _, _ := testRunHelperFull(t, "-"+ROOT_FLAG+" "+DUMMY_PKG2, DUMMY_PKG_FILE_CONTENT, "")
	mustBeEqual(t, output, DUMMY_PKG2+"\n"+DUMMY_PKG1+"\n")
}

func TestNoDeps(t *testing.T) {
	output, _, _ := testRunHelperFull(t, "-"+NO_DEPS_FLAG, DUMMY_PKG1+"\n", "")
	mustContain(t, output, DUMMY_PKG1)
	mustNotContain(t, output, DUMMY_PKG2)
}

func TestSkipPackages(t *testing.T) {
	output, _, _ := testRunHelperFull(t, "-"+UPDATE_FLAG, DUMMY_PKG_FILE_CONTENT, "1 2")
	mustContain(t, output, "All packages skipped")
}

func TestSkipGreylist(t *testing.T) {
	output, stderr, _ := testRunHelperFull(t, "-"+UPDATE_FLAG, DUMMY_GREYLISTED_PKG, "")
	mustContain(t, output, "Nothing to do")
	mustContain(t, stderr, "greylisted:", DUMMY_GREYLISTED_PKG)
}

func TestCleanSystem(t *testing.T) {
	var (
		args       = DummyArgs(t)
		mockStdOut strings.Builder
		mockStdErr strings.Builder
	)
	args.Streams.Out = &mockStdOut
	args.Streams.Err = &mockStdErr
	args.Argv = []string{"pkg", "-" + CLEAN_SYSTEM_FLAG}

	Run(args)

	mustContain(t, mockStdOut.String(), "mocking slackpkg clean")
	mustNotContain(t, mockStdOut.String(), "Space separated list of packages to exclude:", MOCK_CLEAN)

	content := string(must.ReadFile(args.Config.Blacklist))
	mustContain(t, content, DUMMY_BLACKLISTED_PKG, DUMMY_PKG1, DUMMY_PKG2)

	mirrors, err := os.Readlink(filepath.Join(args.Config.SlackpkgConfigDir, "mirrors"))
	if err != nil {
		t.Fatalf("failed to read mirrors symlink: %v", err)
	}
	conf, err := os.Readlink(filepath.Join(args.Config.SlackpkgConfigDir, "slackpkg.conf"))
	if err != nil {
		t.Fatalf("failed to read slackpkg.conf symlink: %v", err)
	}
	mustBeEqual(t, mirrors, filepath.Join(args.Config.SlackpkgSystemConfigDir, "mirrors"))
	mustBeEqual(t, conf, filepath.Join(args.Config.SlackpkgSystemConfigDir, "slackpkg.conf"))
}

func testRunHelperFull(t *testing.T, cmdLine, packages, input string) (string, string, string) {
	var (
		args       = DummyArgs(t)
		mockStdOut strings.Builder
		mockStdErr strings.Builder
	)

	argv := append([]string{"pkg"}, strings.Fields(cmdLine)...)
	must.Content2File(args.Config.DefaultPkgFile, packages)
	args.Streams.Out = &mockStdOut
	args.Streams.In = strings.NewReader(input)
	args.Streams.Err = &mockStdErr
	args.Argv = argv

	PagerCmd := os.Getenv("PAGER")
	os.Setenv("PAGER", "/bin/echo displaying: ")
	defer os.Setenv("PAGER", PagerCmd)

	Run(args)

	return mockStdOut.String(), mockStdErr.String(), args.Config.TmpDir
}

func TestRunRepoDoesNotExist(t *testing.T) {
	var mockStdOut strings.Builder
	pkgFile := must.Content2File(filepath.Join(t.TempDir(), "pkgfile"), DUMMY_PKG_FILE_CONTENT)
	args := DummyArgs(t)
	args.Streams.Out = &mockStdOut
	args.Config.DefaultPkgFile = pkgFile
	args.Config.RepoPath = filepath.Join(t.TempDir(), "doesnotexist")
	args.Config.PullCmd = MOCK_PULL + " && exit 1"

	defer func() {
		if r := recover(); r == nil {
			t.Error("Expected panic, but got none")
		}
	}()
	Run(args)
	mustContain(t, mockStdOut.String(), "mocking pull")
	mustNotContain(t, mockStdOut.String(), "mocking sync", "mocking clean", "mocking download")
}

func TestListInstalled(t *testing.T) {
	var (
		mockStdOut strings.Builder
		args       = DummyArgs(t)
		docDir     = t.TempDir()
	)
	args.Streams.Out = &mockStdOut
	args.Argv = []string{"pkg", "-" + LIST_INSTALLED_FLAG}
	args.Config.DocDir = docDir
	must.Mkdir(filepath.Join(docDir, DUMMY_PKG1+"-dummy_version"))
	Run(args)
	mustBeEqual(t, mockStdOut.String(), DUMMY_PKG1+"\n")
}

func TestRunFatalSettingsError(t *testing.T) {
	if os.Getenv(runFatalSettingsErrorEnv) == "1" {
		args := DummyArgs(t)
		args.Streams.Out = os.Stdout
		args.Streams.Err = os.Stderr
		args.Argv = []string{"pkg", "-" + JOBS_FLAG, "0"}

		Run(args)
		t.Fatal("Run returned without exiting for invalid settings")
	}

	cmd := exec.Command(os.Args[0], "-test.run=^TestRunFatalSettingsError$")
	cmd.Env = append(os.Environ(), runFatalSettingsErrorEnv+"=1")
	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatal("expected Run to exit for invalid settings")
	}
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		t.Fatalf("expected an exit error for invalid settings, got %T: %v", err, err)
	}
	if exitErr.ExitCode() != 1 {
		t.Fatalf("expected exit code 1 for invalid settings, got %d\nOutput:\n%s",
			exitErr.ExitCode(), output)
	}
	mustContain(t, string(output), "-j must be at least 1, got 0")
}
