package config

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

func TestLoad_ValidConfig(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket
      format: json
    - name: network
      path: /var/run/logcorrelator/network.socket
      format: json

outputs:
  file:
    path: /var/log/logcorrelator/correlated.log
  clickhouse:
    dsn: clickhouse://user:pass@localhost:9000/db
    table: correlated_logs

correlation:
  time_window_s: 1
  emit_orphans: true
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

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

	if len(cfg.Inputs.UnixSockets) != 2 {
		t.Errorf("expected 2 unix sockets, got %d", len(cfg.Inputs.UnixSockets))
	}
	if cfg.Outputs.File.Path != "/var/log/logcorrelator/correlated.log" {
		t.Errorf("expected file path /var/log/logcorrelator/correlated.log, got %s", cfg.Outputs.File.Path)
	}
	if !cfg.Outputs.File.Enabled {
		t.Error("expected file output to be enabled by default when path is set")
	}
}

func TestLoad_InvalidPath(t *testing.T) {
	_, err := Load("/nonexistent/path/config.yml")
	if err == nil {
		t.Error("expected error for nonexistent path")
	}
}

func TestLoad_InvalidYAML(t *testing.T) {
	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	content := `invalid: yaml: content: [`
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for invalid YAML")
	}
}

func TestLoad_DefaultValues(t *testing.T) {
	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	content := `
inputs:
  unix_sockets:
    - name: a
      path: /tmp/a.sock
    - name: b
      path: /tmp/b.sock
outputs:
  file:
    path: /var/log/test.log
`
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

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

	// Check defaults
	if cfg.Correlation.TimeWindowS != 1 {
		t.Errorf("expected default time window value 1, got %d", cfg.Correlation.TimeWindowS)
	}
	if cfg.Correlation.EmitOrphans != true {
		t.Error("expected default emit_orphans to be true")
	}
}

func TestValidate_MinimumInputs(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "only_one", Path: "/tmp/test.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: "/var/log/test.log"},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for less than 2 inputs")
	}
}

func TestValidate_AtLeastOneOutput(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File:       FileOutputConfig{Enabled: false},
			ClickHouse: ClickHouseOutputConfig{Enabled: false},
			Stdout:     StdoutOutputConfig{Enabled: false},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for no outputs enabled")
	}
}

func TestGetTimeWindow(t *testing.T) {
	tests := []struct {
		name     string
		config   CorrelationConfig
		expected time.Duration
	}{
		{
			name: "1 second",
			config: CorrelationConfig{
				TimeWindowS: 1,
			},
			expected: time.Second,
		},
		{
			name: "5 seconds",
			config: CorrelationConfig{
				TimeWindowS: 5,
			},
			expected: 5 * time.Second,
		},
		{
			name: "default",
			config: CorrelationConfig{
				TimeWindowS: 0,
			},
			expected: time.Second,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.config.GetTimeWindow()
			if result != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, result)
			}
		})
	}
}

func TestValidate_DuplicateNames(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "same", Path: "/tmp/a.sock"},
				{Name: "same", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: "/var/log/test.log"},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for duplicate names")
	}
}

func TestValidate_DuplicatePaths(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/same.sock"},
				{Name: "b", Path: "/tmp/same.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: "/var/log/test.log"},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for duplicate paths")
	}
}

func TestValidate_EmptyName(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: "/var/log/test.log"},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for empty name")
	}
}

func TestValidate_EmptyPath(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: ""},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: "/var/log/test.log"},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for empty path")
	}
}

func TestValidate_EmptyFilePath(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: ""},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for empty file path")
	}
}

func TestValidate_ClickHouseMissingDSN(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: ""},
			ClickHouse: ClickHouseOutputConfig{
				Enabled: true,
				DSN:     "",
				Table:   "test",
			},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for missing ClickHouse DSN")
	}
}

func TestValidate_ClickHouseMissingTable(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: ""},
			ClickHouse: ClickHouseOutputConfig{
				Enabled: true,
				DSN:     "clickhouse://localhost:9000/db",
				Table:   "",
			},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for missing ClickHouse table")
	}
}

func TestValidate_ClickHouseInvalidBatchSize(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: ""},
			ClickHouse: ClickHouseOutputConfig{
				Enabled:   true,
				DSN:       "clickhouse://localhost:9000/db",
				Table:     "test",
				BatchSize: 0,
			},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for invalid batch size")
	}
}

