package config

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type ConfigTestSuite struct {
	suite.Suite
	tempDir string
}

func TestConfigTestSuite(t *testing.T) {
	suite.Run(t, new(ConfigTestSuite))
}

func (s *ConfigTestSuite) SetupTest() {
	var err error
	s.tempDir, err = os.MkdirTemp("", "gohoarder-config-test-*")
	s.Require().NoError(err)
}

func (s *ConfigTestSuite) TearDownTest() {
	_ = os.RemoveAll(s.tempDir) // #nosec G104 -- Cleanup
}

func (s *ConfigTestSuite) TestDefault() {
	cfg := Default()
	s.NotNil(cfg)
	s.Equal("0.0.0.0", cfg.Server.Host)
	s.Equal(8080, cfg.Server.Port)
	s.Equal("filesystem", cfg.Storage.Backend)
	s.Equal("sqlite", cfg.Metadata.Backend)
	s.NoError(cfg.Validate())
}

func (s *ConfigTestSuite) TestValidate() {
	tests := []struct {
		modify      func(*Config)
		name        string
		errorSubstr string
		expectError bool
	}{
		{
			name:        "valid_config",
			modify:      func(c *Config) {},
			expectError: false,
		},
		{
			name: "invalid_port_too_low",
			modify: func(c *Config) {
				c.Server.Port = 0
			},
			expectError: true,
			errorSubstr: "port must be between",
		},
		{
			name: "invalid_port_too_high",
			modify: func(c *Config) {
				c.Server.Port = 70000
			},
			expectError: true,
			errorSubstr: "port must be between",
		},
		{
			name: "invalid_storage_backend",
			modify: func(c *Config) {
				c.Storage.Backend = "invalid"
			},
			expectError: true,
			errorSubstr: "storage.backend must be one of",
		},
		{
			name: "invalid_metadata_backend",
			modify: func(c *Config) {
				c.Metadata.Backend = "mongodb"
			},
			expectError: true,
			errorSubstr: "metadata.backend must be one of",
		},
		{
			name: "negative_ttl",
			modify: func(c *Config) {
				c.Cache.DefaultTTL = -1 * time.Hour
			},
			expectError: true,
			errorSubstr: "cannot be negative",
		},
		{
			name: "negative_cache_size",
			modify: func(c *Config) {
				c.Cache.MaxSizeBytes = -100
			},
			expectError: true,
			errorSubstr: "cannot be negative",
		},
		{
			name: "invalid_severity",
			modify: func(c *Config) {
				c.Security.BlockOnSeverity = "super-high"
			},
			expectError: true,
			errorSubstr: "block_on_severity must be one of",
		},
		{
			name: "invalid_log_level",
			modify: func(c *Config) {
				c.Logging.Level = "verbose"
			},
			expectError: true,
			errorSubstr: "logging.level must be one of",
		},
		{
			name: "invalid_log_format",
			modify: func(c *Config) {
				c.Logging.Format = "xml"
			},
			expectError: true,
			errorSubstr: "logging.format must be one of",
		},
		{
			name: "invalid_bcrypt_cost_too_low",
			modify: func(c *Config) {
				c.Auth.BcryptCost = 3
			},
			expectError: true,
			errorSubstr: "bcrypt_cost must be between",
		},
		{
			name: "invalid_bcrypt_cost_too_high",
			modify: func(c *Config) {
				c.Auth.BcryptCost = 32
			},
			expectError: true,
			errorSubstr: "bcrypt_cost must be between",
		},
		{
			name: "valid_s3_backend",
			modify: func(c *Config) {
				c.Storage.Backend = "s3"
			},
			expectError: false,
		},
		{
			name: "valid_postgresql_backend",
			modify: func(c *Config) {
				c.Metadata.Backend = "postgresql"
			},
			expectError: false,
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			cfg := Default()
			tt.modify(cfg)
			err := cfg.Validate()

			if tt.expectError {
				s.Error(err)
				if tt.errorSubstr != "" {
					s.Contains(err.Error(), tt.errorSubstr)
				}
			} else {
				s.NoError(err)
			}
		})
	}
}

