package memexec

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"testing"
	"time"
)

func TestCommand(t *testing.T) {
	exe := newEchoExec(t)
	defer func() {
		if err := exe.Close(); err != nil {
			t.Fatalf("close error: %s", err)
		}
	}()

	c := exe.Command("test")
	if have := runCommand(t, c); !strings.HasPrefix(have, "test") {
		t.Errorf("command output = %q doesn't contain %q", have, "test")
	}
}

func TestCommand_Shell(t *testing.T) {
	if runtime.GOOS == "windows" {
		return
	}

	exe, err := New([]byte("#!/bin/sh\necho hello"))
	if err != nil {
		panic(err)
	}
	defer exe.Close()

	b, err := exe.Command().CombinedOutput()
	if err != nil {
		t.Fatalf("err = %v, output = %q", err, b)
	}
	if !bytes.Equal(b, []byte("hello\n")) {
		t.Fatalf("output = %q, want %q", string(b), []byte("hello\n"))
	}
}

func TestCommandContext(t *testing.T) {
	// the test is failing on windows, probably due to missing sleep command,
	// unfortunately I have no windows machines around to fix this
	if runtime.GOOS == "windows" {
		return
	}

	exe := newSleepExec(t)
	defer func() {
		if err := exe.Close(); err != nil {
			t.Fatalf("close error: %s", err)
		}
	}()

	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
	defer cancel()

	c := exe.CommandContext(ctx, "1")

	start := time.Now()
	if err := c.Start(); err != nil {
		t.Fatalf("start failed: %s", err)
	}
	if err := c.Wait(); err != nil && err.Error() != "signal: killed" {
		t.Fatalf("command execution failed: %s", err)
	}
	stop := time.Now()
	if delta := stop.Sub(start); delta > time.Millisecond*600 || delta < time.Millisecond*500 {
		t.Fatalf("unexpected command execution time: delta=%s", delta)
	}
}

func BenchmarkCommand(b *testing.B) {
	exe := newEchoExec(b)
	defer func() {
		if err := exe.Close(); err != nil {
			b.Fatalf("close error: %s", err)
		}
	}()

	for i := 0; i < b.N; i++ {
		cmd := exe.Command("test")
		if _, err := cmd.Output(); err != nil {
			b.Fatal(err)
		}
	}
}

func newExec(t testing.TB, name string) *Exec {
	path, err := exec.LookPath(name)
	if err != nil {
		t.Fatal(err)
	}
	b, err := os.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}

	exe, err := New(b)
	if err != nil {
		t.Fatal(err)
	}
	return exe
}

func newEchoExec(t testing.TB) *Exec {
	// lookup echo binary that is provided on all unix systems
	// and it's not a built-in opposed to `ls` and `type`
	return newExec(t, "echo")
}

func newSleepExec(t testing.TB) *Exec {
	return newExec(t, "sleep")
}

func runCommand(t *testing.T, cmd *exec.Cmd) string {
	t.Helper()
	b, err := cmd.CombinedOutput()
	if err != nil {
		if b != nil {
			t.Fatalf("failed to run: %q", string(b))
		}
		t.Fatal(err)
	}
	return string(b)
}
