package config

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

func TestNewLoadsStrictYAMLConfigFile(t *testing.T) {
	clearConfigEnvironment(t)
	root := t.TempDir()
	path := writeConfigFile(t, fmt.Sprintf(`
environment: production
listen_mode: production
port: 9090
trust_forwarded_host: true

tenants:
  - id: clawmem
    base_url: https://git.staging.clawmem.ai
    domain_aliases:
      - https://code.staging.clawmem.ai
    db_dsn: shared:password@tcp(db.example:4000)/clawmem
    git_repo_dir: %q
    secret_encryption_key: %q
    browser:
      console_base_url: https://console.staging.clawmem.ai
      oauth_device_verification_url: https://console.staging.clawmem.ai/device-login
      oauth_loopback_redirects: true
      cors_allowed_origins:
        - https://console.staging.clawmem.ai
    embedding:
      api_key: shared-openai-key
      base_url: https://api.openai.com
      model: text-embedding-3-small
      dimensions: 1536
    oidc:
      enabled: true
      issuer: https://clawmem.us.auth0.com/
      client_id: clawmem-client
      client_secret: clawmem-secret
      audience: clawmem-api
      scopes: openid profile email
    workflow:
      enabled: true
      image: bash:5.2
      timeout: 3m
      cpus: "0.5"
      memory: 128m
      pids_limit: 64
      nofile: 256
      tmpfs_size: 16m

  - id: ags
    base_url: https://git.staging.ags.example
    db_dsn: shared:password@tcp(db.example:4000)/ags
    git_repo_dir: %q
    secret_encryption_key: %q
    browser:
      console_base_url: https://console.staging.ags.example
      cors_allowed_origins:
        - https://console.staging.ags.example
    embedding:
      api_key: shared-openai-key
      base_url: https://api.openai.com
      model: text-embedding-3-small
    connected_login:
      enabled: true
      provider: slock
      origin: https://app.slock.example
      api_origin: https://api.slock.example
      client_id: ags-client
      client_secret: ags-client-secret
      subject_namespace_claim: server_id
`, filepath.Join(root, "clawmem"), tenantTestKey('a'), filepath.Join(root, "ags"), tenantTestKey('b')))
	t.Setenv("AGS_CONFIG_FILE", path)

	cfg, err := New()
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	if cfg.Environment != "production" || cfg.ListenMode != "production" || cfg.Port != "9090" {
		t.Fatalf("process config = %#v", cfg)
	}
	if !cfg.TrustForwardedHost {
		t.Fatal("trust_forwarded_host was not loaded")
	}
	if len(cfg.Tenants) != 2 || cfg.Tenants[0].ID != "clawmem" || cfg.Tenants[1].ID != "ags" {
		t.Fatalf("tenants = %#v", cfg.Tenants)
	}
	clawmem, ags := cfg.Tenants[0], cfg.Tenants[1]
	if clawmem.Browser.ConsoleBaseURL != "https://console.staging.clawmem.ai" ||
		ags.Browser.ConsoleBaseURL != "https://console.staging.ags.example" {
		t.Fatalf("console URLs = %q, %q", clawmem.Browser.ConsoleBaseURL, ags.Browser.ConsoleBaseURL)
	}
	if clawmem.Embedding.APIKey != ags.Embedding.APIKey {
		t.Fatal("shared embedding API key was not preserved")
	}
	if clawmem.SecretEncryptionKey == ags.SecretEncryptionKey {
		t.Fatal("tenant secret encryption keys unexpectedly match")
	}
	if !clawmem.OIDC.Enabled || clawmem.OIDC.Provider != "auth0" {
		t.Fatalf("clawmem OIDC = %#v", clawmem.OIDC)
	}
	if !clawmem.Workflow.Enabled || clawmem.Workflow.Timeout != 3*time.Minute || clawmem.Workflow.PidsLimit != 64 {
		t.Fatalf("clawmem workflow = %#v", clawmem.Workflow)
	}
	if !ags.ConnectedLogin.Enabled || ags.ConnectedLogin.Provider != "slock" ||
		ags.ConnectedLogin.SubjectNamespaceClaim != "server_id" {
		t.Fatalf("ags connected login = %#v", ags.ConnectedLogin)
	}
}

