package config

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

// ── Helpers ──────────────────────────────────────────────────────────────

// writeYAML creates a temporary YAML file and returns its path.
func writeYAML(t *testing.T, dir, name, content string) string {
	t.Helper()
	path := filepath.Join(dir, name)
	if err := os.WriteFile(path, []byte(content), 0644); err != nil {
		t.Fatal(err)
	}
	return path
}

// ── 2.1: Parse minimal config ───────────────────────────────────────────

func TestLoad_MinimalConfig(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: test-bot
matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
llm:
  primary:
    provider: openai
`)
	cfg, err := Load(path)
	if err != nil {
		t.Fatalf("Load() error: %v", err)
	}
	if cfg.Agent.ID != "test-bot" {
		t.Errorf("Agent.ID = %q, want %q", cfg.Agent.ID, "test-bot")
	}
	if cfg.Matrix.Homeserver != "https://matrix.example.com" {
		t.Errorf("Matrix.Homeserver = %q, want %q", cfg.Matrix.Homeserver, "https://matrix.example.com")
	}
	if cfg.Matrix.UserID != "@bot:example.com" {
		t.Errorf("Matrix.UserID = %q, want %q", cfg.Matrix.UserID, "@bot:example.com")
	}
	if cfg.LLM.Primary.Provider != "openai" {
		t.Errorf("LLM.Primary.Provider = %q, want %q", cfg.LLM.Primary.Provider, "openai")
	}
}

// ── 2.2: Parse full config with all sections ────────────────────────────

func TestLoad_FullConfig(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: full-agent
  name: "Full Agent"
  version: "1.0.0"
  type: agent
  enabled: true
  description: "A fully configured agent"
  tags: ["test", "full"]

personality:
  tone: friendly
  language: es
  prefix: "!"
  role: "test assistant"
  communication:
    formality: semiformal
    humor: subtle

llm:
  primary:
    provider: openai
    model: gpt-4o
    api_key_env: OPENAI_API_KEY
    max_tokens: 4096
    temperature: 0.7
  tool_use:
    enabled: true
    max_iterations: 10
  reasoning:
    system_prompt_file: prompts/system.md
    context_window: 16000

matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
  device_id: TESTDEVICE
  encryption:
    enabled: true
    store_path: ./data/crypto
    trust_mode: tofu
  rooms:
    listen: ["!room1:example.com"]
    respond: ["!room1:example.com"]
  filters:
    command_prefix: "!"
    mention_respond: true
    dm_respond: true
    ignore_bots: true
  threads:
    enabled: true
    auto_thread: false

tools:
  ssh:
    enabled: true
    allowed_targets: ["prod-01"]
  http:
    enabled: true
    allowed_domains: ["api.example.com"]

security:
  sanitize:
    enabled: true
    mode: warn
    min_severity: medium
  tool_rate_limit:
    enabled: true
    max_calls_per_min: 20

memory:
  enabled: true
  window_size: 30

storage:
  base_path: /tmp/test-data
`)

	cfg, err := Load(path)
	if err != nil {
		t.Fatalf("Load() error: %v", err)
	}

	// Agent identity
	if cfg.Agent.ID != "full-agent" {
		t.Errorf("Agent.ID = %q, want %q", cfg.Agent.ID, "full-agent")
	}
	if cfg.Agent.Name != "Full Agent" {
		t.Errorf("Agent.Name = %q, want %q", cfg.Agent.Name, "Full Agent")
	}
	if cfg.Agent.Version != "1.0.0" {
		t.Errorf("Agent.Version = %q, want %q", cfg.Agent.Version, "1.0.0")
	}
	if !cfg.Agent.Enabled {
		t.Error("Agent.Enabled = false, want true")
	}
	if len(cfg.Agent.Tags) != 2 {
		t.Errorf("Agent.Tags len = %d, want 2", len(cfg.Agent.Tags))
	}

	// Personality
	if cfg.Personality.Tone != "friendly" {
		t.Errorf("Personality.Tone = %q, want %q", cfg.Personality.Tone, "friendly")
	}
	if cfg.Personality.Role != "test assistant" {
		t.Errorf("Personality.Role = %q, want %q", cfg.Personality.Role, "test assistant")
	}
	if cfg.Personality.Communication.Formality != "semiformal" {
		t.Errorf("Communication.Formality = %q, want %q", cfg.Personality.Communication.Formality, "semiformal")
	}

	// LLM
	if cfg.LLM.Primary.Model != "gpt-4o" {
		t.Errorf("LLM.Primary.Model = %q, want %q", cfg.LLM.Primary.Model, "gpt-4o")
	}
	if cfg.LLM.Primary.MaxTokens != 4096 {
		t.Errorf("LLM.Primary.MaxTokens = %d, want 4096", cfg.LLM.Primary.MaxTokens)
	}
	if cfg.LLM.Primary.Temperature != 0.7 {
		t.Errorf("LLM.Primary.Temperature = %f, want 0.7", cfg.LLM.Primary.Temperature)
	}
	if !cfg.LLM.ToolUse.Enabled {
		t.Error("LLM.ToolUse.Enabled = false, want true")
	}
	if cfg.LLM.ToolUse.MaxIterations != 10 {
		t.Errorf("LLM.ToolUse.MaxIterations = %d, want 10", cfg.LLM.ToolUse.MaxIterations)
	}

	// Matrix
	if !cfg.Matrix.Encryption.Enabled {
		t.Error("Matrix.Encryption.Enabled = false, want true")
	}
	if cfg.Matrix.Encryption.TrustMode != "tofu" {
		t.Errorf("Encryption.TrustMode = %q, want %q", cfg.Matrix.Encryption.TrustMode, "tofu")
	}
	if len(cfg.Matrix.Rooms.Listen) != 1 {
		t.Errorf("Rooms.Listen len = %d, want 1", len(cfg.Matrix.Rooms.Listen))
	}
	if !cfg.Matrix.Threads.Enabled {
		t.Error("Matrix.Threads.Enabled = false, want true")
	}

	// Tools
	if !cfg.Tools.SSH.Enabled {
		t.Error("Tools.SSH.Enabled = false, want true")
	}
	if !cfg.Tools.HTTP.Enabled {
		t.Error("Tools.HTTP.Enabled = false, want true")
	}

	// Security
	if !cfg.Security.Sanitize.Enabled {
		t.Error("Security.Sanitize.Enabled = false, want true")
	}
	if cfg.Security.Sanitize.Mode != "warn" {
		t.Errorf("Sanitize.Mode = %q, want %q", cfg.Security.Sanitize.Mode, "warn")
	}
	if !cfg.Security.ToolRateLimit.Enabled {
		t.Error("ToolRateLimit.Enabled = false, want true")
	}

	// Memory
	if !cfg.Memory.Enabled {
		t.Error("Memory.Enabled = false, want true")
	}
	if cfg.Memory.WindowSize != 30 {
		t.Errorf("Memory.WindowSize = %d, want 30", cfg.Memory.WindowSize)
	}

	// Storage
	if cfg.Storage.BasePath != "/tmp/test-data" {
		t.Errorf("Storage.BasePath = %q, want %q", cfg.Storage.BasePath, "/tmp/test-data")
	}
}

