package media_test

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"testing"

	"git.netflux.io/rob/clipper/media"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

// fixtureReader loads a fixture into a ReadCloser with the provided limit.
func fixtureReader(t *testing.T, fixturePath string, limit int64) io.ReadCloser {
	fptr, err := os.Open(fixturePath)
	require.NoError(t, err)

	// limitReader to make the mock work realistically, not intended for assertions:
	return struct {
		io.Reader
		io.Closer
	}{
		Reader: io.LimitReader(fptr, limit),
		Closer: fptr,
	}
}

// helperCommand returns a function that builds an *exec.Cmd which executes a
// test function in order to act as a mock process.
func helperCommand(t *testing.T, wantCommand, stdoutFile, stderrString string, forceExitCode int) media.CommandFunc {
	return func(ctx context.Context, name string, args ...string) *exec.Cmd {
		cs := []string{"-test.run=TestHelperProcess", "--", name}
		cs = append(cs, args...)
		cmd := exec.CommandContext(ctx, os.Args[0], cs...)
		cmd.Env = []string{
			"GO_WANT_HELPER_PROCESS=1",
			"GO_WANT_COMMAND=" + wantCommand,
			"GO_STDOUT_FILE=" + stdoutFile,
			"GO_STDERR_STRING=" + stderrString,
			"GO_FORCE_EXIT_CODE=" + strconv.Itoa(forceExitCode),
		}
		return cmd
	}
}

// TestHelperProcess is the body for the mock executable process built by
// helperCommand.
func TestHelperProcess(t *testing.T) {
	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
		return
	}

	defer func() {
		// Stop the helper process writing to stdout after the test has finished.
		// This prevents it from writing the "PASS" string which is unwanted in
		// this context.
		if !t.Failed() {
			os.Stdout, _ = os.Open(os.DevNull)
		}
	}()

	if exitCode := os.Getenv("GO_FORCE_EXIT_CODE"); exitCode != "0" {
		c, _ := strconv.Atoi(exitCode)
		os.Stderr.WriteString(os.Getenv("GO_STDERR_STRING"))
		os.Exit(c)
	}

	if wantCommand := os.Getenv("GO_WANT_COMMAND"); wantCommand != "" {
		gotCmd := strings.Split(strings.Join(os.Args, " "), " -- ")[1]
		if wantCommand != gotCmd {
			fmt.Fprintf(os.Stderr, "GO_WANT_COMMAND assertion failed:\nwant = %v\ngot =  %v", wantCommand, gotCmd)
			t.Fail() // necessary to make the test fail
		}
	}

	// Copy stdin to /dev/null. This is required to avoid broken pipe errors in
	// the tests:
	_, err := io.Copy(io.Discard, os.Stdin)
	require.NoError(t, err)

	// If an output file is provided, then copy that to stdout:
	if fname := os.Getenv("GO_STDOUT_FILE"); fname != "" {
		fptr, err := os.Open(fname)
		require.NoError(t, err)
		defer fptr.Close()

		_, err = io.Copy(os.Stdout, fptr)
		require.NoError(t, err)
	}
}

// testLogger returns a functional development logger.
//lint:ignore U1000 helper method
func testLogger(t *testing.T) *zap.Logger {
	l, err := zap.NewDevelopment()
	require.NoError(t, err)
	return l
}