func TestValidate_ClickHouseInvalidMaxBufferSize(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: ""},
			ClickHouse: ClickHouseOutputConfig{
				Enabled:       true,
				DSN:           "clickhouse://localhost:9000/db",
				Table:         "test",
				BatchSize:     100,
				MaxBufferSize: 0,
			},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for invalid max buffer size")
	}
}

func TestValidate_ClickHouseInvalidTimeout(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: ""},
			ClickHouse: ClickHouseOutputConfig{
				Enabled:   true,
				DSN:       "clickhouse://localhost:9000/db",
				Table:     "test",
				BatchSize: 100,
				TimeoutMs: 0,
			},
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for invalid timeout")
	}
}

func TestValidate_EmptyCorrelationKey(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File: FileOutputConfig{Enabled: true, Path: "/var/log/test.log"},
		},
		Correlation: CorrelationConfig{
			TimeWindowS: 0,
		},
	}

	err := cfg.Validate()
	if err == nil {
		t.Error("expected error for invalid time window")
	}
}

func TestGetSocketPermissions(t *testing.T) {
	tests := []struct {
		name     string
		config   UnixSocketConfig
		expected os.FileMode
	}{
		{
			name: "default",
			config: UnixSocketConfig{
				SocketPermissions: "",
			},
			expected: 0666,
		},
		{
			name: "explicit 0660",
			config: UnixSocketConfig{
				SocketPermissions: "0660",
			},
			expected: 0660,
		},
		{
			name: "explicit 0666",
			config: UnixSocketConfig{
				SocketPermissions: "0666",
			},
			expected: 0666,
		},
		{
			name: "without leading zero",
			config: UnixSocketConfig{
				SocketPermissions: "660",
			},
			expected: 0660,
		},
		{
			name: "invalid value",
			config: UnixSocketConfig{
				SocketPermissions: "invalid",
			},
			expected: 0666,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.config.GetSocketPermissions()
			if result != tt.expected {
				t.Errorf("expected %o, got %o", tt.expected, result)
			}
		})
	}
}

