package helper_test

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"reflect"
	"strconv"
	"strings"
	"syscall"
	"testing"
	"time"

	"hakurei.app/internal/helper"
)

var (
	wantArgs = []string{
		"unix:path=/run/dbus/system_bus_socket",
		"/tmp/hakurei.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket",
		"--filter",
		"--talk=org.bluez",
		"--talk=org.freedesktop.Avahi",
		"--talk=org.freedesktop.UPower",
	}

	wantPayload = strings.Join(wantArgs, "\x00") + "\x00"
	argsWt      = helper.MustNewCheckedArgs(wantArgs...)
)

func argF(argsFd, statFd int) []string {
	if argsFd == -1 {
		panic("invalid args fd")
	}

	return argFChecked(argsFd, statFd)
}

func argFChecked(argsFd, statFd int) (args []string) {
	args = make([]string, 0, 6)
	if argsFd > -1 {
		args = append(args, "--args", strconv.Itoa(argsFd))
	}
	if statFd > -1 {
		args = append(args, "--fd", strconv.Itoa(statFd))
	}
	return
}

const (
	containerTimeout = 30 * time.Second
)

// this function tests an implementation of the helper.Helper interface
func testHelper(t *testing.T, createHelper func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper) {
	oldWaitDelay := helper.WaitDelay
	helper.WaitDelay = 30 * time.Second
	t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })

	t.Run("start helper with status channel and wait", func(t *testing.T) {
		ctx, cancel := context.WithTimeout(t.Context(), containerTimeout)
		stdout := new(strings.Builder)
		h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, true)

		t.Run("wait not yet started helper", func(t *testing.T) {
			if err := h.Wait(); !reflect.DeepEqual(err, syscall.EINVAL) &&
				!reflect.DeepEqual(err, errors.New("exec: not started")) {
				t.Errorf("Wait: error = %v", err)
			}
		})

		t.Log("starting helper stub")
		if err := h.Start(); err != nil {
			t.Errorf("Start: error = %v", err)
			cancel()
			return
		}
		t.Log("cancelling context")
		cancel()

		t.Run("start already started helper", func(t *testing.T) {
			wantErr := "helper: already started"
			if err := h.Start(); err != nil && err.Error() != wantErr {
				t.Errorf("Start: error = %v, wantErr %v",
					err, wantErr)
				return
			}
		})

		t.Log("waiting on helper")
		if err := h.Wait(); !errors.Is(err, context.Canceled) {
			t.Errorf("Wait: error = %v",
				err)
		}

		t.Run("wait already finalised helper", func(t *testing.T) {
			wantErr := "exec: Wait was already called"
			if err := h.Wait(); err != nil && err.Error() != wantErr {
				t.Errorf("Wait: error = %v, wantErr %v",
					err, wantErr)
				return
			}
		})

		if got := trimStdout(stdout); got != wantPayload {
			t.Errorf("Start: stdout = %q, want %q",
				got, wantPayload)
		}
	})

	t.Run("start helper and wait", func(t *testing.T) {
		ctx, cancel := context.WithTimeout(t.Context(), containerTimeout)
		defer cancel()
		stdout := new(strings.Builder)
		h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, false)

		if err := h.Start(); err != nil {
			t.Errorf("Start: error = %v",
				err)
			return
		}

		if err := h.Wait(); err != nil {
			t.Errorf("Wait: error = %v stdout = %q",
				err, stdout)
		}

		if got := trimStdout(stdout); got != wantPayload {
			t.Errorf("Start: stdout = %q, want %q",
				got, wantPayload)
		}
	})
}

func trimStdout(stdout fmt.Stringer) string {
	return strings.TrimPrefix(stdout.String(), "=== RUN   TestHelperInit\n")
}