func (s *ConfigTestSuite) TestLoad() {
	tests := []struct {
		envVars     map[string]string
		validate    func(*Config)
		name        string
		configYAML  string
		expectError bool
	}{
		{
			name: "valid_yaml_config",
			configYAML: `
server:
  host: 127.0.0.1
  port: 9000
storage:
  backend: filesystem
  path: /custom/path
logging:
  level: debug
  format: pretty
`,
			expectError: false,
			validate: func(cfg *Config) {
				s.Equal("127.0.0.1", cfg.Server.Host)
				s.Equal(9000, cfg.Server.Port)
				s.Equal("/custom/path", cfg.Storage.Path)
				s.Equal("debug", cfg.Logging.Level)
				s.Equal("pretty", cfg.Logging.Format)
			},
		},
		{
			name: "env_var_override",
			configYAML: `
server:
  port: 8080
`,
			envVars: map[string]string{
				"GOHOARDER_SERVER_PORT": "9090",
			},
			expectError: false,
			validate: func(cfg *Config) {
				s.Equal(9090, cfg.Server.Port)
			},
		},
		{
			name: "invalid_yaml",
			configYAML: `
server: [invalid
`,
			expectError: true,
		},
		{
			name: "validation_failure",
			configYAML: `
server:
  port: 100000
`,
			expectError: true,
		},
		{
			name: "complete_config",
			configYAML: `
server:
  host: 0.0.0.0
  port: 8080
  read_timeout: 300s
  write_timeout: 300s
storage:
  backend: s3
  s3:
    endpoint: s3.amazonaws.com
    region: us-east-1
    bucket: my-cache
    access_key_id: AKIAIOSFODNN7EXAMPLE
    secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
metadata:
  backend: postgresql
  postgresql:
    host: localhost
    port: 5432
    database: gohoarder
    user: postgres
    password: secret
    ssl_mode: require
cache:
  default_ttl: 168h
  max_size_bytes: 536870912000
security:
  enabled: true
  block_on_severity: high
  scanners:
    trivy:
      enabled: true
      timeout: 300s
auth:
  enabled: true
  bcrypt_cost: 12
`,
			expectError: false,
			validate: func(cfg *Config) {
				s.Equal("s3", cfg.Storage.Backend)
				s.Equal("s3.amazonaws.com", cfg.Storage.S3.Endpoint)
				s.Equal("postgresql", cfg.Metadata.Backend)
				s.Equal("localhost", cfg.Metadata.PostgreSQL.Host)
				s.True(cfg.Security.Enabled)
				s.Equal(12, cfg.Auth.BcryptCost)
			},
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			// Write config file
			configPath := filepath.Join(s.tempDir, "config.yaml")
			err := os.WriteFile(configPath, []byte(tt.configYAML), 0600)
			s.Require().NoError(err)

			// Set environment variables
			for k, v := range tt.envVars {
				_ = os.Setenv(k, v)  // #nosec G104 -- test setup, error not actionable
				defer os.Unsetenv(k) //nolint:errcheck // test cleanup
			}

			// Load config
			cfg, err := Load(configPath)

			if tt.expectError {
				s.Error(err)
			} else {
				s.NoError(err)
				s.NotNil(cfg)
				if tt.validate != nil {
					tt.validate(cfg)
				}
			}
		})
	}
}

func (s *ConfigTestSuite) TestLoadMissingFile() {
	// Should return error when file explicitly specified but not found
	cfg, err := Load("/nonexistent/path/to/config.yaml")
	s.Error(err)
	s.Nil(cfg)
}

// Benchmark tests
func BenchmarkDefault(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = Default()
	}
}

func BenchmarkValidate(b *testing.B) {
	cfg := Default()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = cfg.Validate()
	}
}

// Table-driven edge cases
func TestConfigEdgeCases(t *testing.T) {
	tests := []struct {
		config *Config
		name   string
		valid  bool
	}{
		{
			name:   "minimal_config",
			config: &Config{Server: ServerConfig{Port: 8080}, Storage: StorageConfig{Backend: "filesystem"}, Metadata: MetadataConfig{Backend: "sqlite"}, Logging: LoggingConfig{Level: "info", Format: "json"}, Security: SecurityConfig{BlockOnSeverity: "high"}, Auth: AuthConfig{BcryptCost: 10}},
			valid:  true,
		},
		{
			name:   "zero_ttl",
			config: func() *Config { c := Default(); c.Cache.DefaultTTL = 0; return c }(),
			valid:  true, // Zero is valid (no caching)
		},
		{
			name:   "max_bcrypt_cost",
			config: func() *Config { c := Default(); c.Auth.BcryptCost = 31; return c }(),
			valid:  true,
		},
		{
			name:   "min_bcrypt_cost",
			config: func() *Config { c := Default(); c.Auth.BcryptCost = 4; return c }(),
			valid:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.config.Validate()
			if tt.valid {
				assert.NoError(t, err)
			} else {
				assert.Error(t, err)
			}
		})
	}
}