func TestLogConfig_GetLevel(t *testing.T) {
	tests := []struct {
		name     string
		config   LogConfig
		expected string
	}{
		{
			name:     "default",
			config:   LogConfig{Level: ""},
			expected: "INFO",
		},
		{
			name:     "DEBUG uppercase",
			config:   LogConfig{Level: "DEBUG"},
			expected: "DEBUG",
		},
		{
			name:     "debug lowercase",
			config:   LogConfig{Level: "debug"},
			expected: "DEBUG",
		},
		{
			name:     "WARN",
			config:   LogConfig{Level: "WARN"},
			expected: "WARN",
		},
		{
			name:     "ERROR",
			config:   LogConfig{Level: "ERROR"},
			expected: "ERROR",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.config.GetLevel()
			if result != tt.expected {
				t.Errorf("GetLevel() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestLoad_LogLevel(t *testing.T) {
	content := `
log:
  level: DEBUG

inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket
    - name: network
      path: /var/run/logcorrelator/network.socket

outputs:
  file:
    path: /var/log/test.log

correlation:
  time_window_s: 1
  emit_orphans: true
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

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

	if cfg.Log.GetLevel() != "DEBUG" {
		t.Errorf("expected log level DEBUG, got %s", cfg.Log.GetLevel())
	}
}

func TestLoad_StdoutEnabledObject(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket
    - name: network
      path: /var/run/logcorrelator/network.socket

outputs:
  stdout:
    enabled: true

correlation:
  time_window_s: 1
  emit_orphans: true
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

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

	if !cfg.Outputs.Stdout.Enabled {
		t.Error("expected outputs.stdout.enabled to be true")
	}
}

func TestTimeWindowConfig_GetDuration(t *testing.T) {
	tests := []struct {
		name     string
		config   TimeWindowConfig
		expected time.Duration
	}{
		{
			name:     "seconds",
			config:   TimeWindowConfig{Value: 5, Unit: "s"},
			expected: 5 * time.Second,
		},
		{
			name:     "milliseconds",
			config:   TimeWindowConfig{Value: 500, Unit: "ms"},
			expected: 500 * time.Millisecond,
		},
		{
			name:     "default on zero value",
			config:   TimeWindowConfig{Value: 0, Unit: "s"},
			expected: 1 * time.Second,
		},
		{
			name:     "unknown unit defaults to seconds",
			config:   TimeWindowConfig{Value: 5, Unit: "unknown"},
			expected: 5 * time.Second,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.config.GetDuration()
			if result != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, result)
			}
		})
	}
}

func TestCorrelationConfig_GetTimeWindow(t *testing.T) {
	cfg := CorrelationConfig{
		TimeWindow: TimeWindowConfig{Value: 2, Unit: "s"},
	}

	result := cfg.GetTimeWindow()
	if result != 2*time.Second {
		t.Errorf("expected 2s, got %v", result)
	}
}

func TestCorrelationConfig_GetTimeWindow_Default(t *testing.T) {
	cfg := CorrelationConfig{
		TimeWindow: TimeWindowConfig{Value: 0, Unit: "s"},
	}

	result := cfg.GetTimeWindow()
	if result != 1*time.Second {
		t.Errorf("expected default 1s, got %v", result)
	}
}

func TestCorrelationConfig_GetMatchingMode(t *testing.T) {
	tests := []struct {
		name     string
		config   CorrelationConfig
		expected string
	}{
		{
			name:     "one_to_many",
			config:   CorrelationConfig{Matching: MatchingConfig{Mode: "one_to_many"}},
			expected: "one_to_many",
		},
		{
			name:     "one_to_one",
			config:   CorrelationConfig{Matching: MatchingConfig{Mode: "one_to_one"}},
			expected: "one_to_one",
		},
		{
			name:     "default empty",
			config:   CorrelationConfig{Matching: MatchingConfig{Mode: ""}},
			expected: "one_to_many",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.config.GetMatchingMode()
			if result != tt.expected {
				t.Errorf("expected %s, got %s", tt.expected, result)
			}
		})
	}
}

func TestCorrelationConfig_GetApacheAlwaysEmit(t *testing.T) {
	cfg := CorrelationConfig{
		OrphanPolicy: OrphanPolicyConfig{ApacheAlwaysEmit: true},
	}

	if !cfg.GetApacheAlwaysEmit() {
		t.Error("expected true")
	}
}

func TestCorrelationConfig_GetApacheAlwaysEmit_Deprecated(t *testing.T) {
	cfg := CorrelationConfig{
		EmitOrphans: true, // deprecated field
	}

	if !cfg.GetApacheAlwaysEmit() {
		t.Error("expected true from deprecated field")
	}
}

func TestCorrelationConfig_GetNetworkTTLS(t *testing.T) {
	cfg := CorrelationConfig{
		TTL: TTLConfig{NetworkTTLS: 60},
	}

	result := cfg.GetNetworkTTLS()
	if result != 60 {
		t.Errorf("expected 60, got %d", result)
	}
}

func TestCorrelationConfig_GetNetworkTTLS_Default(t *testing.T) {
	cfg := CorrelationConfig{
		TTL: TTLConfig{NetworkTTLS: 0},
	}

	result := cfg.GetNetworkTTLS()
	if result != 120 {
		t.Errorf("expected default 120, got %d", result)
	}
}

func TestCorrelationConfig_GetMaxHTTPBufferSize(t *testing.T) {
	cfg := CorrelationConfig{
		Buffers: BuffersConfig{MaxHTTPItems: 5000},
	}

	result := cfg.GetMaxHTTPBufferSize()
	if result != 5000 {
		t.Errorf("expected 5000, got %d", result)
	}
}

func TestCorrelationConfig_GetMaxHTTPBufferSize_Default(t *testing.T) {
	cfg := CorrelationConfig{
		Buffers: BuffersConfig{MaxHTTPItems: 0},
	}

	result := cfg.GetMaxHTTPBufferSize()
	if result != 10000 {
		t.Errorf("expected default 10000, got %d", result)
	}
}

func TestCorrelationConfig_GetMaxNetworkBufferSize(t *testing.T) {
	cfg := CorrelationConfig{
		Buffers: BuffersConfig{MaxNetworkItems: 15000},
	}

	result := cfg.GetMaxNetworkBufferSize()
	if result != 15000 {
		t.Errorf("expected 15000, got %d", result)
	}
}

func TestCorrelationConfig_GetMaxNetworkBufferSize_Default(t *testing.T) {
	cfg := CorrelationConfig{
		Buffers: BuffersConfig{MaxNetworkItems: 0},
	}

	result := cfg.GetMaxNetworkBufferSize()
	if result != 20000 {
		t.Errorf("expected default 20000, got %d", result)
	}
}

func TestUnixSocketConfig_GetSocketPermissions_Invalid(t *testing.T) {
	cfg := UnixSocketConfig{
		SocketPermissions: "invalid",
	}

	result := cfg.GetSocketPermissions()
	if result != 0666 {
		t.Errorf("expected default 0666 for invalid value, got %o", result)
	}
}

func TestLoadConfig_Validation(t *testing.T) {
	// Test config with no inputs
	content := `
outputs:
  file:
    path: /var/log/test.log

correlation:
  time_window_s: 1
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for config with no inputs")
	}
}

func TestLoadConfig_Validation_NoOutputs(t *testing.T) {
	// Test config with no outputs
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket

correlation:
  time_window_s: 1
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for config with no outputs")
	}
}

func TestLoadConfig_Validation_DuplicateSocketNames(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket
    - name: http
      path: /var/run/logcorrelator/other.socket

outputs:
  file:
    path: /var/log/test.log

correlation:
  time_window_s: 1
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for duplicate socket names")
	}
}

func TestLoadConfig_Validation_DuplicateSocketPaths(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket
    - name: network
      path: /var/run/logcorrelator/http.socket

outputs:
  file:
    path: /var/log/test.log

correlation:
  time_window_s: 1
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for duplicate socket paths")
	}
}