func TestYAMLConfigFileRejectsUnknownDuplicateAndReferenceFields(t *testing.T) {
	tests := []struct {
		name string
		raw  string
		want string
	}{
		{
			name: "schema version",
			raw:  "schema_version: 1\ntenants: []\n",
			want: "field schema_version not found",
		},
		{
			name: "secret reference",
			raw:  "secret_ref: shared\ntenants: []\n",
			want: "field secret_ref not found",
		},
		{
			name: "unknown top-level field",
			raw:  "environment: production\nunknown: true\ntenants: []\n",
			want: "field unknown not found",
		},
		{
			name: "unknown nested field",
			raw: "tenants:\n" +
				"  - id: ags\n" +
				"    base_url: https://git.example.test\n" +
				"    embedding:\n" +
				"      unknown: true\n",
			want: "field unknown not found",
		},
		{
			name: "environment reference",
			raw: "tenants:\n" +
				"  - id: ags\n" +
				"    base_url: https://git.example.test\n" +
				"    db_dsn_env: AGS_DB_DSN\n",
			want: "field db_dsn_env not found",
		},
		{
			name: "duplicate field",
			raw:  "environment: production\nenvironment: development\ntenants: []\n",
			want: "mapping key \"environment\" already defined",
		},
		{
			name: "multiple documents",
			raw:  "tenants:\n  - id: ags\n    base_url: https://git.example.test\n---\ntenants: []\n",
			want: "multiple YAML documents",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, err := loadConfigFile(writeConfigFile(t, tc.raw))
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("loadConfigFile error = %v, want %q", err, tc.want)
			}
		})
	}
}

func TestNewRejectsEnvironmentConfigurationWithConfigFile(t *testing.T) {
	path := writeConfigFile(t, "tenants:\n  - id: ags\n    base_url: https://git.example.test\n")
	for _, name := range configFileEnvironmentNames() {
		t.Run(name, func(t *testing.T) {
			clearConfigEnvironment(t)
			t.Setenv("AGS_CONFIG_FILE", path)
			t.Setenv(name, "configured")
			_, err := New()
			if err == nil || !strings.Contains(err.Error(), name) {
				t.Fatalf("New error = %v, want conflict for %s", err, name)
			}
		})
	}
}

func TestNewAllowsSubsystemEnvironmentWithConfigFile(t *testing.T) {
	clearConfigEnvironment(t)
	path := writeConfigFile(t, fmt.Sprintf(`
tenants:
  - id: ags
    base_url: https://git.example.test
    db_dsn: root@tcp(db.example:4000)/ags
    git_repo_dir: %q
`, t.TempDir()))
	t.Setenv("AGS_CONFIG_FILE", path)
	t.Setenv("LOG_FORMAT", "json")
	t.Setenv("GITHTTP_MAX_PUSH_BYTES", "1024")
	t.Setenv("EMBEDDING_CONCURRENCY", "2")

	if _, err := New(); err != nil {
		t.Fatalf("New: %v", err)
	}
}

func TestYAMLConfigFileValidatesPortAndTenantRequirements(t *testing.T) {
	for _, tc := range []struct {
		name string
		raw  string
		want string
	}{
		{name: "invalid port", raw: "port: 0\ntenants:\n  - id: ags\n", want: "port must be between"},
		{name: "missing tenants", raw: "environment: production\n", want: "at least one tenant is required"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := loadConfigFile(writeConfigFile(t, tc.raw))
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("loadConfigFile error = %v, want %q", err, tc.want)
			}
		})
	}
}

func writeConfigFile(t *testing.T, contents string) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "config.yaml")
	if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}
	return path
}

func clearConfigEnvironment(t *testing.T) {
	t.Helper()
	for _, name := range configFileEnvironmentNames() {
		t.Setenv(name, "")
	}
}