// ── 2.3: Env var expansion ──────────────────────────────────────────────

func TestLoad_EnvVarExpansion(t *testing.T) {
	// Set env vars for the test
	t.Setenv("TEST_HOMESERVER", "https://expanded.example.com")
	t.Setenv("TEST_USER_ID", "@expanded:example.com")
	t.Setenv("TEST_PROVIDER", "anthropic")

	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: env-test
matrix:
  homeserver: "$TEST_HOMESERVER"
  user_id: "${TEST_USER_ID}"
llm:
  primary:
    provider: "$TEST_PROVIDER"
`)

	cfg, err := Load(path)
	if err != nil {
		t.Fatalf("Load() error: %v", err)
	}

	if cfg.Matrix.Homeserver != "https://expanded.example.com" {
		t.Errorf("Homeserver = %q, want %q", cfg.Matrix.Homeserver, "https://expanded.example.com")
	}
	if cfg.Matrix.UserID != "@expanded:example.com" {
		t.Errorf("UserID = %q, want %q", cfg.Matrix.UserID, "@expanded:example.com")
	}
	if cfg.LLM.Primary.Provider != "anthropic" {
		t.Errorf("Provider = %q, want %q", cfg.LLM.Primary.Provider, "anthropic")
	}
}

// ── 2.4: Unknown fields (forward compat) ────────────────────────────────

func TestLoad_UnknownFieldsIgnored(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: unknown-fields-test
  future_field: "should be ignored"
matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
  new_section:
    enabled: true
llm:
  primary:
    provider: openai
`)

	cfg, err := Load(path)
	if err != nil {
		t.Fatalf("Load() error: %v", err)
	}
	if cfg.Agent.ID != "unknown-fields-test" {
		t.Errorf("Agent.ID = %q, want %q", cfg.Agent.ID, "unknown-fields-test")
	}
}

// ── 2.5: Default values ─────────────────────────────────────────────────

func TestLoad_DefaultValues(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: defaults-test
matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
llm:
  primary:
    provider: openai
`)

	cfg, err := Load(path)
	if err != nil {
		t.Fatalf("Load() error: %v", err)
	}

	// Boolean defaults should be false (Go zero values)
	if cfg.Agent.Enabled {
		t.Error("Agent.Enabled should default to false")
	}
	if cfg.Memory.Enabled {
		t.Error("Memory.Enabled should default to false")
	}
	if cfg.LLM.ToolUse.Enabled {
		t.Error("ToolUse.Enabled should default to false")
	}
	if cfg.Security.Sanitize.Enabled {
		t.Error("Sanitize.Enabled should default to false")
	}

	// Numeric defaults should be zero
	if cfg.LLM.Primary.MaxTokens != 0 {
		t.Errorf("MaxTokens should default to 0, got %d", cfg.LLM.Primary.MaxTokens)
	}
	if cfg.Memory.WindowSize != 0 {
		t.Errorf("Memory.WindowSize should default to 0, got %d", cfg.Memory.WindowSize)
	}

	// String defaults should be empty
	if cfg.Agent.Version != "" {
		t.Errorf("Agent.Version should default to empty, got %q", cfg.Agent.Version)
	}
	if cfg.Agent.Type != "" {
		t.Errorf("Agent.Type should default to empty, got %q", cfg.Agent.Type)
	}
}

// ── Validation tests ────────────────────────────────────────────────────

func TestLoad_MissingAgentID(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
llm:
  primary:
    provider: openai
`)

	_, err := Load(path)
	if err == nil {
		t.Fatal("Load() should fail when agent.id is missing")
	}
}