func TestLoadConfig_Validation_ClickHouseEnabled(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket

outputs:
  clickhouse:
    enabled: true

correlation:
  time_window_s: 1
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for ClickHouse enabled without DSN")
	}
}

func TestLoadConfig_Validation_ClickHouseEnabled_NoTable(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket

outputs:
  clickhouse:
    enabled: true
    dsn: clickhouse://localhost:9000/db

correlation:
  time_window_s: 1
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

	_, err := Load(configPath)
	if err == nil {
		t.Error("expected error for ClickHouse enabled without table")
	}
}

func TestValidate_FileOutputDisabled(t *testing.T) {
	cfg := &Config{
		Inputs: InputsConfig{
			UnixSockets: []UnixSocketConfig{
				{Name: "a", Path: "/tmp/a.sock"},
				{Name: "b", Path: "/tmp/b.sock"},
			},
		},
		Outputs: OutputsConfig{
			File:       FileOutputConfig{Enabled: false, Path: "/var/log/test.log"},
			ClickHouse: ClickHouseOutputConfig{Enabled: false},
			Stdout:     StdoutOutputConfig{Enabled: true},
		},
		Correlation: CorrelationConfig{
			TimeWindowS: 1,
		},
	}

	err := cfg.Validate()
	if err != nil {
		t.Errorf("expected no error when file is disabled but stdout is enabled, got: %v", err)
	}
}

func TestLoadConfig_FileOutputDisabled(t *testing.T) {
	content := `
inputs:
  unix_sockets:
    - name: http
      path: /var/run/logcorrelator/http.socket
    - name: network
      path: /var/run/logcorrelator/network.socket

outputs:
  file:
    enabled: false
    path: /var/log/logcorrelator/correlated.log
  stdout:
    enabled: true

correlation:
  time_window_s: 1
  emit_orphans: true
`

	tmpDir := t.TempDir()
	configPath := filepath.Join(tmpDir, "config.yml")
	if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
		t.Fatalf("failed to write config: %v", err)
	}

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

	if cfg.Outputs.File.Enabled {
		t.Error("expected file output to be disabled")
	}
	if cfg.Outputs.File.Path != "/var/log/logcorrelator/correlated.log" {
		t.Errorf("expected file path to be preserved, got %s", cfg.Outputs.File.Path)
	}
	if !cfg.Outputs.Stdout.Enabled {
		t.Error("expected stdout output to be enabled")
	}
}

// TestTimeWindowConfig_GetDuration_Seconds tests time window in seconds.
func TestTimeWindowConfig_GetDuration_Seconds(t *testing.T) {
cases := []struct {
unit     string
value    int
expected time.Duration
}{
{"s", 5, 5 * time.Second},
{"sec", 10, 10 * time.Second},
{"second", 1, 1 * time.Second},
{"seconds", 30, 30 * time.Second},
{"", 2, 2 * time.Second}, // default is seconds
{"ms", 500, 500 * time.Millisecond},
{"millisecond", 100, 100 * time.Millisecond},
{"milliseconds", 250, 250 * time.Millisecond},
}

for _, tc := range cases {
t.Run(tc.unit, func(t *testing.T) {
cfg := &TimeWindowConfig{Value: tc.value, Unit: tc.unit}
got := cfg.GetDuration()
if got != tc.expected {
t.Errorf("GetDuration() = %v, want %v", got, tc.expected)
}
})
}
}

