package validators

import (
	"testing"

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

func TestDomainValidator_SafeHostname(t *testing.T) {
	type testCase struct {
		description string
		options     DomainValidatorOptions
		input       string
		expected    string
		errorFunc   func(t *testing.T, e error)
	}

	tests := []testCase{
		{
			description: "Empty url fails",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "url cannot be empty")
			},
		},
		{
			description: "URL without host fails",
			input:       "/foo",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "missing host in url")
			},
		},
		{
			description: "Invalid url fails",
			input:       "foo:foo",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "failed to parse host")
			},
		},
		{
			description: "With scheme and invalid url should fail",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https"}},
			input:       "https://foo:foo",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "failed to parse input url")
			},
		},
		{
			description: "Scheme disabled with scheme should pass",
			input:       "https://example.com",
			expected:    "example.com",
		},
		{
			description: "Domain without scheme should parse if scheme is disabled",
			input:       "example.com",
			expected:    "example.com",
		},
		{
			description: "Domain without scheme should not parse if scheme is enabled",
			options:     DomainValidatorOptions{WithScheme: true},
			input:       "example.com",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "invalid scheme")
			},
		},
		{
			description: "Domain with scheme and disallowed scheme should fail",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https"}},
			input:       "foo://example.com",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "invalid scheme")
			},
		},
		{
			description: "Domain with scheme and allowed scheme should pass",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https"}},
			input:       "https://example.com",
			expected:    "example.com",
		},
		{
			description: "Domain should get lowercased",
			input:       "EXAMPLE.COM",
			expected:    "example.com",
		},
		{
			description: "DNS dot should be removed",
			input:       "example.com.",
			expected:    "example.com",
		},
		{
			description: "IPv4 address should fail",
			input:       "127.0.0.1",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "ip addresses are not supported")
			},
		},
		{
			description: "IPv6 address should fail",
			input:       "[::1]",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "ip addresses are not supported")
			},
		},
		{
			description: "With port enabled without any port should work",
			options:     DomainValidatorOptions{WithPort: true},
			input:       "example.com",
			expected:    "example.com",
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			v := NewDomainValidator(test.options)
			res, err := v.SafeHostname(test.input)
			if test.errorFunc != nil {
				test.errorFunc(t, err)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, test.expected, res)
		})
	}
}

func TestDomainValidator_Validate(t *testing.T) {
	type testCase struct {
		description string
		options     DomainValidatorOptions
		expected    string
		actual      string
		errorFunc   func(t *testing.T, e error)
	}

	tests := []testCase{
		{
			description: "Invalid expected domain fails checks",
			expected:    "foo:foo",
			actual:      "bar.com",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "failed to parse host:")
			},
		},
		{
			description: "Invalid check domain fails checks",
			expected:    "example.com",
			actual:      "foo:foo",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorContains(t, e, "failed to parse host:")
			},
		},
		{
			description: "Valid domains with non-matching schemes should fail",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}},
			expected:    "https://example.com",
			actual:      "http://example.com",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorIs(t, e, ErrSchemeMismatch)
			},
		},
		{
			description: "Valid domains with matching schemes should pass",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}},
			expected:    "https://example.com",
			actual:      "https://example.com",
		},
		{
			description: "Port validation with ports enabled and empty ports should work",
			options:     DomainValidatorOptions{WithPort: true},
			expected:    "example.com",
			actual:      "example.com",
		},
		{
			description: "Port validation should pass with same port",
			options:     DomainValidatorOptions{WithPort: true},
			expected:    "example.com:8080",
			actual:      "example.com:8080",
		},
		{
			description: "Port enabled with scheme and matching port should pass",
			options:     DomainValidatorOptions{WithPort: true},
			expected:    "https://example.com:443",
			actual:      "https://example.com:443",
		},
		{
			description: "Valid domains with matching schemes and ports should pass",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}, WithPort: true},
			expected:    "https://example.com:8080",
			actual:      "https://example.com:8080",
		},
		{
			description: "Valid domains with matching schemes should pass",
			options:     DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}},
			expected:    "https://example.com",
			actual:      "https://example.com",
		},
		{
			description: "Valid domains with non matching ports should fail",
			options:     DomainValidatorOptions{WithPort: true},
			expected:    "example.com:8080",
			actual:      "example.com:8085",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorIs(t, e, ErrPortMismatch)
			},
		},
		{
			description: "Valid domains without ports or schemes should pass",
			actual:      "example.com",
			expected:    "example.com",
		},
		{
			description: "Non matching hostnames should fail",
			expected:    "example.com",
			actual:      "foo.com",
			errorFunc: func(t *testing.T, e error) {
				assert.ErrorIs(t, e, ErrHostnameMismatch)
			},
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			v := NewDomainValidator(test.options)
			err := v.Validate(test.expected, test.actual)
			if test.errorFunc != nil {
				test.errorFunc(t, err)
				return
			}
			require.NoError(t, err)
		})
	}
}
