package wand

import (
	"bytes"
	"fmt"
	"io"
	"strings"
	"testing"
)

type testCase struct {
	impl           any
	stdin          string
	conf           string
	mergeConfTyped []Config
	mergeConf      []string
	env            string
	command        string
	contains       bool
}

func testExec(test testCase, err string, expect ...string) func(*testing.T) {
	return func(t *testing.T) {
		var exitCode int
		exit := func(code int) { exitCode = code }

		var stdin io.Reader
		if test.stdin == "" {
			stdin = bytes.NewBuffer(nil)
		} else {
			stdin = bytes.NewBufferString(test.stdin)
		}

		stdout := bytes.NewBuffer(nil)
		stderr := bytes.NewBuffer(nil)
		cmd := wrap(test.impl)
		e := strings.Split(test.env, ";")
		a := strings.Split(test.command, " ")

		zeroOrOne := func(b ...bool) bool {
			var one bool
			for _, bi := range b {
				if bi && one {
					return false
				}

				if bi {
					one = true
				}
			}

			return true
		}

		if !zeroOrOne(test.conf != "", len(test.mergeConf) > 0, len(test.mergeConfTyped) > 0) {
			t.Fatal("test error: conflicting test config")
		}

		var conf Config
		if test.conf != "" {
			conf = Config{test: test.conf}
		} else if len(test.mergeConf) > 0 {
			var c []Config
			for _, cs := range test.mergeConf {
				c = append(c, Config{test: cs})
			}

			conf = MergeConfig(c...)
		} else if len(test.mergeConfTyped) > 0 {
			conf = MergeConfig(test.mergeConfTyped...)
		}

		exec(stdin, stdout, stderr, exit, cmd, conf, e, a)
		if exitCode != 0 && err == "" {
			t.Fatal("non-zero exit code:", stderr.String())
		}

		if err != "" && exitCode == 0 {
			t.Fatal("failed to fail")
		}

		if err != "" && !strings.Contains(stderr.String(), err) {
			t.Fatal("expected error not received:", stderr.String())
		}

		if exitCode != 0 {
			return
		}

		var expstr []string
		for _, e := range expect {
			expstr = append(expstr, fmt.Sprint(e))
		}

		output := stdout.String()
		if output == "" || output[len(output)-1] != '\n' {
			output = output + "\n"
		}

		checkOutput := func() bool {
			return output == strings.Join(expstr, "\n")+"\n"
		}

		if test.contains {
			checkOutput = func() bool {
				for _, e := range expstr {
					if !strings.Contains(output, e) {
						return false
					}
				}

				return true
			}
		}

		if !checkOutput() {
			t.Fatal("unexpected output:", output)
		}
	}
}
