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

package appconfig

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

	"git.secluded.site/cooked-mcp/internal/configvalue"
)

func TestLoadACIDAuthenticationCredentials21ResolvesTOMLConfigValues(t *testing.T) {
	t.Setenv("COOKED_USERNAME_FROM_MY_SHELL", "shell-user")

	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "$COOKED_USERNAME_FROM_MY_SHELL"
password = "!printf shell-password"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.Username != "shell-user" {
		t.Fatalf("Username = %q, want shell-user", got.Username)
	}
	if got.Password != "shell-password" {
		t.Fatalf("Password = %q, want shell-password", got.Password)
	}
}

func TestLoadACIDAuthenticationCredentials11EnvironmentOverridesTOML(t *testing.T) {
	t.Setenv("COOKED_LOGIN_USERNAME", "override-user")
	t.Setenv("COOKED_LOGIN_PASSWORD", "override-password")

	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.Username != "override-user" {
		t.Fatalf("Username = %q, want override-user", got.Username)
	}
	if got.Password != "override-password" {
		t.Fatalf("Password = %q, want override-password", got.Password)
	}
}

func TestLoadDefaultsHTTPAddr(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.HTTPAddr != "127.0.0.1:8123" {
		t.Fatalf("HTTPAddr = %q, want default loopback address", got.HTTPAddr)
	}
}

// server.CONFIG.8-2
func TestLoadACIDServerConfig8_2LoadsTOMLHTTPAddr(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"

[mcp]
http_addr = "127.0.0.1:9000"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.HTTPAddr != "127.0.0.1:9000" {
		t.Fatalf("HTTPAddr = %q, want TOML address", got.HTTPAddr)
	}
}

// server.CONFIG.3 server.CONFIG.8-1
func TestLoadACIDServerConfig3And8_1EnvironmentOverridesTOMLHTTPAddr(t *testing.T) {
	t.Setenv("COOKED_MCP_HTTP_ADDR", "127.0.0.1:9001")

	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"

[mcp]
http_addr = "127.0.0.1:9000"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.HTTPAddr != "127.0.0.1:9001" {
		t.Fatalf("HTTPAddr = %q, want environment address", got.HTTPAddr)
	}
}

// server.SECURITY.4 server.SECURITY.9
func TestLoadACIDServerSecurity4And9EnvironmentOverridesTOMLHTTPToken(t *testing.T) {
	t.Setenv("COOKED_MCP_HTTP_TOKEN", "env-token")

	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"

[mcp]
http_token = "toml-token"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.HTTPToken != "env-token" {
		t.Fatalf("HTTPToken = %q, want environment token", got.HTTPToken)
	}
}

// server.SECURITY.5 server.SECURITY.10 server.SECURITY.12 server.SECURITY.13
func TestLoadACIDServerSecurity5And10ResolvesTOMLHTTPTokenConfigValue(t *testing.T) {
	t.Setenv("TEST_HTTP_TOKEN", "shell-token")

	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"

[mcp]
http_token = "$TEST_HTTP_TOKEN"
`)

	got, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if got.HTTPToken != "shell-token" {
		t.Fatalf("HTTPToken = %q, want resolved TOML token", got.HTTPToken)
	}
}

// server.SECURITY.11
func TestLoadACIDServerSecurity11RejectsEmptyEnvironmentHTTPToken(t *testing.T) {
	t.Setenv("COOKED_MCP_HTTP_TOKEN", "")

	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"
`)

	_, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err == nil {
		t.Fatal("Load() error = nil, want empty HTTP token error")
	}
	if !strings.Contains(err.Error(), "MCP HTTP token must not be empty") {
		t.Fatalf("Load() error = %v, want empty HTTP token error", err)
	}
}

// server.SECURITY.11
func TestLoadACIDServerSecurity11RejectsEmptyTOMLHTTPToken(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"

[mcp]
http_token = ""
`)

	_, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err == nil {
		t.Fatal("Load() error = nil, want empty HTTP token error")
	}
	if !strings.Contains(err.Error(), "MCP HTTP token must not be empty") {
		t.Fatalf("Load() error = %v, want empty HTTP token error", err)
	}
}

func TestLoadRejectsMissingExplicitConfigFile(t *testing.T) {
	_, err := Load(context.Background(), filepath.Join(t.TempDir(), "missing.toml"), configvalue.NewResolver())
	if err == nil {
		t.Fatal("Load() error = nil, want missing config error")
	}
}

func TestLoadACIDServerConfig72RejectsMissingDefaultConfigHome(t *testing.T) {
	t.Setenv("XDG_CONFIG_HOME", "")

	originalUserHomeDir := userHomeDir
	userHomeDir = func() (string, error) {
		return "", os.ErrNotExist
	}
	t.Cleanup(func() {
		userHomeDir = originalUserHomeDir
	})

	_, err := Load(context.Background(), "", configvalue.NewResolver())
	if err == nil {
		t.Fatal("Load() error = nil, want default config path error")
	}
	if !strings.Contains(err.Error(), "resolve default config path") {
		t.Fatalf("Load() error = %v, want default config path error", err)
	}
}

func TestLoadRejectsNonLocalHTTPBaseURL(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.toml")
	writeConfig(t, configPath, `[user]
name = "toml-user"
password = "toml-password"

[cooked]
base_url = "http://example.com"
`)

	_, err := Load(context.Background(), configPath, configvalue.NewResolver())
	if err == nil {
		t.Fatal("Load() error = nil, want non-local HTTP base URL error")
	}
	if !strings.Contains(err.Error(), "must use https") {
		t.Fatalf("Load() error = %v, want https error", err)
	}
}

func writeConfig(t *testing.T, path, content string) {
	t.Helper()

	if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}
}
