package config

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

	"github.com/jfraeys/fetch_ml/internal/config"
)

// MockValidator implements the Validator interface for testing
type MockValidator struct {
	shouldFail bool
	errorMsg   string
}

func (m *MockValidator) Validate() error {
	if m.shouldFail {
		return fmt.Errorf("validation error: %s", m.errorMsg)
	}
	return nil
}

func TestValidateConfig(t *testing.T) {
	t.Parallel() // Enable parallel execution

	// Test with valid validator
	validValidator := &MockValidator{shouldFail: false}
	err := config.ValidateConfig(validValidator)
	if err != nil {
		t.Errorf("Expected no error for valid validator, got %v", err)
	}

	// Test with invalid validator
	invalidValidator := &MockValidator{shouldFail: true, errorMsg: "validation failed"}
	err = config.ValidateConfig(invalidValidator)
	if err == nil {
		t.Error("Expected error for invalid validator")
	}
	if err.Error() != "validation error: validation failed" {
		t.Errorf("Expected error message 'validation error: validation failed', got %s", err.Error())
	}
}

func TestValidatePort(t *testing.T) {
	t.Parallel() // Enable parallel execution

	// Test valid ports
	validPorts := []int{1, 22, 80, 443, 6379, 65535}
	for _, port := range validPorts {
		err := config.ValidatePort(port)
		if err != nil {
			t.Errorf("Expected no error for valid port %d, got %v", port, err)
		}
	}

	// Test invalid ports
	invalidPorts := []int{0, -1, 65536, 100000}
	for _, port := range invalidPorts {
		err := config.ValidatePort(port)
		if err == nil {
			t.Errorf("Expected error for invalid port %d", port)
		}
	}
}

func TestValidateDirectory(t *testing.T) {
	t.Parallel() // Enable parallel execution

	// Test empty path
	err := config.ValidateDirectory("")
	if err == nil {
		t.Error("Expected error for empty path")
	}

	// Test non-existent directory
	err = config.ValidateDirectory("/nonexistent/directory")
	if err == nil {
		t.Error("Expected error for non-existent directory")
	}

	// Test existing directory
	tempDir := t.TempDir()
	err = config.ValidateDirectory(tempDir)
	if err != nil {
		t.Errorf("Expected no error for existing directory %s, got %v", tempDir, err)
	}

	// Test file instead of directory
	tempFile := filepath.Join(tempDir, "test_file")
	err = os.WriteFile(tempFile, []byte("test"), 0600)
	if err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err = config.ValidateDirectory(tempFile)
	if err == nil {
		t.Error("Expected error for file path")
	}

	// Test directory with environment variable expansion
	// Reuse the tempDir path so validation succeeds after expansion
	_ = os.Setenv("TEST_DIR", tempDir)
	defer func() { _ = os.Unsetenv("TEST_DIR") }()

	err = config.ValidateDirectory("$TEST_DIR")
	if err != nil {
		t.Errorf("Expected no error for expanded directory path, got %v", err)
	}

	// Test directory with tilde expansion (if home directory is available)
	home, err := os.UserHomeDir()
	if err == nil {
		// Create a test directory in home
		testHomeDir := filepath.Join(home, "test_fetch_ml")
		err = os.MkdirAll(testHomeDir, 0750)
		if err == nil {
			defer func() { _ = os.RemoveAll(tempDir) }()

			err = config.ValidateDirectory("~/test_fetch_ml")
			if err != nil {
				t.Errorf("Expected no error for tilde expanded path, got %v", err)
			}
		}
	}
}

func TestValidateRedisAddr(t *testing.T) {
	t.Parallel() // Enable parallel execution

	// Test valid Redis addresses
	validAddrs := []string{
		"localhost:6379",
		"127.0.0.1:6379",
		"redis.example.com:6379",
		"10.0.0.1:6380",
		"[::1]:6379", // IPv6
	}

	for _, addr := range validAddrs {
		err := config.ValidateRedisAddr(addr)
		if err != nil {
			t.Errorf("Expected no error for valid Redis address %s, got %v", addr, err)
		}
	}

	// Test invalid Redis addresses
	invalidAddrs := []string{
		"",                     // empty
		"localhost",            // missing port
		":6379",                // missing host
		"localhost:",           // missing port number
		"localhost:abc",        // non-numeric port
		"localhost:-1",         // negative port
		"localhost:0",          // port too low
		"localhost:65536",      // port too high
		"localhost:999999",     // port way too high
		"multiple:colons:6379", // too many colons
	}

	for _, addr := range invalidAddrs {
		err := config.ValidateRedisAddr(addr)
		if err == nil {
			t.Errorf("Expected error for invalid Redis address %s", addr)
		}
	}
}

func TestValidateRedisAddrEdgeCases(t *testing.T) {
	t.Parallel() // Enable parallel execution

	// Test edge case ports
	edgeCases := []struct {
		addr      string
		shouldErr bool
	}{
		{"localhost:1", false},     // minimum valid port
		{"localhost:65535", false}, // maximum valid port
		{"localhost:0", true},      // below minimum
		{"localhost:65536", true},  // above maximum
	}

	for _, tc := range edgeCases {
		err := config.ValidateRedisAddr(tc.addr)
		if tc.shouldErr && err == nil {
			t.Errorf("Expected error for Redis address %s", tc.addr)
		}
		if !tc.shouldErr && err != nil {
			t.Errorf("Expected no error for Redis address %s, got %v", tc.addr, err)
		}
	}
}

func TestValidatorInterface(t *testing.T) {
	t.Parallel() // Enable parallel execution

	// Test that our mock properly implements the interface
	var _ config.Validator = &MockValidator{}

	// Test that the interface works as expected
	validator := &MockValidator{shouldFail: false}
	err := validator.Validate()
	if err != nil {
		t.Errorf("MockValidator should not fail when shouldFail is false")
	}

	validator = &MockValidator{shouldFail: true, errorMsg: "test error"}
	err = validator.Validate()
	if err == nil {
		t.Error("MockValidator should fail when shouldFail is true")
	}
}
