// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: LicenseRef-MutuaL-1.2

package configvalue

import (
	"context"
	"os"
	"runtime"
	"strconv"
	"strings"
	"testing"
	"time"
)

func TestResolveACIDConfigValuesResolution1InterpolatesEnvironment(t *testing.T) {
	t.Setenv("COOKED_USERNAME_FROM_MY_SHELL", "ada")
	t.Setenv("COOKED_SUFFIX", "lovelace")

	tests := []struct {
		name   string
		config string
		want   string
	}{
		{name: "plain literal", config: "literal", want: "literal"},
		{name: "dollar prefix", config: "$COOKED_USERNAME_FROM_MY_SHELL", want: "ada"},
		{name: "braced reference", config: "${COOKED_USERNAME_FROM_MY_SHELL}-${COOKED_SUFFIX}", want: "ada-lovelace"},
		{name: "escaped dollar and bang", config: "$$$COOKED_USERNAME_FROM_MY_SHELL$!", want: "$ada!"},
	}

	resolver := NewResolver()
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, ok := resolver.Resolve(context.Background(), tt.config)
			if !ok {
				t.Fatalf("Resolve(%q) ok = false", tt.config)
			}

			if got != tt.want {
				t.Fatalf("Resolve(%q) = %q, want %q", tt.config, got, tt.want)
			}
		})
	}
}

func TestResolveACIDConfigValuesResolution1RejectsMissingEnvironment(t *testing.T) {
	resolver := NewResolver()

	got, ok := resolver.Resolve(context.Background(), "$COOKED_MISSING_VALUE")
	if ok {
		t.Fatalf("Resolve ok = true, got %q", got)
	}
}

func TestResolveACIDConfigValuesCommands3CachesCommandResults(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("shell command syntax is POSIX-specific")
	}

	resolver := NewResolver()
	command := "!printf cooked"

	first, ok := resolver.Resolve(context.Background(), command)
	if !ok {
		t.Fatal("first command resolution failed")
	}

	second, ok := resolver.Resolve(context.Background(), command)
	if !ok {
		t.Fatal("second command resolution failed")
	}

	if first != "cooked" || second != "cooked" {
		t.Fatalf("resolved command values = %q and %q, want cooked", first, second)
	}

	resolver.CommandTimeout = time.Nanosecond
	third, ok := resolver.Resolve(context.Background(), command)
	if !ok || third != "cooked" {
		t.Fatalf("cached command value = %q, %v; want cooked, true", third, ok)
	}
}

func TestResolveACIDAuthenticationCredentials17RejectsOversizedCommandStdout(t *testing.T) {
	t.Setenv("COOKED_CONFIGVALUE_FAKE_SHELL", "1")
	t.Setenv("COOKED_CONFIGVALUE_FAKE_SHELL_STDOUT_BYTES", strconv.Itoa(maxCommandStdoutBytes+1))

	resolver := NewResolver()
	resolver.shellCommand = func(string) (string, []string) {
		return os.Args[0], []string{"-test.run=TestConfigValueFakeShell"}
	}

	got, ok := resolver.Resolve(context.Background(), "!oversized")
	if ok {
		t.Fatalf("oversized command stdout resolved with length %d, want unresolved", len(got))
	}
	if got != "" {
		t.Fatalf("oversized command stdout returned non-empty value length %d, want empty", len(got))
	}
}

func TestConfigValueFakeShell(t *testing.T) {
	if os.Getenv("COOKED_CONFIGVALUE_FAKE_SHELL") != "1" {
		return
	}

	byteCount, err := strconv.Atoi(os.Getenv("COOKED_CONFIGVALUE_FAKE_SHELL_STDOUT_BYTES"))
	if err != nil {
		os.Exit(2)
	}
	if _, err := os.Stdout.WriteString(strings.Repeat("x", byteCount)); err != nil {
		os.Exit(2)
	}

	os.Exit(0)
}
