package xmlparser

import (
	"bytes"
	"fmt"
	"io"
	"strings"
	"sync"
	"testing"
)

// Mock XML data for testing
const (
	validMockXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-123">
	<name>Test Mock Name</name>
	<value>Test mock value</value>
</MockElement>`

	validMockXMLWithNamespace = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement xmlns="http://test.example.com" id="test-456">
	<name>Namespaced Mock</name>
	<value>Namespaced value</value>
</MockElement>`

	invalidMockXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-invalid">
	<name></name>
	<value>Missing name</value>
</MockElement>`

	malformedXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="malformed">
	<name>Unclosed tag</name>
	<value>This will fail</value>
</MockElemen>`

	emptyXML = ``

	largeMockXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="large-test">
	<name>Large Mock</name>
	<value>This is a large XML document with nested elements</value>
	<nested>
		<level1>
			<level2>
				<level3>
					<level4>
						<level5>Deep nesting test</level5>
					</level4>
				</level3>
			</level2>
		</level1>
	</nested>
</MockElement>`

	deeplyNestedXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="deep-test">
	<name>Deep Nesting Test</name>
	<value>Testing depth limits</value>
	<level1><level2><level3><level4><level5>
		<level6><level7><level8><level9><level10>
			<level11><level12><level13><level14><level15>
				<level16><level17><level18><level19><level20>Deep</level20></level19></level18></level17></level16>
			</level15></level14></level13></level12></level11>
		</level10></level9></level8></level7></level6>
	</level5></level4></level3></level2></level1>
</MockElement>`
)

// errorReader is a mock reader that always returns an error
type errorReader struct{}

func (er errorReader) Read(p []byte) (n int, err error) {
	return 0, io.ErrUnexpectedEOF
}

func TestNewParser(t *testing.T) {
	tests := []struct {
		name    string
		options *ParserOptions
	}{
		{
			name:    "Default options",
			options: nil,
		},
		{
			name: "Custom options",
			options: &ParserOptions{
				StrictValidation:      false,
				AllowEmptyElements:    false,
				PreserveWhitespace:    true,
				IgnoreUnknownElements: true,
				MaxDepth:              50,
				MaxSize:               1024 * 1024,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := NewParser[MockParsable](tt.options)

			if parser == nil {
				t.Error("Expected parser to not be nil")
				return
			}

			// Verify parser type
			if _, ok := parser.(*GenericParser[MockParsable]); !ok {
				t.Error("Expected parser to be of type *GenericParser[MockParsable]")
			}
		})
	}
}

func TestParseString(t *testing.T) {
	tests := []struct {
		name        string
		xmlData     string
		expectError bool
		expectedID  string
		options     *ParserOptions
	}{
		{
			name:        "Valid XML",
			xmlData:     validMockXML,
			expectError: false,
			expectedID:  "test-123",
			options:     nil,
		},
		{
			name:        "Valid XML with namespace",
			xmlData:     validMockXMLWithNamespace,
			expectError: false,
			expectedID:  "test-456",
			options:     nil,
		},
		{
			name:        "Invalid XML (validation failure)",
			xmlData:     invalidMockXML,
			expectError: true,
			expectedID:  "",
			options:     nil,
		},
		{
			name:        "Malformed XML",
			xmlData:     malformedXML,
			expectError: true,
			expectedID:  "",
			options:     nil,
		},
		{
			name:        "Empty XML",
			xmlData:     emptyXML,
			expectError: true,
			expectedID:  "",
			options:     nil,
		},
		{
			name:        "Large XML",
			xmlData:     largeMockXML,
			expectError: false,
			expectedID:  "large-test",
			options:     nil,
		},
		{
			name:        "XML exceeds size limit",
			xmlData:     largeMockXML,
			expectError: true,
			expectedID:  "",
			options: &ParserOptions{
				MaxSize: 100, // Very small limit
			},
		},
		{
			name:        "Deeply nested XML exceeds depth",
			xmlData:     deeplyNestedXML,
			expectError: true,
			expectedID:  "",
			options: &ParserOptions{
				MaxDepth: 10, // Shallow depth limit
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := NewParser[MockParsable](tt.options)
			result, err := parser.ParseString(tt.xmlData)

			if tt.expectError {
				if err == nil {
					t.Error("Expected parsing error but got none")
				}
				if result != nil {
					t.Error("Expected result to be nil when error occurs")
				}
				return
			}

			if err != nil {
				t.Errorf("Unexpected parsing error: %v", err)
				return
			}

			if result == nil {
				t.Error("Expected result to not be nil")
				return
			}

			if result.GetID() != tt.expectedID {
				t.Errorf("Expected ID '%s', got '%s'", tt.expectedID, result.GetID())
			}

			// Verify the mock is valid (should pass validation)
			if err := result.Validate(); err != nil {
				t.Errorf("Parsed result failed validation: %v", err)
			}
		})
	}
}