func TestLoad_MissingHomeserver(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: test-bot
matrix:
  user_id: "@bot:example.com"
llm:
  primary:
    provider: openai
`)

	_, err := Load(path)
	if err == nil {
		t.Fatal("Load() should fail when matrix.homeserver is missing")
	}
}

func TestLoad_MissingUserID(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: test-bot
matrix:
  homeserver: https://matrix.example.com
llm:
  primary:
    provider: openai
`)

	_, err := Load(path)
	if err == nil {
		t.Fatal("Load() should fail when matrix.user_id is missing")
	}
}

func TestLoad_MissingProvider(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: test-bot
matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
`)

	_, err := Load(path)
	if err == nil {
		t.Fatal("Load() should fail when llm.primary.provider is missing")
	}
}

func TestLoad_FileNotFound(t *testing.T) {
	_, err := Load("/nonexistent/path/config.yaml")
	if err == nil {
		t.Fatal("Load() should fail when file does not exist")
	}
}

func TestLoad_InvalidYAML(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: [invalid yaml content
  this is broken
`)

	_, err := Load(path)
	if err == nil {
		t.Fatal("Load() should fail on invalid YAML")
	}
}

// ── LoadMeta tests ──────────────────────────────────────────────────────

func TestLoadMeta_BasicParsing(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  id: meta-test
  name: "Meta Test"
  type: robot
  enabled: true
  description: "A test robot"
matrix:
  homeserver: https://matrix.example.com
  user_id: "@bot:example.com"
  access_token_env: "$NONEXISTENT_VAR"
`)

	cfg, err := LoadMeta(path)
	if err != nil {
		t.Fatalf("LoadMeta() error: %v", err)
	}
	if cfg.Agent.ID != "meta-test" {
		t.Errorf("Agent.ID = %q, want %q", cfg.Agent.ID, "meta-test")
	}
	if cfg.Agent.Type != "robot" {
		t.Errorf("Agent.Type = %q, want %q", cfg.Agent.Type, "robot")
	}
	// LoadMeta does NOT expand env vars — raw $NONEXISTENT_VAR kept
	if cfg.Matrix.AccessTokenEnv != "$NONEXISTENT_VAR" {
		t.Errorf("AccessTokenEnv = %q, want %q", cfg.Matrix.AccessTokenEnv, "$NONEXISTENT_VAR")
	}
}

func TestLoadMeta_MissingAgentID(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "config.yaml", `
agent:
  name: "No ID"
`)

	_, err := LoadMeta(path)
	if err == nil {
		t.Fatal("LoadMeta() should fail when agent.id is missing")
	}
}

// ── LoadSpecial tests ───────────────────────────────────────────────────

func TestLoadSpecial_MinimalConfig(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "special.yaml", `
special:
  id: orchestrator
  type: orchestrator
  enabled: true
llm:
  primary:
    provider: openai
`)

	cfg, err := LoadSpecial(path)
	if err != nil {
		t.Fatalf("LoadSpecial() error: %v", err)
	}
	if cfg.Special.ID != "orchestrator" {
		t.Errorf("Special.ID = %q, want %q", cfg.Special.ID, "orchestrator")
	}
	if cfg.Special.Type != "orchestrator" {
		t.Errorf("Special.Type = %q, want %q", cfg.Special.Type, "orchestrator")
	}
}

func TestLoadSpecial_MissingSpecialID(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "special.yaml", `
special:
  type: orchestrator
llm:
  primary:
    provider: openai
`)

	_, err := LoadSpecial(path)
	if err == nil {
		t.Fatal("LoadSpecial() should fail when special.id is missing")
	}
}

func TestLoadSpecial_MissingType(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "special.yaml", `
special:
  id: orchestrator
llm:
  primary:
    provider: openai
`)

	_, err := LoadSpecial(path)
	if err == nil {
		t.Fatal("LoadSpecial() should fail when special.type is missing")
	}
}

func TestLoadSpecial_MissingProvider(t *testing.T) {
	dir := t.TempDir()
	path := writeYAML(t, dir, "special.yaml", `
special:
  id: orchestrator
  type: orchestrator
`)

	_, err := LoadSpecial(path)
	if err == nil {
		t.Fatal("LoadSpecial() should fail when llm.primary.provider is missing")
	}
}
