package xmlparser

import (
	"errors"
	"strings"
	"testing"
)

func TestValidationError(t *testing.T) {
	tests := []struct {
		name     string
		field    string
		message  string
		value    string
		expected string
	}{
		{
			name:     "Error with value",
			field:    "name",
			message:  "is required",
			value:    "empty",
			expected: "validation error in field 'name': is required (value: empty)",
		},
		{
			name:     "Error without value",
			field:    "id",
			message:  "must be unique",
			value:    "",
			expected: "validation error in field 'id': must be unique",
		},
		{
			name:     "Complex error message",
			field:    "email",
			message:  "must be a valid email address",
			value:    "invalid-email",
			expected: "validation error in field 'email': must be a valid email address (value: invalid-email)",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidationError{
				Field:   tt.field,
				Message: tt.message,
				Value:   tt.value,
			}

			if err.Error() != tt.expected {
				t.Errorf("Expected error message '%s', got '%s'", tt.expected, err.Error())
			}
		})
	}
}

func TestValidationErrors(t *testing.T) {
	tests := []struct {
		name     string
		errors   ValidationErrors
		expected string
	}{
		{
			name:     "Empty validation errors",
			errors:   ValidationErrors{},
			expected: "no validation errors",
		},
		{
			name: "Single validation error",
			errors: ValidationErrors{
				{Field: "name", Message: "is required"},
			},
			expected: "validation failed with 1 error(s): validation error in field 'name': is required",
		},
		{
			name: "Multiple validation errors",
			errors: ValidationErrors{
				{Field: "name", Message: "is required"},
				{Field: "email", Message: "must be valid", Value: "invalid"},
			},
			expected: "validation failed with 2 error(s): validation error in field 'name': is required; validation error in field 'email': must be valid (value: invalid)",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.errors.Error() != tt.expected {
				t.Errorf("Expected error message '%s', got '%s'", tt.expected, tt.errors.Error())
			}
		})
	}
}

func TestValidationErrorsIsEmpty(t *testing.T) {
	tests := []struct {
		name     string
		errors   ValidationErrors
		expected bool
	}{
		{
			name:     "Empty errors",
			errors:   ValidationErrors{},
			expected: true,
		},
		{
			name:     "Nil errors",
			errors:   nil,
			expected: true,
		},
		{
			name: "Non-empty errors",
			errors: ValidationErrors{
				{Field: "test", Message: "test error"},
			},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.errors.IsEmpty() != tt.expected {
				t.Errorf("Expected IsEmpty() to return %v, got %v", tt.expected, tt.errors.IsEmpty())
			}
		})
	}
}

func TestValidationErrorsAdd(t *testing.T) {
	var errors ValidationErrors

	// Test adding errors
	errors.Add("field1", "message1", "value1")
	errors.Add("field2", "message2", "")

	if len(errors) != 2 {
		t.Errorf("Expected 2 errors, got %d", len(errors))
	}

	// Test first error
	if errors[0].Field != "field1" {
		t.Errorf("Expected first error field 'field1', got '%s'", errors[0].Field)
	}
	if errors[0].Message != "message1" {
		t.Errorf("Expected first error message 'message1', got '%s'", errors[0].Message)
	}
	if errors[0].Value != "value1" {
		t.Errorf("Expected first error value 'value1', got '%s'", errors[0].Value)
	}

	// Test second error
	if errors[1].Field != "field2" {
		t.Errorf("Expected second error field 'field2', got '%s'", errors[1].Field)
	}
	if errors[1].Message != "message2" {
		t.Errorf("Expected second error message 'message2', got '%s'", errors[1].Message)
	}
	if errors[1].Value != "" {
		t.Errorf("Expected second error value to be empty, got '%s'", errors[1].Value)
	}
}

func TestXMLParseError(t *testing.T) {
	tests := []struct {
		name     string
		error    XMLParseError
		expected string
	}{
		{
			name: "Complete error information",
			error: XMLParseError{
				Line:     10,
				Column:   5,
				Element:  "TestElement",
				Message:  "unexpected end of element",
				Original: errors.New("original error"),
			},
			expected: "XML parse error (element 'TestElement', line 10, column 5): unexpected end of element",
		},
		{
			name: "Error with line only",
			error: XMLParseError{
				Line:    15,
				Message: "syntax error",
			},
			expected: "XML parse error (line 15): syntax error",
		},
		{
			name: "Error with element only",
			error: XMLParseError{
				Element: "BadElement",
				Message: "invalid element",
			},
			expected: "XML parse error (element 'BadElement'): invalid element",
		},
		{
			name: "Minimal error",
			error: XMLParseError{
				Message: "general parse error",
			},
			expected: "XML parse error: general parse error",
		},
		{
			name: "Error with line and column",
			error: XMLParseError{
				Line:    20,
				Column:  8,
				Message: "missing closing tag",
			},
			expected: "XML parse error (line 20, column 8): missing closing tag",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.error.Error() != tt.expected {
				t.Errorf("Expected error message '%s', got '%s'", tt.expected, tt.error.Error())
			}
		})
	}
}