func TestParseBytes(t *testing.T) {
	xmlData := []byte(validMockXML)
	parser := NewParser[MockParsable](nil)

	result, err := parser.ParseBytes(xmlData)
	if err != nil {
		t.Errorf("Unexpected parsing error: %v", err)
		return
	}

	if result == nil {
		t.Error("Expected result to not be nil")
		return
	}

	if result.GetID() != "test-123" {
		t.Errorf("Expected ID 'test-123', got '%s'", result.GetID())
	}
}

func TestParse(t *testing.T) {
	tests := []struct {
		name        string
		reader      io.Reader
		expectError bool
		expectedID  string
	}{
		{
			name:        "Valid reader",
			reader:      strings.NewReader(validMockXML),
			expectError: false,
			expectedID:  "test-123",
		},
		{
			name:        "Error reader",
			reader:      errorReader{},
			expectError: true,
			expectedID:  "",
		},
		{
			name:        "Empty reader",
			reader:      strings.NewReader(""),
			expectError: true,
			expectedID:  "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := NewParser[MockParsable](nil)
			result, err := parser.Parse(tt.reader)

			if tt.expectError {
				if err == nil {
					t.Error("Expected parsing error but got none")
				}
				if result != nil {
					t.Error("Expected result to be nil when error occurs")
				}
				return
			}

			if err != nil {
				t.Errorf("Unexpected parsing error: %v", err)
				return
			}

			if result == nil {
				t.Error("Expected result to not be nil")
				return
			}

			if result.GetID() != tt.expectedID {
				t.Errorf("Expected ID '%s', got '%s'", tt.expectedID, result.GetID())
			}
		})
	}
}

func TestValidate(t *testing.T) {
	tests := []struct {
		name        string
		reader      io.Reader
		expectError bool
		options     *ParserOptions
	}{
		{
			name:        "Valid XML",
			reader:      strings.NewReader(validMockXML),
			expectError: false,
			options:     nil,
		},
		{
			name:        "Malformed XML",
			reader:      strings.NewReader(malformedXML),
			expectError: true,
			options:     nil,
		},
		{
			name:        "Empty XML",
			reader:      strings.NewReader(""),
			expectError: true,
			options:     nil,
		},
		{
			name:        "Error reader",
			reader:      errorReader{},
			expectError: true,
			options:     nil,
		},
		{
			name:        "Deeply nested XML exceeds depth",
			reader:      strings.NewReader(deeplyNestedXML),
			expectError: true,
			options: &ParserOptions{
				MaxDepth: 10,
			},
		},
		{
			name:        "Valid deeply nested XML within limits",
			reader:      strings.NewReader(deeplyNestedXML),
			expectError: false,
			options: &ParserOptions{
				MaxDepth: 100,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := NewParser[MockParsable](tt.options)
			err := parser.Validate(tt.reader)

			if tt.expectError {
				if err == nil {
					t.Error("Expected validation error but got none")
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected validation error: %v", err)
				}
			}
		})
	}
}

func TestParseWithResult(t *testing.T) {
	tests := []struct {
		name             string
		reader           io.Reader
		expectError      bool
		expectedID       string
		expectMetadata   bool
		expectedVersion  string
		expectedEncoding string
	}{
		{
			name:             "Valid XML with metadata",
			reader:           strings.NewReader(validMockXML),
			expectError:      false,
			expectedID:       "test-123",
			expectMetadata:   true,
			expectedVersion:  "1.0",
			expectedEncoding: "UTF-8",
		},
		{
			name:             "Valid XML with namespace",
			reader:           strings.NewReader(validMockXMLWithNamespace),
			expectError:      false,
			expectedID:       "test-456",
			expectMetadata:   true,
			expectedVersion:  "1.0",
			expectedEncoding: "UTF-8",
		},
		{
			name:           "Error reader",
			reader:         errorReader{},
			expectError:    true,
			expectedID:     "",
			expectMetadata: false,
		},
		{
			name:           "Invalid XML (validation failure)",
			reader:         strings.NewReader(invalidMockXML),
			expectError:    true,
			expectedID:     "",
			expectMetadata: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := NewParser[MockParsable](nil)

			// Cast to GenericParser to access ParseWithResult
			genericParser, ok := parser.(*GenericParser[MockParsable])
			if !ok {
				t.Error("Expected parser to be of type *GenericParser[MockParsable]")
				return
			}

			result, err := genericParser.ParseWithResult(tt.reader)

			if result == nil {
				t.Error("Expected result to not be nil")
				return
			}

			if tt.expectError {
				if err == nil {
					t.Error("Expected parsing error but got none")
				}
				if len(result.Errors) == 0 {
					t.Error("Expected errors in result")
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected parsing error: %v", err)
				}
				if len(result.Errors) > 0 {
					t.Errorf("Unexpected errors in result: %v", result.Errors)
				}
			}

			if tt.expectMetadata {
				if result.Metadata == nil {
					t.Error("Expected metadata to not be nil")
				} else {
					if tt.expectedVersion != "" && result.Metadata.Version != tt.expectedVersion {
						t.Errorf("Expected version '%s', got '%s'", tt.expectedVersion, result.Metadata.Version)
					}
					if tt.expectedEncoding != "" && result.Metadata.Encoding != tt.expectedEncoding {
						t.Errorf("Expected encoding '%s', got '%s'", tt.expectedEncoding, result.Metadata.Encoding)
					}
				}
			}

			if !tt.expectError && result.Data != nil {
				if result.Data.GetID() != tt.expectedID {
					t.Errorf("Expected ID '%s', got '%s'", tt.expectedID, result.Data.GetID())
				}
			}
		})
	}
}

