package infra

import (
	"strings"
	"testing"
)

func TestShellExecWhitelist(t *testing.T) {
	t.Run("echo whitelisted returns stdout", func(t *testing.T) {
		result, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{"echo", "hola"},
			BinariesAllowed: []string{"echo"},
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if result.ExitCode != 0 {
			t.Errorf("exit code: got %d, want 0", result.ExitCode)
		}
		if result.Stdout != "hola\n" {
			t.Errorf("stdout: got %q, want %q", result.Stdout, "hola\n")
		}
		if result.TimedOut {
			t.Error("should not be timed out")
		}
	})

	t.Run("binary not in whitelist rejected without spawn", func(t *testing.T) {
		_, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{"evil"},
			BinariesAllowed: []string{"echo"},
		})
		if err == nil {
			t.Fatal("expected error for non-whitelisted binary, got nil")
		}
		// Debe fallar por whitelist, no por spawn (el binario "evil" ni siquiera existe).
		if !strings.Contains(err.Error(), "not in whitelist") &&
			!strings.Contains(err.Error(), "not found in PATH") {
			t.Errorf("unexpected error message: %v", err)
		}
	})

	t.Run("timeout kills process and sets TimedOut", func(t *testing.T) {
		result, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{"sleep", "10"},
			BinariesAllowed: []string{"sleep"},
			TimeoutSeconds:  1,
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if !result.TimedOut {
			t.Error("expected TimedOut=true")
		}
		if result.ExitCode == 0 {
			t.Error("expected non-zero exit code on timeout")
		}
	})

	t.Run("empty whitelist returns error", func(t *testing.T) {
		_, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{"echo", "test"},
			BinariesAllowed: []string{},
		})
		if err == nil {
			t.Fatal("expected error for empty whitelist, got nil")
		}
		if !strings.Contains(err.Error(), "no binaries whitelisted") {
			t.Errorf("unexpected error message: %v", err)
		}
	})

	t.Run("stdin payload passes to process", func(t *testing.T) {
		result, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{"cat"},
			BinariesAllowed: []string{"cat"},
			StdinPayload:    []byte("hello registry"),
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if result.ExitCode != 0 {
			t.Errorf("exit code: got %d, want 0", result.ExitCode)
		}
		if result.Stdout != "hello registry" {
			t.Errorf("stdout: got %q, want %q", result.Stdout, "hello registry")
		}
	})

	t.Run("output exceeding MaxOutputBytes is truncated", func(t *testing.T) {
		// Genera ~100 bytes, limite de 10 → debe truncar.
		result, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{"echo", strings.Repeat("x", 100)},
			BinariesAllowed: []string{"echo"},
			MaxOutputBytes:  10,
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if !result.Truncated {
			t.Error("expected Truncated=true")
		}
		if len(result.Stdout) > 10 {
			t.Errorf("stdout length %d exceeds MaxOutputBytes 10", len(result.Stdout))
		}
	})

	t.Run("absolute path in whitelist matches resolved binary", func(t *testing.T) {
		// Buscar el path absoluto de "true" para usarlo en la whitelist.
		// /usr/bin/true o /bin/true según la distro.
		candidates := []string{"/usr/bin/true", "/bin/true"}
		truePath := ""
		for _, c := range candidates {
			if _, err := ShellExecWhitelist(ShellExecOpts{
				Cmd:             []string{c},
				BinariesAllowed: []string{c},
			}); err == nil {
				truePath = c
				break
			}
		}
		if truePath == "" {
			t.Skip("could not find absolute path for 'true'; skipping absolute-path whitelist test")
		}
		result, err := ShellExecWhitelist(ShellExecOpts{
			Cmd:             []string{truePath},
			BinariesAllowed: []string{truePath},
		})
		if err != nil {
			t.Fatalf("unexpected error with absolute path whitelist: %v", err)
		}
		if result.ExitCode != 0 {
			t.Errorf("exit code: got %d, want 0", result.ExitCode)
		}
	})
}
