package pkg

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

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

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")

	// Check a successful build and install with dummy download files
	output_pkg3, _, tmpDir := testRunHelperFull(t, "-"+UPDATE_FLAG, DUMMY_PKG3, "")
	/* ------------------------------- checks --------------------------------- */
	// 1. the download branch executed
	mustContain(t, output_pkg3, "mocking download")
	mustContain(t, output_pkg3, "All packages installed successfully")
	mustContain(t, string(must.ReadFile(filepath.Join(dummyPkg3.Path(), "Install.log"))), "mocking install")

	// 2. "downloaded" file exists in tmpDir
	if _, err := os.Stat(filepath.Join(tmpDir, DUMMY_PKG3_DOWNLOAD)); err != nil {
		t.Fatalf("expected %q in tmpDir: %v", DUMMY_PKG3_DOWNLOAD, err)
	}

	// 3. a symlink was created inside the package directory
	linkPath := filepath.Join(dummyPkg3.Path(), DUMMY_PKG3_DOWNLOAD)
	info, err := os.Lstat(linkPath)
	if err != nil {
		t.Fatalf("expected symlink %q to exist: %v\noutput: %s", linkPath, err, output_pkg3)
	}
	if info.Mode()&os.ModeSymlink == 0 {
		t.Fatalf("expected %q to be a symlink", linkPath)
	}
	target, err := os.Readlink(linkPath)
	if err != nil {
		t.Fatalf("readlink failed: %v", err)
	}
	want := filepath.Join(tmpDir, DUMMY_PKG3_DOWNLOAD)
	if target != want {
		t.Fatalf("symlink target: got %q, want %q", target, want)
	}
}

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 TestRunUsesOutputEnvForBuiltPackage(t *testing.T) {
	var (
		args       = DummyArgs(t)
		mockStdOut strings.Builder
		mockStdErr strings.Builder
		outputDir  = t.TempDir()
		pkgFile    = DUMMY_PKG3 + "-" + DUMMY_VERSION + "-noarch-1_SBo.txz"
	)
	oldOutput, hadOutput := os.LookupEnv("OUTPUT")
	os.Setenv("OUTPUT", outputDir)
	defer func() {
		if hadOutput {
			os.Setenv("OUTPUT", oldOutput)
		} else {
			os.Unsetenv("OUTPUT")
		}
	}()

	os.Remove(filepath.Join(args.TmpDir, pkgFile))
	must.Content2File(filepath.Join(outputDir, pkgFile), "")
	args.Ostream = &mockStdOut
	args.Estream = &mockStdErr
	args.Argv = []string{"pkg", "-" + YES_FLAG, DUMMY_PKG3}
	args.InstallCmd = MOCK_INSTALL + " "

	Run(args)

	mustContain(t, mockStdOut.String(), "All packages installed successfully")
	mustContain(t,
		string(must.ReadFile(filepath.Join(dummyPkg3.Path(), "Install.log"))),
		filepath.Join(outputDir, pkgFile),
	)
}

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.DefaultPkgFile, packages)
	args.Ostream = &mockStdOut
	args.Istream = strings.NewReader(input)
	args.Estream = &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.TmpDir
}

func TestSkipBlacklistUpdate(t *testing.T) {
	var (
		args       = DummyArgs(t)
		mockStdOut strings.Builder
		mockStdErr strings.Builder
	)

	args.Ostream = &mockStdOut
	args.Estream = &mockStdErr
	must.Chmod(args.Blacklist, 0o444) // Make it read-only

	Run(args)

	mustContain(t, mockStdErr.String(), "Skipping blacklist update", "open "+args.Blacklist+": permission denied")

}

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.Ostream = &mockStdOut
	args.DefaultPkgFile = pkgFile
	args.RepoPath = filepath.Join(t.TempDir(), "doesnotexist")
	args.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.Ostream = &mockStdOut
	args.Argv = []string{"pkg", "-" + LIST_INSTALLED_FLAG}
	args.DocDir = docDir
	must.Mkdir(filepath.Join(docDir, DUMMY_PKG1+"-dummy_version"))
	Run(args)
	mustBeEqual(t, mockStdOut.String(), DUMMY_PKG1+"\n")
}