func TestParseWithInvalidMockData(t *testing.T) {
	// Create invalid mock data that will pass XML parsing but fail validation
	invalidMockData := MockParsable{
		ID:    "test-invalid",
		Name:  "Test Name",
		Value: "test-validation-failure", // This will cause validation to fail
		Valid: false,                     // This will cause validation to fail
	}

	// This test ensures that even if XML parsing succeeds, validation errors are caught
	xmlData := `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-invalid">
	<name>Test Name</name>
	<value>test-validation-failure</value>
</MockElement>`

	parser := NewParser[MockParsable](nil)
	result, err := parser.ParseString(xmlData)

	// This should fail during validation, not XML parsing
	if err == nil {
		t.Error("Expected validation error but got none")
	}

	if result != nil {
		t.Error("Expected result to be nil when validation fails")
	}

	// Verify the error message contains validation information
	if !strings.Contains(err.Error(), "validation failed") {
		t.Errorf("Expected validation error message, got: %v", err)
	}

	// Test the mock data validation directly
	validationErr := invalidMockData.Validate()
	if validationErr == nil {
		t.Error("Expected direct validation to fail")
	}
}

func TestConcurrentParsing(t *testing.T) {
	parser := NewParser[MockParsable](nil)
	numGoroutines := 10
	numOperations := 5

	var wg sync.WaitGroup
	errors := make(chan error, numGoroutines*numOperations)
	results := make(chan *MockParsable, numGoroutines*numOperations)

	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func(goroutineID int) {
			defer wg.Done()

			for j := 0; j < numOperations; j++ {
				// Use different XML data for each operation
				xmlData := strings.Replace(validMockXML, "test-123",
					fmt.Sprintf("test-%d-%d", goroutineID, j), 1)

				result, err := parser.ParseString(xmlData)

				if err != nil {
					errors <- err
				} else {
					results <- result
				}
			}
		}(i)
	}

	wg.Wait()
	close(errors)
	close(results)

	// Check for errors
	errorCount := 0
	for err := range errors {
		t.Errorf("Concurrent parsing error: %v", err)
		errorCount++
	}

	// Check results
	resultCount := 0
	for range results {
		resultCount++
	}

	expectedResults := numGoroutines * numOperations
	if resultCount != expectedResults {
		t.Errorf("Expected %d results, got %d (errors: %d)", expectedResults, resultCount, errorCount)
	}
}

func TestParserOptionsEffects(t *testing.T) {
	tests := []struct {
		name        string
		options     *ParserOptions
		xmlData     string
		expectError bool
	}{
		{
			name: "Strict validation enabled",
			options: &ParserOptions{
				StrictValidation: true,
			},
			xmlData:     validMockXML,
			expectError: false,
		},
		{
			name: "Strict validation disabled",
			options: &ParserOptions{
				StrictValidation: false,
			},
			xmlData:     validMockXML,
			expectError: false,
		},
		{
			name: "Very small size limit",
			options: &ParserOptions{
				MaxSize: 10, // Very small
			},
			xmlData:     validMockXML,
			expectError: true,
		},
		{
			name: "Very small depth limit",
			options: &ParserOptions{
				MaxDepth: 1, // Very shallow
			},
			xmlData:     largeMockXML, // Contains nested elements
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := NewParser[MockParsable](tt.options)
			_, err := parser.ParseString(tt.xmlData)

			if tt.expectError {
				if err == nil {
					t.Error("Expected parsing error but got none")
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected parsing error: %v", err)
				}
			}
		})
	}
}

