package pkg

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

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

const newSettingsInvalidFlagEnv = "PKG_TEST_NEW_SETTINGS_INVALID_FLAG"
const newSettingsDefaultFileEnv = "PKG_TEST_NEW_SETTINGS_DEFAULT_FILE"

func TestNewSettingsInvalidFlagHelper(t *testing.T) {
	if os.Getenv(newSettingsInvalidFlagEnv) == "" {
		return
	}

	_, _ = NewSettings([]string{"/usr/bin/pkg", "-z"}, os.Getenv(newSettingsDefaultFileEnv))
}

func mustNewSettings(t *testing.T, args []string, defaultFile string) Settings {
	t.Helper()

	s, err := NewSettings(args, defaultFile)
	if err != nil {
		t.Fatal(err)
	}
	return s
}

func assertNewSettingsError(t *testing.T, args []string, defaultFile string, expectedMessages ...string) {
	t.Helper()

	_, err := NewSettings(args, defaultFile)
	if err == nil {
		t.Fatal("expected NewSettings to return an error")
	}
	errMsg := err.Error()
	for _, expectedMessage := range expectedMessages {
		if !strings.Contains(errMsg, expectedMessage) {
			t.Fatalf("expected error to contain %q, got:\n%s", expectedMessage, errMsg)
		}
	}
}

func TestNewSettings_Commands(t *testing.T) {
	// We test that each mutually exclusive command sets the correct Cmd field

	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")

	t.Run("LIST_INSTALLED", func(t *testing.T) {
		args := []string{"test", "-" + LIST_INSTALLED_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandListInstalled)
	})

	t.Run("DEPENDEES", func(t *testing.T) {
		args := []string{"test", "-" + ROOT_FLAG, "rootPackage"}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandDependents)
		mustBeEqual(t, s.Root, "rootPackage")
	})

	t.Run("SHOW_VERSION", func(t *testing.T) {
		args := []string{"test", "-" + VERSION_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandShowVersion)
	})

	t.Run("SEARCH", func(t *testing.T) {
		args := []string{"test", "-" + SEARCH_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandSearch)
	})

	t.Run("TRACKING", func(t *testing.T) {
		args := []string{"test", "-" + TRACK_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandTracking)
	})

	t.Run("VIEW", func(t *testing.T) {
		args := []string{"test", "-" + VIEW_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandView)
	})

	t.Run("VIEW_BUILD", func(t *testing.T) {
		args := []string{"test", "-" + VIEWBUILD_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandViewBuild)
	})

	t.Run("DEFAULT_UPGRADE", func(t *testing.T) {
		// If none of the “command” flags are set and root is empty => UPGRADE
		args := []string{"test"}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Cmd, CommandUpgrade)
	})
}

func TestNewSettings_MutuallyExclusiveCommands(t *testing.T) {
	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")
	assertNewSettingsError(t,
		[]string{"test", "-" + VIEW_FLAG, "-" + SEARCH_FLAG},
		defaultFile,
		"At most one of",
	)
}

func TestNewSettings_FileFlagAndPositionalArgs(t *testing.T) {
	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")
	t.Run("ErrorIfFileFlagAndPositionalArgsUsedTogether", func(t *testing.T) {
		assertNewSettingsError(t,
			[]string{"test", "-" + FILE_FLAG, "someOtherFile", "pkg1", "pkg2"},
			defaultFile,
			"Cannot use positional arguments and -f together",
		)
	})

	t.Run("LoadsDefaultFileWithoutPositionalArgs", func(t *testing.T) {
		// This should be fine: no positional args => we load from defaultFile
		args := []string{"test", "-" + FILE_FLAG, defaultFile}
		s := mustNewSettings(t, args, defaultFile)
		if len(s.PkgNames) == 2 {
			// By default, if pkgNames is empty, we read from file
		} else {
			t.Errorf("Expected no pkgNames if none are specified, got %v", s.PkgNames)
		}
	})
}