func TestXMLParseErrorUnwrap(t *testing.T) {
	originalErr := errors.New("original error")
	xmlErr := XMLParseError{
		Message:  "wrapper error",
		Original: originalErr,
	}

	unwrapped := xmlErr.Unwrap()
	if unwrapped != originalErr {
		t.Errorf("Expected unwrapped error to be original error, got %v", unwrapped)
	}

	// Test with no original error
	xmlErrNoOriginal := XMLParseError{
		Message: "standalone error",
	}

	unwrappedNil := xmlErrNoOriginal.Unwrap()
	if unwrappedNil != nil {
		t.Errorf("Expected unwrapped error to be nil, got %v", unwrappedNil)
	}
}

func TestSanitizeXMLString(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "Clean string",
			input:    "Hello World",
			expected: "Hello World",
		},
		{
			name:     "String with valid whitespace",
			input:    "Hello\tWorld\nNew Line\rCarriage Return",
			expected: "Hello\tWorld\nNew Line\rCarriage Return",
		},
		{
			name:     "String with control characters",
			input:    "Hello\x00\x01\x02World",
			expected: "HelloWorld",
		},
		{
			name:     "String with high Unicode",
			input:    "Hello 世界 🌍",
			expected: "Hello 世界 🌍",
		},
		{
			name:     "Empty string",
			input:    "",
			expected: "",
		},
		{
			name:     "String with only control characters",
			input:    "\x00\x01\x02\x03",
			expected: "",
		},
		{
			name:     "Mixed valid and invalid characters",
			input:    "Valid\x00Invalid\tTab\x01More\nNewline",
			expected: "ValidInvalid\tTabMore\nNewline",
		},
		{
			name:     "XML special characters",
			input:    "<>&\"'",
			expected: "<>&\"'",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := SanitizeXMLString(tt.input)
			if result != tt.expected {
				t.Errorf("Expected '%s', got '%s'", tt.expected, result)
			}
		})
	}
}

func TestValidateXMLName(t *testing.T) {
	tests := []struct {
		name     string
		xmlName  string
		expected bool
	}{
		// Valid names
		{
			name:     "Simple name",
			xmlName:  "element",
			expected: true,
		},
		{
			name:     "Name with underscore",
			xmlName:  "_element",
			expected: true,
		},
		{
			name:     "Name with uppercase",
			xmlName:  "Element",
			expected: true,
		},
		{
			name:     "Name with numbers",
			xmlName:  "element123",
			expected: true,
		},
		{
			name:     "Name with hyphen",
			xmlName:  "my-element",
			expected: true,
		},
		{
			name:     "Name with period",
			xmlName:  "my.element",
			expected: true,
		},
		{
			name:     "Complex valid name",
			xmlName:  "My_Element-123.test",
			expected: true,
		},
		// Invalid names
		{
			name:     "Empty name",
			xmlName:  "",
			expected: false,
		},
		{
			name:     "Name starting with number",
			xmlName:  "123element",
			expected: false,
		},
		{
			name:     "Name starting with hyphen",
			xmlName:  "-element",
			expected: false,
		},
		{
			name:     "Name starting with period",
			xmlName:  ".element",
			expected: false,
		},
		{
			name:     "Name with space",
			xmlName:  "my element",
			expected: false,
		},
		{
			name:     "Name with special characters",
			xmlName:  "element@test",
			expected: false,
		},
		{
			name:     "Name with colon",
			xmlName:  "element:test",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ValidateXMLName(tt.xmlName)
			if result != tt.expected {
				t.Errorf("Expected ValidateXMLName('%s') to return %v, got %v", tt.xmlName, tt.expected, result)
			}
		})
	}
}