// TestTimeWindowConfig_GetDuration_ZeroUsesDefault verifies Value<=0 defaults to 1.
func TestTimeWindowConfig_GetDuration_ZeroUsesDefault(t *testing.T) {
cfg := &TimeWindowConfig{Value: 0, Unit: "s"}
got := cfg.GetDuration()
if got != 1*time.Second {
t.Errorf("expected 1s for zero value, got %v", got)
}
}

// TestGetSocketPermissions_Default tests that missing permissions return 0666.
func TestGetSocketPermissions_Default(t *testing.T) {
s := &UnixSocketConfig{SocketPermissions: ""}
if got := s.GetSocketPermissions(); got != 0666 {
t.Errorf("expected 0666, got %o", got)
}
}

// TestGetSocketPermissions_Valid tests valid octal string parsing.
func TestGetSocketPermissions_Valid(t *testing.T) {
cases := []struct {
input    string
expected os.FileMode
}{
{"0660", 0660},
{"0666", 0666},
{"660", 0660},
{"0777", 0777},
}

for _, tc := range cases {
t.Run(tc.input, func(t *testing.T) {
s := &UnixSocketConfig{SocketPermissions: tc.input}
if got := s.GetSocketPermissions(); got != tc.expected {
t.Errorf("expected %o, got %o", tc.expected, got)
}
})
}
}

// TestGetSocketPermissions_Invalid tests that invalid string returns 0666.
func TestGetSocketPermissions_Invalid(t *testing.T) {
s := &UnixSocketConfig{SocketPermissions: "not-valid"}
if got := s.GetSocketPermissions(); got != 0666 {
t.Errorf("expected 0666 for invalid input, got %o", got)
}
}

// TestIsSourceIPExcluded_Empty tests that empty exclude list never excludes.
func TestIsSourceIPExcluded_Empty(t *testing.T) {
cfg := &CorrelationConfig{}
if cfg.IsSourceIPExcluded("1.2.3.4") {
t.Error("empty exclude list should not exclude any IP")
}
}

// TestIsSourceIPExcluded_ExactMatch tests exact IP exclusion.
func TestIsSourceIPExcluded_ExactMatch(t *testing.T) {
cfg := &CorrelationConfig{ExcludeSourceIPs: []string{"1.2.3.4"}}
if !cfg.IsSourceIPExcluded("1.2.3.4") {
t.Error("exact match IP should be excluded")
}
if cfg.IsSourceIPExcluded("1.2.3.5") {
t.Error("non-matching IP should not be excluded")
}
}

// TestIsSourceIPExcluded_CIDR tests CIDR range exclusion.
func TestIsSourceIPExcluded_CIDR(t *testing.T) {
cfg := &CorrelationConfig{ExcludeSourceIPs: []string{"10.0.0.0/8"}}
if !cfg.IsSourceIPExcluded("10.1.2.3") {
t.Error("IP in CIDR range should be excluded")
}
if cfg.IsSourceIPExcluded("192.168.1.1") {
t.Error("IP outside CIDR range should not be excluded")
}
}

// TestIsSourceIPExcluded_InvalidIP tests that invalid IP returns false.
func TestIsSourceIPExcluded_InvalidIP(t *testing.T) {
cfg := &CorrelationConfig{ExcludeSourceIPs: []string{"1.2.3.4"}}
if cfg.IsSourceIPExcluded("not-an-ip") {
t.Error("invalid IP should not be excluded")
}
}

// TestGetMatchingMode_Default tests that default matching mode is one_to_many.
func TestGetMatchingMode_Default(t *testing.T) {
cfg := &CorrelationConfig{}
if got := cfg.GetMatchingMode(); got != "one_to_many" {
t.Errorf("expected one_to_many, got %s", got)
}
}

