package testenv

import (
	"context"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/kopia/kopia/internal/testutil"
)

// CLIExeRunner is a CLIExeRunner that invokes the commands via external executable.
type CLIExeRunner struct {
	Exe               string
	PassthroughStderr bool       // this is for debugging only
	NextCommandStdin  io.Reader  // this is used for stdin source tests
	ExtraFiles        []*os.File // this is used for socket-activation tests
	LogsDir           string
}

// Start implements CLIRunner.
func (e *CLIExeRunner) Start(tb testing.TB, ctx context.Context, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, interrupt func(os.Signal)) {
	tb.Helper()

	c := exec.CommandContext(ctx, e.Exe, append([]string{
		"--log-dir", e.LogsDir,
	}, args...)...)

	c.Env = append(c.Env, os.Environ()...)

	for k, v := range env {
		c.Env = append(c.Env, k+"="+v)
	}

	stdoutPipe, err := c.StdoutPipe()
	if err != nil {
		tb.Fatalf("can't set up stdout pipe reader: %v", err)
	}

	stderrPipe, err := c.StderrPipe()
	if err != nil {
		tb.Fatalf("can't set up stderr pipe reader: %v", err)
	}

	c.Stdin = e.NextCommandStdin
	e.NextCommandStdin = nil
	c.ExtraFiles = e.ExtraFiles

	if err := c.Start(); err != nil {
		tb.Fatalf("unable to start: %v", err)
	}

	return stdoutPipe, stderrPipe, c.Wait, func(sig os.Signal) {
		if sig == os.Kill {
			c.Process.Kill()

			return
		}

		c.Process.Signal(sig)
	}
}

// NewExeRunner returns a CLIRunner that will execute kopia commands by launching subprocesses
// for each. The kopia executable must be passed via KOPIA_EXE environment variable. The test
// will be skipped if it's not provided (unless running inside an IDE in which case system-wide
// `kopia` will be used by default).
func NewExeRunner(tb testing.TB) *CLIExeRunner {
	tb.Helper()

	exe := os.Getenv("KOPIA_EXE")
	if exe == "" {
		if os.Getenv("VSCODE_PID") != "" {
			// we're launched from VSCode, use system-installed kopia executable.
			exe = "kopia"
		} else {
			tb.Skip()
		}
	}

	return NewExeRunnerWithBinary(tb, exe)
}

// NewExeRunnerWithBinary returns a CLIRunner that will execute kopia commands by launching subprocesses
// for each.
func NewExeRunnerWithBinary(tb testing.TB, exe string) *CLIExeRunner {
	tb.Helper()

	// unset environment variables that disrupt tests when passed to subprocesses.
	os.Unsetenv("KOPIA_PASSWORD")

	logsDir := testutil.TempLogDirectory(tb)

	return &CLIExeRunner{
		Exe:     filepath.FromSlash(exe),
		LogsDir: logsDir,
	}
}

var _ CLIRunner = (*CLIExeRunner)(nil)