func TestIsValidNamespace(t *testing.T) {
	tests := []struct {
		name      string
		namespace string
		expected  bool
	}{
		// Valid namespaces
		{
			name:      "Empty namespace",
			namespace: "",
			expected:  true,
		},
		{
			name:      "HTTP namespace",
			namespace: "http://example.com",
			expected:  true,
		},
		{
			name:      "HTTPS namespace",
			namespace: "https://example.com",
			expected:  true,
		},
		{
			name:      "URN namespace",
			namespace: "urn:example:namespace",
			expected:  true,
		},
		{
			name:      "Complex HTTP namespace",
			namespace: "http://www.example.com/schema/2023",
			expected:  true,
		},
		{
			name:      "Complex HTTPS namespace",
			namespace: "https://schemas.example.org/xml/namespace",
			expected:  true,
		},
		{
			name:      "Complex URN namespace",
			namespace: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
			expected:  true,
		},
		// Invalid namespaces
		{
			name:      "Invalid protocol",
			namespace: "ftp://example.com",
			expected:  false,
		},
		{
			name:      "No protocol",
			namespace: "example.com",
			expected:  false,
		},
		{
			name:      "Invalid format",
			namespace: "not-a-namespace",
			expected:  false,
		},
		{
			name:      "Partial HTTP",
			namespace: "http:",
			expected:  false,
		},
		{
			name:      "Partial URN",
			namespace: "urn",
			expected:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsValidNamespace(tt.namespace)
			if result != tt.expected {
				t.Errorf("Expected IsValidNamespace('%s') to return %v, got %v", tt.namespace, tt.expected, result)
			}
		})
	}
}

func TestNormalizeWhitespace(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "Single spaces",
			input:    "hello world",
			expected: "hello world",
		},
		{
			name:     "Multiple spaces",
			input:    "hello    world",
			expected: "hello world",
		},
		{
			name:     "Mixed whitespace",
			input:    "hello\t\n\r world",
			expected: "hello world",
		},
		{
			name:     "Leading whitespace",
			input:    "   hello world",
			expected: "hello world",
		},
		{
			name:     "Trailing whitespace",
			input:    "hello world   ",
			expected: "hello world",
		},
		{
			name:     "Leading and trailing whitespace",
			input:    "   hello world   ",
			expected: "hello world",
		},
		{
			name:     "Multiple words with mixed whitespace",
			input:    "one\ttwo\n\nthree\r\r\rfour     five",
			expected: "one two three four five",
		},
		{
			name:     "Empty string",
			input:    "",
			expected: "",
		},
		{
			name:     "Only whitespace",
			input:    "   \t\n\r   ",
			expected: "",
		},
		{
			name:     "Single word",
			input:    "hello",
			expected: "hello",
		},
		{
			name:     "Single word with whitespace",
			input:    "  hello  ",
			expected: "hello",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := NormalizeWhitespace(tt.input)
			if result != tt.expected {
				t.Errorf("Expected '%s', got '%s'", tt.expected, result)
			}
		})
	}
}

func TestTruncateString(t *testing.T) {
	tests := []struct {
		name      string
		input     string
		maxLength int
		expected  string
	}{
		{
			name:      "Short string",
			input:     "hello",
			maxLength: 10,
			expected:  "hello",
		},
		{
			name:      "Exact length",
			input:     "hello",
			maxLength: 5,
			expected:  "hello",
		},
		{
			name:      "Truncate with ellipsis",
			input:     "hello world",
			maxLength: 8,
			expected:  "hello...",
		},
		{
			name:      "Very short max length",
			input:     "hello",
			maxLength: 3,
			expected:  "hel",
		},
		{
			name:      "Max length 0",
			input:     "hello",
			maxLength: 0,
			expected:  "",
		},
		{
			name:      "Max length 1",
			input:     "hello",
			maxLength: 1,
			expected:  "h",
		},
		{
			name:      "Max length 2",
			input:     "hello",
			maxLength: 2,
			expected:  "he",
		},
		{
			name:      "Empty string",
			input:     "",
			maxLength: 5,
			expected:  "",
		},
		{
			name:      "Long string truncation",
			input:     "This is a very long string that needs to be truncated",
			maxLength: 20,
			expected:  "This is a very lo...",
		},
		{
			name:      "Unicode string",
			input:     "Hello 世界",
			maxLength: 8,
			expected:  "Hello...",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := TruncateString(tt.input, tt.maxLength)
			if result != tt.expected {
				t.Errorf("Expected '%s', got '%s'", tt.expected, result)
			}

			// Verify result length doesn't exceed maxLength
			if len(result) > tt.maxLength {
				t.Errorf("Result length %d exceeds maxLength %d", len(result), tt.maxLength)
			}
		})
	}
}

