package cmd

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

// fakeEngine emulates podman: info succeeds, images are absent unless named
// "present", pull reports up-to-date, run exits 7.
const fakeEngine = `#!/bin/sh
echo "$@" >> "$FAKE_LOG"
case "$1" in
  info) exit 0 ;;
  image) [ "$3" = "present" ] && exit 0; exit 1 ;;
  pull) echo "Status: Image is up to date for $2"; exit 0 ;;
  ps) printf 'abc123\timg-exited\texited\tc1\ndef456\timg-running\trunning\tc2\n' ;;
  stop|rm|rmi) exit 0 ;;
  run) exit 7 ;;
esac
exit 0
`

func TestRunContainerEndToEnd(t *testing.T) {
	log := fakeRuntime(t, "podman", fakeEngine)
	persistence := filepath.Join(t.TempDir(), "persistence")
	app, _, _ := testApp(t, map[string]string{"NEST_PERSISTENCE_DIR": persistence})

	code := app.Run([]string{"run", "nmap", "--container", "--", "-sV", "10.0.0.1"})
	if code != 7 {
		t.Errorf("exit = %d, want the tool's exit code 7", code)
	}

	calls := readLog(t, log)
	if !strings.Contains(calls, "pull docker.io/parrotsec/security") {
		t.Errorf("missing image should be pulled first:\n%s", calls)
	}
	runLine := ""
	for _, line := range strings.Split(calls, "\n") {
		if strings.HasPrefix(line, "run ") {
			runLine = line
		}
	}
	for _, want := range []string{
		"--rm", "-i",
		"--label io.parrot.nest.managed=true",
		"--label io.parrot.nest.tool=nmap",
		"-v " + persistence + ":/persistence",
		"--workdir /persistence",
		"--network host", "--cap-add NET_RAW",
		"docker.io/parrotsec/security /usr/bin/nmap -sV 10.0.0.1",
	} {
		if !strings.Contains(runLine, want) {
			t.Errorf("run invocation missing %q:\n%s", want, runLine)
		}
	}
	if strings.Contains(runLine, "-t") {
		t.Errorf("-t must not be passed without a terminal:\n%s", runLine)
	}
	if st, err := os.Stat(persistence); err != nil || !st.IsDir() {
		t.Error("nest must create the persistence dir before invoking the runtime")
	}
}

func TestRunContainerSkipsPullWhenPresent(t *testing.T) {
	log := fakeRuntime(t, "podman", fakeEngine)
	app, _, _ := testApp(t, map[string]string{"NEST_PERSISTENCE_DIR": t.TempDir()})

	code := app.Run([]string{"run", "--container", "--image", "present", "nmap"})
	if code != 7 {
		t.Errorf("exit = %d, want 7", code)
	}
	if strings.Contains(readLog(t, log), "pull") {
		t.Error("present image must not be pulled")
	}
}

func TestRunLocalNotInstalled(t *testing.T) {
	app, _, stderr := testApp(t, nil)
	code := app.Run([]string{"run", "nmap", "--local"})
	if code != exitNotFound {
		t.Errorf("exit = %d, want 3", code)
	}
	if !strings.Contains(stderr.String(), "try: nest run nmap --container") {
		t.Errorf("stderr should hint the container fallback:\n%s", stderr.String())
	}
}

func TestRunContainerNoImage(t *testing.T) {
	app, _, stderr := testApp(t, nil)
	code := app.Run([]string{"run", "wireshark", "--container"})
	if code != exitNotFound {
		t.Errorf("exit = %d, want 3", code)
	}
	if !strings.Contains(stderr.String(), "sudo apt install wireshark") {
		t.Errorf("stderr should hint apt for imageless tools:\n%s", stderr.String())
	}
}

func TestRunContainerNoImageWithImageFlag(t *testing.T) {
	fakeRuntime(t, "podman", fakeEngine)
	app, _, _ := testApp(t, map[string]string{"NEST_PERSISTENCE_DIR": t.TempDir()})
	// --image explicitly overrides the missing catalog image (§2.3)
	code := app.Run([]string{"run", "wireshark", "--container", "--image", "present"})
	if code != 7 {
		t.Errorf("exit = %d, want 7", code)
	}
}

func TestRunLocalNotInstalledNoImage(t *testing.T) {
	app, _, stderr := testApp(t, nil)
	code := app.Run([]string{"run", "wireshark", "--local"})
	if code != exitNotFound {
		t.Errorf("exit = %d, want 3", code)
	}
	if !strings.Contains(stderr.String(), "sudo apt install wireshark") {
		t.Errorf("stderr should hint apt when no container exists:\n%s", stderr.String())
	}
}

func TestRunUnknownTool(t *testing.T) {
	app, _, _ := testApp(t, nil)
	if code := app.Run([]string{"run", "aircrack-ng", "--container"}); code != exitNotFound {
		t.Errorf("exit = %d, want 3", code)
	}
}

func TestRunContainerInsideContainer(t *testing.T) {
	app, _, _ := testApp(t, map[string]string{"container": "podman"})
	if code := app.Run([]string{"run", "nmap", "--container"}); code != exitRuntimeFail {
		t.Errorf("exit = %d, want 4 inside a container", code)
	}
}

func TestRunConflictingModes(t *testing.T) {
	app, _, _ := testApp(t, nil)
	if code := app.Run([]string{"run", "nmap", "--local", "--container"}); code != exitUsage {
		t.Errorf("exit = %d, want 2", code)
	}
}

func TestRunUnknownFlagBeforeSeparator(t *testing.T) {
	app, _, stderr := testApp(t, nil)
	if code := app.Run([]string{"run", "nmap", "-sV"}); code != exitUsage {
		t.Errorf("exit = %d, want 2 (flags need --)", code)
	}
	if !strings.Contains(stderr.String(), "use -- to pass flags") {
		t.Errorf("stderr should explain the -- convention:\n%s", stderr.String())
	}
}

func TestRunNoRuntime(t *testing.T) {
	t.Setenv("PATH", t.TempDir()) // no podman/docker anywhere
	app, _, stderr := testApp(t, nil)
	code := app.Run([]string{"run", "nmap", "--container"})
	if code != exitRuntimeFail {
		t.Errorf("exit = %d, want 4", code)
	}
	if !strings.Contains(stderr.String(), "install podman or docker") {
		t.Errorf("stderr should be actionable:\n%s", stderr.String())
	}
}

func TestParseRunArgsInterspersed(t *testing.T) {
	ra, err := parseRunArgs([]string{"--container", "nmap", "--runtime=podman", "--", "-sV", "-p-", "host"})
	if err != nil {
		t.Fatalf("parseRunArgs: %v", err)
	}
	if !ra.container || ra.runtimeName != "podman" || ra.tool != "nmap" {
		t.Errorf("parsed %+v", ra)
	}
	if strings.Join(ra.toolArgs, " ") != "-sV -p- host" {
		t.Errorf("toolArgs = %q", ra.toolArgs)
	}
}
