package service

import (
	"net"
	"testing"
)

// =============================================================================
// Webhook Security Functions Tests
// =============================================================================

func TestIsPrivateIP(t *testing.T) {
	tests := []struct {
		name     string
		ip       string
		expected bool
	}{
		// Private ranges - 10.0.0.0/8
		{"10.0.0.0", "10.0.0.0", true},
		{"10.255.255.255", "10.255.255.255", true},
		{"10.1.2.3", "10.1.2.3", true},

		// Private ranges - 172.16.0.0/12
		{"172.16.0.0", "172.16.0.0", true},
		{"172.31.255.255", "172.31.255.255", true},
		{"172.20.1.1", "172.20.1.1", true},

		// Private ranges - 192.168.0.0/16
		{"192.168.0.0", "192.168.0.0", true},
		{"192.168.255.255", "192.168.255.255", true},
		{"192.168.1.100", "192.168.1.100", true},

		// Loopback
		{"127.0.0.1", "127.0.0.1", true},
		{"127.255.255.255", "127.255.255.255", true},
		{"::1", "::1", true},

		// Public IPs
		{"8.8.8.8", "8.8.8.8", false},
		{"1.1.1.1", "1.1.1.1", false},
		{"93.184.216.34", "93.184.216.34", false},
		{"142.250.80.46", "142.250.80.46", false},

		// Edge cases
		{"", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ip := net.ParseIP(tt.ip)
			if tt.ip == "" {
				// Empty IP should return false
				result := isPrivateIP(nil)
				if result != false {
					t.Errorf("isPrivateIP(nil) = %v, want %v", result, false)
				}
				return
			}
			if ip == nil {
				t.Skipf("could not parse IP: %s", tt.ip)
			}
			result := isPrivateIP(ip)
			if result != tt.expected {
				t.Errorf("isPrivateIP(%s) = %v, want %v", tt.ip, result, tt.expected)
			}
		})
	}
}

func TestIsSafeURL(t *testing.T) {
	tests := []struct {
		name     string
		url      string
		expected bool
	}{
		// Valid public HTTPS URLs
		{"https example.com", "https://example.com/webhook", true},
		{"https with path", "https://example.com/api/v1/hook", true},
		{"https with query", "https://example.com/hook?a=1&b=2", true},
		{"https with port", "https://example.com:8443/hook", true},
		{"https subdomains", "https://sub.example.com/hook", true},

		// HTTP (allowed but public only)
		{"http public", "http://example.com/hook", true},
		{"http with port", "http://example.com:8080/hook", true},

		// Invalid schemes
		{"ftp scheme", "ftp://example.com/hook", false},
		{"file scheme", "file:///etc/passwd", false},
		{"data scheme", "data:text/html,<script>alert(1)</script>", false},
		{"javascript scheme", "javascript:alert(1)", false},

		// Localhost blocked
		{"localhost http", "http://localhost/hook", false},
		{"localhost https", "https://localhost/hook", false},
		{"127.0.0.1", "http://127.0.0.1/hook", false},
		{"::1", "http://[::1]/hook", false},

		// Private IPs blocked
		{"10.x.x.x", "http://10.0.0.1/hook", false},
		{"172.16.x.x", "http://172.16.0.1/hook", false},
		{"192.168.x.x", "http://192.168.1.1/hook", false},

		// Internal domains blocked
		{"internal domain", "https://server.internal/hook", false},
		{"local domain", "https://host.local/hook", false},
		{"corp domain", "https://host.corp/hook", false},
		{"lan domain", "https://host.lan/hook", false},
		{"intranet domain", "https://host.intranet/hook", false},

		// Cloud metadata IPs blocked
		{"gcp metadata", "http://metadata.google.internal/", false},
		{"aws metadata", "http://169.254.169.254/latest/meta-data/", false},
		{"azure metadata", "http://metadata.azure.internal/", false},
		{"aliyun metadata", "http://100.100.100.200/latest/meta-data/", false},

		// Invalid URLs
		{"empty", "", false},
		{"no scheme", "example.com/hook", false},
		{"relative", "/hook", false},
	}

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

func TestComputeHMAC(t *testing.T) {
	tests := []struct {
		name    string
	payload []byte
		secret  string
	}{
		{
			name:    "simple payload",
			payload: []byte(`{"event":"user.created"}`),
			secret:  "test-secret",
		},
		{
			name:    "empty payload",
			payload: []byte{},
			secret:  "test-secret",
		},
		{
			name:    "empty secret",
			payload: []byte(`{"event":"user.deleted"}`),
			secret:  "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result1 := computeHMAC(tt.payload, tt.secret)
			result2 := computeHMAC(tt.payload, tt.secret)

			// Same input should produce same output
			if result1 != result2 {
				t.Errorf("computeHMAC not deterministic: got %s and %s", result1, result2)
			}

			// Result should not be empty for non-empty payload
			if len(tt.payload) > 0 && result1 == "" {
				t.Error("computeHMAC returned empty string for non-empty payload")
			}

			// Result should be hex-encoded (64 chars for SHA256)
			if len(result1) != 64 {
				t.Errorf("computeHMAC returned %d chars, want 64 (SHA256 hex)", len(result1))
			}
		})
	}
}

func TestComputeHMAC_DifferentInputs(t *testing.T) {
	payload1 := []byte(`{"event":"user.created"}`)
	payload2 := []byte(`{"event":"user.deleted"}`)
	secret := "test-secret"

	result1 := computeHMAC(payload1, secret)
	result2 := computeHMAC(payload2, secret)

	if result1 == result2 {
		t.Error("Different payloads should produce different HMACs")
	}
}

func TestComputeHMAC_DifferentSecrets(t *testing.T) {
	payload := []byte(`{"event":"user.created"}`)

	result1 := computeHMAC(payload, "secret1")
	result2 := computeHMAC(payload, "secret2")

	if result1 == result2 {
		t.Error("Different secrets should produce different HMACs")
	}
}
