package integrationservice

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestLoadConfig(t *testing.T) {
	tokenPath := filepath.Join(t.TempDir(), "workload-token")
	if err := os.WriteFile(tokenPath, []byte("token-value\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	values := validConfigValues(tokenPath)
	config, err := LoadConfig(mapLookup(values))
	if err != nil {
		t.Fatalf("LoadConfig() error = %v", err)
	}
	if config.Runtime.WorkloadToken != "token-value" || config.Runtime.Target != values[RuntimeTargetEnv] {
		t.Fatalf("unexpected Runtime config: %+v", config.Runtime)
	}
	if config.Runtime.ClientCertificateFile != "" || config.Runtime.ClientKeyFile != "" {
		t.Fatal("optional mTLS identity must remain absent")
	}
	if config.ConnectedMail != nil {
		t.Fatal("connected-mail configuration must remain absent when no provider variables are set")
	}
}

func TestLoadConfigRejectsInvalidAuthority(t *testing.T) {
	tokenPath := filepath.Join(t.TempDir(), "workload-token")
	if err := os.WriteFile(tokenPath, []byte("token-value"), 0o600); err != nil {
		t.Fatal(err)
	}
	tests := []struct {
		name   string
		mutate func(map[string]string)
		want   string
	}{
		{name: "missing scope", mutate: func(v map[string]string) { delete(v, OrgIDEnv) }, want: OrgIDEnv},
		{name: "noncanonical scope", mutate: func(v map[string]string) { v[AppInstallationIDEnv] = " install " }, want: AppInstallationIDEnv},
		{name: "invalid address", mutate: func(v map[string]string) { v[HTTPAddressEnv] = "localhost" }, want: HTTPAddressEnv},
		{name: "ephemeral address", mutate: func(v map[string]string) { v[HTTPAddressEnv] = "127.0.0.1:0" }, want: "between 1 and 65535"},
		{name: "invalid duration", mutate: func(v map[string]string) { v[RequestTimeoutEnv] = "0s" }, want: RequestTimeoutEnv},
		{name: "partial mtls", mutate: func(v map[string]string) { v[RuntimeClientCertificateEnv] = "/cert.pem" }, want: "configured together"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			values := validConfigValues(tokenPath)
			test.mutate(values)
			_, err := LoadConfig(mapLookup(values))
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("LoadConfig() error = %v, want containing %q", err, test.want)
			}
		})
	}
}

func TestReadWorkloadTokenRejectsUnsafeFiles(t *testing.T) {
	t.Run("relative", func(t *testing.T) {
		if _, err := readWorkloadToken("token"); err == nil {
			t.Fatal("readWorkloadToken() accepted relative path")
		}
	})
	t.Run("world readable", func(t *testing.T) {
		path := filepath.Join(t.TempDir(), "token")
		if err := os.WriteFile(path, []byte("token"), 0o644); err != nil {
			t.Fatal(err)
		}
		if _, err := readWorkloadToken(path); err == nil {
			t.Fatal("readWorkloadToken() accepted world-readable token")
		}
	})
	t.Run("projected group readable symlink", func(t *testing.T) {
		root := t.TempDir()
		projected := filepath.Join(root, "..data", "token")
		if err := os.MkdirAll(filepath.Dir(projected), 0o700); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(projected, []byte("projected-token\n"), 0o440); err != nil {
			t.Fatal(err)
		}
		path := filepath.Join(root, "token")
		if err := os.Symlink(filepath.Join("..data", "token"), path); err != nil {
			t.Fatal(err)
		}
		got, err := readWorkloadToken(path)
		if err != nil || got != "projected-token" {
			t.Fatalf("readWorkloadToken(projected symlink) = %q, %v", got, err)
		}
	})
	t.Run("multiple lines", func(t *testing.T) {
		path := filepath.Join(t.TempDir(), "token")
		if err := os.WriteFile(path, []byte("one\ntwo"), 0o600); err != nil {
			t.Fatal(err)
		}
		if _, err := readWorkloadToken(path); err == nil {
			t.Fatal("readWorkloadToken() accepted multiple tokens")
		}
	})
}

func validConfigValues(tokenPath string) map[string]string {
	return map[string]string{
		HTTPAddressEnv: "127.0.0.1:8443", OrgIDEnv: "org", AppInstallationIDEnv: "install",
		AuthorizedWorkloadIDEnv: "integration", WorkloadTokenFileEnv: tokenPath,
		RuntimeTargetEnv: "runtime.internal:7443", RuntimeCAFileEnv: "/ca.pem",
		RuntimeServerNameEnv: "runtime.internal", RuntimeAttemptTimeoutEnv: "5s",
		RuntimeStartupTimeoutEnv: "30s", ShutdownTimeoutEnv: "30s",
		ReadHeaderTimeoutEnv: "5s", RequestTimeoutEnv: "30s", IdleTimeoutEnv: "1m",
	}
}

func mapLookup(values map[string]string) LookupEnv {
	return func(name string) (string, bool) {
		value, ok := values[name]
		return value, ok
	}
}
