package pkg

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

	"gitlab.com/M0M097/pkg/lib/utils"
)

const (
	DUMMY_PKG_FILE_CONTENT = DUMMY_PKG1 + "\n" + DUMMY_PKG2 + "\n"
	MOCK_PULL              = "echo mocking pull"
	MOCK_CLEAN             = "echo mocking clean"
	MOCK_SYNC              = "echo mocking sync"
	MOCK_DOWNLOAD          = "echo mocking download"
	MOCK_BUILD             = "echo mocking build"
	MOCK_INSTALL           = "echo mocking install"
)

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], _ = testRunHelper(t, "-"+cmd, DUMMY_PKG1, "")
		output_pkg2[i], _ = testRunHelper(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")
	mustContain(t, output_pkg1[4],
		"mocking sync",
		"mocking clean",
		"Space separated list of packages to exclude:",
		"prebuild script",
		"1 "+DUMMY_PKG2,
		"2 "+DUMMY_PKG1,
		BUILD_CMD,
		DUMMY_PKG2+": Failed to build",
		DUMMY_PKG1+": Depends on "+DUMMY_PKG2,
	)
	mustContain(t, output_pkg2[4],
		"mocking sync",
		"mocking clean",
		"Space separated list of packages to exclude:",
		"1 "+DUMMY_PKG2,
		BUILD_CMD,
		DUMMY_PKG2+": Failed to build",
	)
	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 := testRunHelper(t, "-"+UPDATE_FLAG, DUMMY_PKG3, "")
	/* ------------------------------- checks --------------------------------- */
	// 1. the download branch executed
	mustContain(t, output_pkg3, "mocking install")
	mustContain(t, output_pkg3, "mocking download")

	// 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 TestDisplayDependees(t *testing.T) {
	output, _ := testRunHelper(t, "-"+ROOT_FLAG+" "+DUMMY_PKG2, DUMMY_PKG_FILE_CONTENT, "")
	mustBeEqual(t, output, DUMMY_PKG1+"\n")
}

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

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

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

func testRunHelper(t *testing.T, cmdLine, packages, input string) (string, string) {
	var (
		repoPath, tmpDir = makeDummyRepo(t)
		configDir        = t.TempDir()
		greylist         = filepath.Join(configDir, "greylist")
		blacklist        = filepath.Join(configDir, "blacklist")
		otherBlacklist   = filepath.Join(configDir, "other_blacklist")
		pkgFile          = filepath.Join(configDir, "pkgfile")
		preBuildScript   = filepath.Join(configDir, DUMMY_PKG2+PREBUILD_EXT)
		mockStdOut       strings.Builder
		mockStdErr       strings.Builder
		downloadScript   = fmt.Sprintf(`#!/bin/sh`+"\n"+MOCK_DOWNLOAD+
			`;for f in "$@"; do touch "%s/${f##*/}"; done`, tmpDir)
		downloadCmd = filepath.Join(t.TempDir(), "mock_download.sh")
	)

	utils.Content2File(pkgFile, packages)
	utils.Content2File(greylist, DUMMY_GREYLISTED_PKG)
	utils.Content2File(blacklist, DUMMY_BLACKLISTED_PKG)
	utils.Content2File(otherBlacklist, DUMMY_OTHER_BLACKLISTED_PKG)

	utils.Content2File(preBuildScript, "#!/bin/sh\necho prebuild script")
	utils.Chmod(preBuildScript, 0o755)

	utils.Content2File(downloadCmd, downloadScript)
	utils.Chmod(downloadCmd, 0o755)

	args := append([]string{"pkg"}, strings.Split(cmdLine, " ")...)

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

	Run(
		&mockStdOut, strings.NewReader(input), &mockStdErr, // Standard streams
		args,
		repoPath, DOC_DIR, tmpDir, configDir, // Constant filepaths
		greylist, blacklist, otherBlacklist, pkgFile, // Constant filepaths
		MOCK_PULL, MOCK_SYNC, MOCK_CLEAN, downloadCmd, MOCK_INSTALL, // Commands
		"echo mocking pager: ",
	)

	return mockStdOut.String(), tmpDir
}

func TestSkipBlacklistUpdate(t *testing.T) {
	var (
		repoPath, tmpDir = makeDummyRepo(t)
		configDir        = t.TempDir()
		greylist         = filepath.Join(configDir, "greylist")
		blacklist        = filepath.Join(configDir, "blacklist")
		otherBlacklist   = filepath.Join(configDir, "other_blacklist")
		pkgFile          = filepath.Join(configDir, "pkgfile")
		preBuildScript   = filepath.Join(configDir, DUMMY_PKG2+PREBUILD_EXT)
		mockStdOut       strings.Builder
		mockStdErr       strings.Builder
	)

	utils.Content2File(pkgFile, DUMMY_PKG_FILE_CONTENT)
	utils.Content2File(greylist, DUMMY_GREYLISTED_PKG)
	utils.Content2File(otherBlacklist, DUMMY_OTHER_BLACKLISTED_PKG)

	utils.Content2File(blacklist, DUMMY_BLACKLISTED_PKG)
	utils.Chmod(blacklist, 0o444) // Make it read-only

	utils.Content2File(preBuildScript, "#!/bin/sh\necho prebuild script")
	utils.Chmod(preBuildScript, 0o755)

	Run(
		&mockStdOut, strings.NewReader(""), &mockStdErr, // Standard streams
		[]string{"pkg"},                      // args
		repoPath, DOC_DIR, tmpDir, configDir, // Constant filepaths
		greylist, blacklist, otherBlacklist, pkgFile, // Constant filepaths
		MOCK_PULL, MOCK_SYNC, MOCK_CLEAN, MOCK_DOWNLOAD, MOCK_INSTALL, // Commands
		"echo mocking pager: ",
	)

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

}

func TestRunRepoDoesNotExist(t *testing.T) {
	var mockStdOut strings.Builder

	defer func() {
		if r := recover(); r == nil {
			t.Error("Expected panic, but got none")
		}
	}()
	Run(
		&mockStdOut, os.Stdin, os.Stderr, // Standard streams
		[]string{"pkg"}, // args
		filepath.Join(t.TempDir(), "doesnotexist"), DOC_DIR, TMP_DIR, CONFIG_DIR, // Constant filepaths
		"", "", "", DEFAULT_PKG_FILE, // Constant filepaths
		MOCK_PULL+" && exit 1", MOCK_SYNC, MOCK_CLEAN, MOCK_DOWNLOAD, MOCK_INSTALL, // Commands
		"echo mocking pager: ",
	)
	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
		repoPath, tmpDir = makeDummyRepo(t)
		docDir           = t.TempDir()
		pkgFile          = filepath.Join(t.TempDir(), "pkgfile")
	)
	utils.Content2File(pkgFile, DUMMY_PKG_FILE_CONTENT)
	utils.Mkdir(filepath.Join(docDir, DUMMY_PKG1+"-dummy_version"))
	Run(
		&mockStdOut, os.Stdin, os.Stderr, // Standard streams
		[]string{"pkg", "-" + LIST_INSTALLED_FLAG}, // args
		repoPath, docDir, tmpDir, CONFIG_DIR, // Constant filepaths
		"", "", "", pkgFile, "", "", "", "", "", "",
	)
	mustBeEqual(t, mockStdOut.String(), DUMMY_PKG1+"\n")
}