func TestUtilityFunctionsWithRealWorldData(t *testing.T) {
	// Test with real-world XML-like data
	t.Run("Real world XML sanitization", func(t *testing.T) {
		input := "Contract Notice\x00 for\x01 Public\x02 Procurement\t\nProject"
		expected := "Contract Notice for Public Procurement\t\nProject"
		result := SanitizeXMLString(input)
		if result != expected {
			t.Errorf("Expected '%s', got '%s'", expected, result)
		}
	})

	t.Run("Real world XML name validation", func(t *testing.T) {
		validNames := []string{
			"ContractNotice",
			"ProcurementProject",
			"TenderingTerms",
			"_privateElement",
			"xml-element",
			"element.version",
		}

		for _, name := range validNames {
			if !ValidateXMLName(name) {
				t.Errorf("Expected '%s' to be valid XML name", name)
			}
		}

		invalidNames := []string{
			"123Contract",
			"-element",
			".element",
			"element with spaces",
			"element@symbol",
		}

		for _, name := range invalidNames {
			if ValidateXMLName(name) {
				t.Errorf("Expected '%s' to be invalid XML name", name)
			}
		}
	})

	t.Run("Real world namespace validation", func(t *testing.T) {
		validNamespaces := []string{
			"",
			"http://www.w3.org/2001/XMLSchema",
			"https://ted.europa.eu/schema/eforms",
			"urn:oasis:names:specification:ubl:schema:xsd:ContractNotice-2",
		}

		for _, ns := range validNamespaces {
			if !IsValidNamespace(ns) {
				t.Errorf("Expected '%s' to be valid namespace", ns)
			}
		}
	})

	t.Run("Real world whitespace normalization", func(t *testing.T) {
		input := "  Contract\t\tNotice\n\n\nfor\r\r\rPublic     Procurement  "
		expected := "Contract Notice for Public Procurement"
		result := NormalizeWhitespace(input)
		if result != expected {
			t.Errorf("Expected '%s', got '%s'", expected, result)
		}
	})

	t.Run("Real world string truncation", func(t *testing.T) {
		longDescription := "This is a very long contract description that contains detailed information about the procurement project including all requirements, specifications, and conditions that need to be met by potential contractors."
		truncated := TruncateString(longDescription, 50)

		if len(truncated) > 50 {
			t.Errorf("Truncated string length %d exceeds limit 50", len(truncated))
		}

		if !strings.HasSuffix(truncated, "...") && len(longDescription) > 50 {
			t.Error("Expected truncated string to end with '...'")
		}
	})
}

func TestErrorTypes(t *testing.T) {
	// Test that our error types implement the error interface
	var _ error = ValidationError{}
	var _ error = ValidationErrors{}
	var _ error = XMLParseError{}

	// Test error interface methods
	ve := ValidationError{Field: "test", Message: "test error"}
	if ve.Error() == "" {
		t.Error("ValidationError.Error() should return non-empty string")
	}

	ves := ValidationErrors{ve}
	if ves.Error() == "" {
		t.Error("ValidationErrors.Error() should return non-empty string")
	}

	xpe := XMLParseError{Message: "test parse error"}
	if xpe.Error() == "" {
		t.Error("XMLParseError.Error() should return non-empty string")
	}
}

func TestValidationErrorsChaining(t *testing.T) {
	var errors ValidationErrors

	// Build up errors
	errors.Add("field1", "error1", "value1")
	errors.Add("field2", "error2", "")
	errors.Add("field3", "error3", "value3")

	if len(errors) != 3 {
		t.Errorf("Expected 3 errors, got %d", len(errors))
	}

	if errors.IsEmpty() {
		t.Error("Expected errors to not be empty")
	}

	errorMessage := errors.Error()
	if !strings.Contains(errorMessage, "validation failed with 3 error(s)") {
		t.Errorf("Error message should contain count, got: %s", errorMessage)
	}

	if !strings.Contains(errorMessage, "field1") {
		t.Error("Error message should contain field1")
	}

	if !strings.Contains(errorMessage, "field2") {
		t.Error("Error message should contain field2")
	}

	if !strings.Contains(errorMessage, "field3") {
		t.Error("Error message should contain field3")
	}
}
