package tor

import (
	"strings"
	"testing"
)

func TestValidateOnionAddress(t *testing.T) {
	tests := []struct {
		name    string
		addr    string
		wantErr bool
	}{
		// Valid v3 .onion addresses
		{
			name:    "valid v3 onion with port",
			addr:    "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:8080",
			wantErr: false,
		},
		{
			name:    "valid v3 onion without port",
			addr:    "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion",
			wantErr: false,
		},
		{
			name:    "valid v3 onion lowercase",
			addr:    "vwxyz234567abcdefghijklmnopqrstuvwxyz234567abcdefghijk.onion:443",
			wantErr: false,
		},

		// Invalid addresses
		{
			name:    "empty",
			addr:    "",
			wantErr: true,
		},
		{
			name:    "clearnet domain",
			addr:    "example.com:8080",
			wantErr: true,
		},
		{
			name:    "clearnet IP",
			addr:    "192.168.1.1:8080",
			wantErr: true,
		},
		{
			name:    "v2 onion (too short)",
			addr:    "abcdefghijklmnop.onion:8080",
			wantErr: true,
		},
		{
			name:    "uppercase not allowed",
			addr:    "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUV.onion",
			wantErr: true,
		},
		{
			name:    "invalid characters",
			addr:    "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrst01.onion",
			wantErr: true, // 0 and 1 not in base32
		},
		{
			name:    "too long",
			addr:    "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuvwxyz.onion",
			wantErr: true,
		},
		{
			name:    "missing .onion",
			addr:    "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv",
			wantErr: true,
		},
		{
			name:    "invalid port",
			addr:    "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:999999",
			wantErr: true,
		},
		{
			name:    "localhost",
			addr:    "localhost:8080",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateOnionAddress(tt.addr)
			if (err != nil) != tt.wantErr {
				t.Errorf("ValidateOnionAddress(%q) error = %v, wantErr %v", tt.addr, err, tt.wantErr)
			}
		})
	}
}

func TestValidateOnionAddress_ErrorMessages(t *testing.T) {
	tests := []struct {
		addr        string
		errContains string
	}{
		{"", "empty"},
		{"example.com", ".onion"},
		{"short.onion", "invalid"},
	}

	for _, tt := range tests {
		err := ValidateOnionAddress(tt.addr)
		if err == nil {
			t.Errorf("Expected error for %q", tt.addr)
			continue
		}
		if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tt.errContains)) {
			t.Errorf("Error %q should contain %q", err.Error(), tt.errContains)
		}
	}
}

func TestOnionRegex(t *testing.T) {
	// Valid patterns
	validAddrs := []string{
		"abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion",
		"abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:80",
		"abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:8080",
		"abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:65535",
	}

	for _, addr := range validAddrs {
		if !OnionRegex.MatchString(addr) {
			t.Errorf("OnionRegex should match %q", addr)
		}
	}

	// Invalid patterns
	invalidAddrs := []string{
		"tooshort.onion",
		"example.com",
		"127.0.0.1:8080",
		"UPPERCASE.onion",
	}

	for _, addr := range invalidAddrs {
		if OnionRegex.MatchString(addr) {
			t.Errorf("OnionRegex should NOT match %q", addr)
		}
	}
}

func TestDefaultProxyAddresses(t *testing.T) {
	if len(DefaultProxyAddresses) == 0 {
		t.Error("DefaultProxyAddresses should not be empty")
	}

	for _, addr := range DefaultProxyAddresses {
		if !strings.HasPrefix(addr, "socks5://") {
			t.Errorf("Proxy address should start with socks5://: %s", addr)
		}
	}
}

func TestNewDialer(t *testing.T) {
	dialer := NewDialer()

	if dialer == nil {
		t.Fatal("NewDialer() returned nil")
	}

	if len(dialer.ProxyAddresses) == 0 {
		t.Error("Dialer should have default proxy addresses")
	}

	if dialer.Timeout == 0 {
		t.Error("Dialer should have default timeout")
	}

	if dialer.KeepAlive == 0 {
		t.Error("Dialer should have default keepalive")
	}
}

func TestDialer_CustomSettings(t *testing.T) {
	dialer := NewDialer()
	dialer.ProxyAddresses = []string{"socks5://custom:1234"}

	if len(dialer.ProxyAddresses) != 1 {
		t.Error("Should allow custom proxy addresses")
	}
}

// Note: These tests don't actually connect to Tor (that would require Tor running)
// They just test the validation and configuration logic

func TestConnect_InvalidAddress(t *testing.T) {
	_, err := Connect("invalid-address", nil)
	if err == nil {
		t.Error("Connect should fail with invalid address")
	}
}

func TestConnect_EmptyAddress(t *testing.T) {
	_, err := Connect("", nil)
	if err == nil {
		t.Error("Connect should fail with empty address")
	}
}

func TestDialer_Dial_InvalidAddress(t *testing.T) {
	dialer := NewDialer()
	_, err := dialer.Dial("not-an-onion")
	if err == nil {
		t.Error("Dial should fail with invalid address")
	}
}

func TestConnectWithCallback_InvalidAddress(t *testing.T) {
	var progressMessages []string
	callback := func(msg string) {
		progressMessages = append(progressMessages, msg)
	}

	_, err := ConnectWithCallback("invalid", nil, callback)
	if err == nil {
		t.Error("ConnectWithCallback should fail with invalid address")
	}
}

func TestTestProxyAvailable_InvalidURL(t *testing.T) {
	err := TestProxyAvailable("not-a-valid-url")
	if err == nil {
		t.Error("TestProxyAvailable should fail with invalid URL")
	}
}

func TestTestProxyAvailable_NotRunning(t *testing.T) {
	// This proxy shouldn't exist
	err := TestProxyAvailable("socks5://127.0.0.1:59999")
	if err == nil {
		t.Error("TestProxyAvailable should fail when proxy not running")
	}
}

func TestDialer_IsAvailable_NoProxy(t *testing.T) {
	dialer := &Dialer{
		ProxyAddresses: []string{"socks5://127.0.0.1:59999"}, // Non-existent
	}

	if dialer.IsAvailable() {
		t.Error("IsAvailable should return false when no proxy running")
	}
}

// Test constants
func TestConstants(t *testing.T) {
	if DefaultConnectionTimeout == 0 {
		t.Error("DefaultConnectionTimeout should not be zero")
	}
	if DefaultKeepAlive == 0 {
		t.Error("DefaultKeepAlive should not be zero")
	}
	if ProxyTestTimeout == 0 {
		t.Error("ProxyTestTimeout should not be zero")
	}
}

// Benchmark validation
func BenchmarkValidateOnionAddress_Valid(b *testing.B) {
	addr := "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:8080"
	for i := 0; i < b.N; i++ {
		ValidateOnionAddress(addr)
	}
}

func BenchmarkValidateOnionAddress_Invalid(b *testing.B) {
	addr := "example.com"
	for i := 0; i < b.N; i++ {
		ValidateOnionAddress(addr)
	}
}

func BenchmarkOnionRegex_Match(b *testing.B) {
	addr := "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion:8080"
	for i := 0; i < b.N; i++ {
		OnionRegex.MatchString(addr)
	}
}