func TestExtractMetadata(t *testing.T) {
	parser := NewParser[MockParsable](nil)
	genericParser, ok := parser.(*GenericParser[MockParsable])
	if !ok {
		t.Error("Expected parser to be of type *GenericParser[MockParsable]")
		return
	}

	tests := []struct {
		name              string
		xmlData           string
		expectedVersion   string
		expectedEncoding  string
		expectedNamespace string
	}{
		{
			name:              "Standard XML with version and encoding",
			xmlData:           validMockXML,
			expectedVersion:   "1.0",
			expectedEncoding:  "UTF-8",
			expectedNamespace: "",
		},
		{
			name:              "XML with namespace",
			xmlData:           validMockXMLWithNamespace,
			expectedVersion:   "1.0",
			expectedEncoding:  "UTF-8",
			expectedNamespace: "http://test.example.com",
		},
		{
			name: "XML with single quotes",
			xmlData: `<?xml version='1.1' encoding='ISO-8859-1'?>
<MockElement id="test">
	<name>Test</name>
	<value>Value</value>
</MockElement>`,
			expectedVersion:   "1.1",
			expectedEncoding:  "ISO-8859-1",
			expectedNamespace: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			metadata := &XMLMetadata{}
			err := genericParser.extractMetadata([]byte(tt.xmlData), metadata)

			if err != nil {
				t.Errorf("Unexpected metadata extraction error: %v", err)
				return
			}

			if metadata.Version != tt.expectedVersion {
				t.Errorf("Expected version '%s', got '%s'", tt.expectedVersion, metadata.Version)
			}

			if metadata.Encoding != tt.expectedEncoding {
				t.Errorf("Expected encoding '%s', got '%s'", tt.expectedEncoding, metadata.Encoding)
			}

			if metadata.Namespace != tt.expectedNamespace {
				t.Errorf("Expected namespace '%s', got '%s'", tt.expectedNamespace, metadata.Namespace)
			}
		})
	}
}

func TestParseEdgeCases(t *testing.T) {
	parser := NewParser[MockParsable](nil)

	tests := []struct {
		name        string
		xmlData     string
		expectError bool
		description string
	}{
		{
			name: "XML with comments",
			xmlData: `<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a comment -->
<MockElement id="test-comments">
	<!-- Another comment -->
	<name>Test with comments</name>
	<value>Comments should be ignored</value>
</MockElement>`,
			expectError: false,
			description: "XML with comments should parse successfully",
		},
		{
			name: "XML with CDATA",
			xmlData: `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-cdata">
	<name>CDATA Test</name>
	<value><![CDATA[This is CDATA content with <special> characters]]></value>
</MockElement>`,
			expectError: false,
			description: "XML with CDATA should parse successfully",
		},
		{
			name: "XML with special characters",
			xmlData: `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-special">
	<name>Special &amp; Characters</name>
	<value>&lt;tag&gt; &quot;quoted&quot; &apos;text&apos;</value>
</MockElement>`,
			expectError: false,
			description: "XML with escaped special characters should parse successfully",
		},
		{
			name: "Minimal valid XML",
			xmlData: `<MockElement id="minimal">
	<name>Minimal</name>
	<value>Test</value>
</MockElement>`,
			expectError: false,
			description: "Minimal XML without declaration should parse successfully",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := parser.ParseString(tt.xmlData)

			if tt.expectError {
				if err == nil {
					t.Errorf("%s: Expected error but got none", tt.description)
				}
			} else {
				if err != nil {
					t.Errorf("%s: Unexpected error: %v", tt.description, err)
					return
				}

				if result == nil {
					t.Errorf("%s: Expected result to not be nil", tt.description)
					return
				}

				// Verify the result is valid
				if err := result.Validate(); err != nil {
					t.Errorf("%s: Result validation failed: %v", tt.description, err)
				}
			}
		})
	}
}

func TestParserMemoryUsage(t *testing.T) {
	// Test parsing multiple documents to ensure no memory leaks
	parser := NewParser[MockParsable](nil)

	for i := 0; i < 100; i++ {
		xmlData := strings.Replace(validMockXML, "test-123", fmt.Sprintf("test-%d", i), 1)
		result, err := parser.ParseString(xmlData)

		if err != nil {
			t.Errorf("Parse error on iteration %d: %v", i, err)
			break
		}

		if result == nil {
			t.Errorf("Nil result on iteration %d", i)
			break
		}

		// Explicitly trigger validation to ensure full processing
		if err := result.Validate(); err != nil {
			t.Errorf("Validation error on iteration %d: %v", i, err)
			break
		}
	}
}

func TestParseFromBuffer(t *testing.T) {
	// Test parsing from a bytes.Buffer
	buffer := bytes.NewBufferString(validMockXML)
	parser := NewParser[MockParsable](nil)

	result, err := parser.Parse(buffer)
	if err != nil {
		t.Errorf("Unexpected error parsing from buffer: %v", err)
		return
	}

	if result == nil {
		t.Error("Expected result to not be nil")
		return
	}

	if result.GetID() != "test-123" {
		t.Errorf("Expected ID 'test-123', got '%s'", result.GetID())
	}
}
