package testenv

import (
	"context"
	"fmt"
	"io"
	"os"
	"sync"
	"sync/atomic"
	"testing"

	"github.com/alecthomas/kingpin/v2"

	"github.com/kopia/kopia/cli"
)

var envPrefixCounter = new(int32)

// CLIInProcRunner is a CLIRunner that invokes provided commands in the current process.
type CLIInProcRunner struct {
	mu sync.Mutex

	// +checklocks:mu
	nextCommandStdin io.Reader // this is used for stdin source tests

	CustomizeApp func(a *cli.App, kp *kingpin.Application)
}

// Start implements CLIRunner.
func (e *CLIInProcRunner) 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()

	a := cli.NewApp()
	a.DangerousCommands = "enabled"

	envPrefix := fmt.Sprintf("T%v_", atomic.AddInt32(envPrefixCounter, 1))
	a.SetEnvNamePrefixForTesting(envPrefix)

	kpapp := kingpin.New("test", "test")

	if e.CustomizeApp != nil {
		e.CustomizeApp(a, kpapp)
	}

	e.mu.Lock()
	stdin := e.nextCommandStdin
	e.nextCommandStdin = nil
	e.mu.Unlock()

	for k, v := range env {
		os.Setenv(envPrefix+k, v)
	}

	return a.RunSubcommand(ctx, kpapp, stdin, args)
}

// SetNextStdin sets the stdin to be used on next command execution.
func (e *CLIInProcRunner) SetNextStdin(stdin io.Reader) {
	e.mu.Lock()
	defer e.mu.Unlock()

	e.nextCommandStdin = stdin
}

// NewInProcRunner returns a runner that executes CLI subcommands in the current process using cli.RunSubcommand().
func NewInProcRunner(t *testing.T) *CLIInProcRunner {
	t.Helper()

	if os.Getenv("KOPIA_EXE") != "" && os.Getenv("KOPIA_RUN_ALL_INTEGRATION_TESTS") == "" {
		t.Skip("not running test since it's also included in the unit tests")
	}

	return &CLIInProcRunner{
		CustomizeApp: func(a *cli.App, kp *kingpin.Application) {
			a.AddStorageProvider(cli.StorageProvider{
				Name:        "in-memory",
				Description: "in-memory storage backend",
				NewFlags:    func() cli.StorageFlags { return &storageInMemoryFlags{} },
			})
		},
	}
}

var _ CLIRunner = (*CLIInProcRunner)(nil)