// TestGetMatchingMode_Explicit tests that explicit matching mode is returned.
func TestGetMatchingMode_Explicit(t *testing.T) {
cfg := &CorrelationConfig{Matching: MatchingConfig{Mode: "one_to_one"}}
if got := cfg.GetMatchingMode(); got != "one_to_one" {
t.Errorf("expected one_to_one, got %s", got)
}
}

// TestGetApacheAlwaysEmit tests the OrphanPolicy.ApacheAlwaysEmit flag.
func TestGetApacheAlwaysEmit_OrphanPolicySet(t *testing.T) {
cfg := &CorrelationConfig{OrphanPolicy: OrphanPolicyConfig{ApacheAlwaysEmit: true}}
if !cfg.GetApacheAlwaysEmit() {
t.Error("expected true when OrphanPolicy.ApacheAlwaysEmit is true")
}
}

// TestGetApacheAlwaysEmit_FallbackEmitOrphans tests deprecated fallback.
func TestGetApacheAlwaysEmit_FallbackEmitOrphans(t *testing.T) {
cfg := &CorrelationConfig{EmitOrphans: true}
if !cfg.GetApacheAlwaysEmit() {
t.Error("expected true when deprecated EmitOrphans is true")
}
}

// TestLogConfig_GetLevel_Cases tests all branches of GetLevel().
func TestLogConfig_GetLevel_Cases(t *testing.T) {
cases := []struct {
input    string
expected string
}{
{"", "INFO"},
{"debug", "DEBUG"},
{"WARN", "WARN"},
{"error", "ERROR"},
}
for _, tc := range cases {
t.Run(tc.input, func(t *testing.T) {
cfg := &LogConfig{Level: tc.input}
if got := cfg.GetLevel(); got != tc.expected {
t.Errorf("expected %s, got %s", tc.expected, got)
}
})
}
}

// TestValidate_OnlyOneSocket tests that config with one socket fails validation.
func TestValidate_OnlyOneSocket(t *testing.T) {
cfg := &Config{
Inputs: InputsConfig{
UnixSockets: []UnixSocketConfig{
{Name: "http", Path: "/tmp/http.sock"},
},
},
Outputs: OutputsConfig{
Stdout: StdoutOutputConfig{Enabled: true},
},
Correlation: CorrelationConfig{TimeWindowS: 1},
}
if err := cfg.Validate(); err == nil {
t.Error("expected error for less than 2 unix socket inputs")
}
}

// TestValidate_EmptySocketName tests that empty socket name fails validation.
func TestValidate_EmptySocketName(t *testing.T) {
cfg := &Config{
Inputs: InputsConfig{
UnixSockets: []UnixSocketConfig{
{Name: "", Path: "/tmp/a.sock"},
{Name: "b", Path: "/tmp/b.sock"},
},
},
Outputs: OutputsConfig{
Stdout: StdoutOutputConfig{Enabled: true},
},
Correlation: CorrelationConfig{TimeWindowS: 1},
}
if err := cfg.Validate(); err == nil {
t.Error("expected error for empty socket name")
}
}

// TestValidate_EmptySocketPath tests that empty socket path fails validation.
func TestValidate_EmptySocketPath(t *testing.T) {
cfg := &Config{
Inputs: InputsConfig{
UnixSockets: []UnixSocketConfig{
{Name: "a", Path: ""},
{Name: "b", Path: "/tmp/b.sock"},
},
},
Outputs: OutputsConfig{
Stdout: StdoutOutputConfig{Enabled: true},
},
Correlation: CorrelationConfig{TimeWindowS: 1},
}
if err := cfg.Validate(); err == nil {
t.Error("expected error for empty socket path")
}
}

// TestGetTimeWindow_NewFieldTakesPrecedence tests that TimeWindow.Value overrides TimeWindowS.
func TestGetTimeWindow_NewFieldTakesPrecedence(t *testing.T) {
cfg := &CorrelationConfig{
TimeWindow:  TimeWindowConfig{Value: 5, Unit: "s"},
TimeWindowS: 99,
}
got := cfg.GetTimeWindow()
if got != 5*time.Second {
t.Errorf("expected 5s, got %v", got)
}
}

// TestGetTimeWindow_FallbackToDeprecated tests fallback to deprecated TimeWindowS.
func TestGetTimeWindow_FallbackToDeprecated(t *testing.T) {
cfg := &CorrelationConfig{TimeWindowS: 3}
got := cfg.GetTimeWindow()
if got != 3*time.Second {
t.Errorf("expected 3s, got %v", got)
}
}