func TestNewSettings_BooleanFlags(t *testing.T) {
	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")
	t.Run("MD5Flag", func(t *testing.T) {
		// By default, checkMD5Sum = true, but if we pass -md5 then it's toggled to false
		args := []string{"test", "-" + MD5_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if s.CheckMD5Sum {
			t.Errorf("Expected checkMD5Sum to be false, got true (md5 flag was set)")
		}
	})

	t.Run("NoDeps", func(t *testing.T) {
		args := []string{"test", "-" + NO_DEPS_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if !s.NoDeps {
			t.Errorf("Expected noDeps to be true, got false")
		}
	})

	t.Run("FilterInstalled", func(t *testing.T) {
		// The code has filterInstalled as !*filterInstalledPtr
		// meaning the default is true, but if we pass the flag, we flip it.
		args := []string{"test", "-" + FILTERINSTALLED_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if s.FilterInstalled {
			t.Errorf("Expected filterInstalled to be false, got true")
		}
	})

	t.Run("RespectGreylist", func(t *testing.T) {
		// The code sets s.RespectGreylist = !*skipGreylistPtr
		// If we pass -respect-greylist (skipGreylistPtr = true), then respectGreylist = false
		args := []string{"test", "-" + RESPECTGREYLIST_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if s.RespectGreylist {
			t.Errorf("Expected respectGreylist=false, got true")
		}
	})

	t.Run("Update", func(t *testing.T) {
		args := []string{"test", "-" + UPDATE_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if !s.Update {
			t.Errorf("Expected update=true, got false")
		}
	})

	t.Run("Yes", func(t *testing.T) {
		args := []string{"test", "-" + YES_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if !s.Yes {
			t.Errorf("Expected yes=true, got false")
		}
	})

	t.Run("JobsDefault", func(t *testing.T) {
		args := []string{"test"}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Jobs, NUM_DEFAULT_JOBS)
	})

	t.Run("JobsCustom", func(t *testing.T) {
		args := []string{"test", "-" + JOBS_FLAG, "3"}
		s := mustNewSettings(t, args, defaultFile)
		mustBeEqual(t, s.Jobs, 3)
	})
}

func TestNewSettings_InvalidJobs(t *testing.T) {
	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")

	assertNewSettingsError(t,
		[]string{"test", "-" + JOBS_FLAG, "0"},
		defaultFile,
		"-j must be at least 1, got 0",
	)
}

func TestNewSettings_InvalidFlagMentionsManpage(t *testing.T) {
	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")

	cmd := exec.Command(os.Args[0], "-test.run=^TestNewSettingsInvalidFlagHelper$")
	cmd.Env = append(os.Environ(),
		newSettingsInvalidFlagEnv+"=1",
		newSettingsDefaultFileEnv+"="+defaultFile,
	)
	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatal("expected NewSettings to exit for invalid flag")
	}
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		t.Fatalf("expected an exit error, got %T: %v", err, err)
	}
	if exitErr.ExitCode() != 2 {
		t.Fatalf("expected exit code 2, got %d\nOutput:\n%s", exitErr.ExitCode(), output)
	}
	mustContain(t, string(output), "flag provided but not defined: -z", "man pkg")
}

func TestNewSettings_MissingDefaultPkgFile(t *testing.T) {
	defaultFile := t.TempDir() + "/pkg_list.txt"

	assertNewSettingsError(t,
		[]string{"test"},
		defaultFile,
		"open "+defaultFile+": no such file or directory",
		"This is probably your first usage of `pkg` read `man pkg`.",
	)
}

func TestNewSettings_NoDepsRejectedForGraphCommands(t *testing.T) {
	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")

	assertNewSettingsError(t,
		[]string{"test", "-" + NO_DEPS_FLAG, "-" + TRACK_FLAG},
		defaultFile,
		"At most one of",
		"-"+NO_DEPS_FLAG,
		"-"+TRACK_FLAG,
	)
	assertNewSettingsError(t,
		[]string{"test", "-" + NO_DEPS_FLAG, "-" + ROOT_FLAG, "pkg1"},
		defaultFile,
		"At most one of",
		"-"+NO_DEPS_FLAG,
		"-"+ROOT_FLAG,
	)
}

func TestNewSettings_UpdateBlacklist(t *testing.T) {
	// s.UpdateBlacklist gets set if:
	//   (filePtr == DefaultPkgFile) && pkgs_from_file && !noDepsPtr
	// That means:
	//   1) -f not used or is defaultFile
	//   2) pkgNames are empty => loaded from file => pkgs_from_file = true
	//   3) -no-deps not passed

	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")

	t.Run("UpdateBlacklist_True", func(t *testing.T) {
		args := []string{"test"} // no commands => upgrade, no file override
		// No positional arguments => pkgs_from_file is true
		// filePtr == DefaultPkgFile => let's say defaultFile
		s := mustNewSettings(t, args, defaultFile)
		if !s.UpdateBlacklist {
			t.Errorf("Expected updateBlacklist=true under these conditions, got false")
		}
	})

	t.Run("UpdateBlacklist_False_When_CustomFile", func(t *testing.T) {
		// If we use -f customFile, then (filePtr == DefaultPkgFile) is false
		// => s.UpdateBlacklist should be false
		customFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")
		args := []string{"test", "-" + FILE_FLAG, customFile}
		s := mustNewSettings(t, args, defaultFile)
		if s.UpdateBlacklist {
			t.Errorf("Expected updateBlacklist=false when using -f customFile, got true")
		}
	})

	t.Run("UpdateBlacklist_False_When_PositionalArgs", func(t *testing.T) {
		// If we pass positional arguments => pkgs_from_file = false
		// => s.UpdateBlacklist = false
		args := []string{"test", "pkg1", "pkg2"}
		s := mustNewSettings(t, args, defaultFile)
		if s.UpdateBlacklist {
			t.Errorf("Expected updateBlacklist=false when positional arguments are provided, got true")
		}
	})

	t.Run("UpdateBlacklist_False_When_NoDeps", func(t *testing.T) {
		args := []string{"test", "-" + NO_DEPS_FLAG}
		s := mustNewSettings(t, args, defaultFile)
		if s.UpdateBlacklist {
			t.Errorf("Expected updateBlacklist=false when noDeps is set, got true")
		}
	})
}

func TestNewSettings_PkgNamesAndBuildOptions(t *testing.T) {
	// Suppose we pass multiple packages with build options

	defaultFile := must.Content2File(t.TempDir()+"/pkglist", "pkg1\npkg2\n")

	args := []string{"test", "pkg1", "OPT1=val1", "pkg2", "OPT2=val2"}
	s := mustNewSettings(t, args, defaultFile)
	pkgs := s.PkgNames

	mustBeEqual(t, len(pkgs), 2)
	mustBeEqual(t, pkgs[0], "pkg1")
	mustBeEqual(t, pkgs[1], "pkg2")
	mustBeEqualStringSlices(t, s.BuildOptions["pkg1"], []string{"OPT1=val1"})
	mustBeEqualStringSlices(t, s.BuildOptions["pkg2"], []string{"OPT2=val2"})
}

func TestPkgNamesAndBuildOptions(t *testing.T) {
	input := []string{"foo", "bar=baz"}
	names, buildOptions := pkgNamesAndBuildOptions(input)
	if len(names) != 1 || names[0] != "foo" {
		t.Error("pkgNamesAndBuildOptions() returned incorrect package names")
	}
	if len(buildOptions) != 1 || buildOptions["foo"][0] != "bar=baz" {
		t.Error("pkgNamesAndBuildOptions() returned incorrect build options")
	}
	t.Log("pkgNamesAndBuildOptions() test passed with names:", names, "and build options:", buildOptions)
}
