package xmlparser

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"reflect"
	"strings"
)

// GenericParser implements the Parser interface using Go generics
type GenericParser[T Parsable] struct {
	options *ParserOptions
}

// NewParser creates a new generic XML parser
func NewParser[T Parsable](options *ParserOptions) Parser[T] {
	if options == nil {
		options = DefaultParserOptions()
	}
	return &GenericParser[T]{
		options: options,
	}
}

// Parse parses XML from reader into the target type
func (p *GenericParser[T]) Parse(reader io.Reader) (*T, error) {
	// Read all data from reader
	data, err := io.ReadAll(reader)
	if err != nil {
		return nil, fmt.Errorf("failed to read XML data: %w", err)
	}

	return p.ParseBytes(data)
}

// ParseString parses XML from string into the target type
func (p *GenericParser[T]) ParseString(xmlData string) (*T, error) {
	return p.ParseBytes([]byte(xmlData))
}

// ParseBytes parses XML from byte slice into the target type
func (p *GenericParser[T]) ParseBytes(xmlData []byte) (*T, error) {
	// Check size limit
	if p.options.MaxSize > 0 && int64(len(xmlData)) > p.options.MaxSize {
		return nil, fmt.Errorf("XML size %d exceeds maximum allowed size %d", len(xmlData), p.options.MaxSize)
	}

	// Create a new instance of T
	var result T
	resultType := reflect.TypeOf(result)

	// Create a pointer to the struct if it's not already a pointer
	var targetPtr interface{}
	if resultType.Kind() == reflect.Ptr {
		// T is already a pointer type
		targetPtr = reflect.New(resultType.Elem()).Interface()
	} else {
		// T is a value type, create a pointer to it
		targetPtr = reflect.New(resultType).Interface()
	}

	// Check depth limit before parsing if specified
	if p.options.MaxDepth > 0 {
		if err := p.checkDepthLimit(xmlData); err != nil {
			return nil, err
		}
	}

	// Create XML decoder
	decoder := xml.NewDecoder(bytes.NewReader(xmlData))

	// Configure decoder options
	if !p.options.PreserveWhitespace {
		decoder.CharsetReader = nil
	}

	if p.options.StrictValidation {
		decoder.Strict = true
	}

	// Parse XML into the pointer
	err := decoder.Decode(targetPtr)
	if err != nil {
		return nil, fmt.Errorf("failed to decode XML: %w", err)
	}

	// Convert back to the target type
	var parsedResult T
	if resultType.Kind() == reflect.Ptr {
		// T is a pointer type, so targetPtr is already T
		parsedResult = targetPtr.(T)
	} else {
		// T is a value type, so dereference the pointer
		parsedResult = reflect.ValueOf(targetPtr).Elem().Interface().(T)
	}

	// Validate the parsed result
	if err := parsedResult.Validate(); err != nil {
		return nil, fmt.Errorf("validation failed: %w", err)
	}

	return &parsedResult, nil
}

// Validate validates XML against schema (basic validation for now)
func (p *GenericParser[T]) Validate(reader io.Reader) error {
	data, err := io.ReadAll(reader)
	if err != nil {
		return fmt.Errorf("failed to read XML data: %w", err)
	}

	// Check for empty data
	if len(data) == 0 {
		return fmt.Errorf("XML data is empty")
	}

	// Basic XML well-formedness validation
	decoder := xml.NewDecoder(bytes.NewReader(data))

	// Count depth for max depth validation
	depth := 0
	maxDepthReached := 0

	for {
		token, err := decoder.Token()
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("XML validation failed: %w", err)
		}

		switch token.(type) {
		case xml.StartElement:
			depth++
			if depth > maxDepthReached {
				maxDepthReached = depth
			}
			if p.options.MaxDepth > 0 && depth > p.options.MaxDepth {
				return fmt.Errorf("XML depth %d exceeds maximum allowed depth %d", depth, p.options.MaxDepth)
			}
		case xml.EndElement:
			depth--
		}
	}

	return nil
}

// checkDepthLimit validates that XML doesn't exceed the maximum depth limit
func (p *GenericParser[T]) checkDepthLimit(xmlData []byte) error {
	decoder := xml.NewDecoder(bytes.NewReader(xmlData))
	depth := 0
	maxDepthReached := 0

	for {
		token, err := decoder.Token()
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("XML depth validation failed: %w", err)
		}

		switch token.(type) {
		case xml.StartElement:
			depth++
			if depth > maxDepthReached {
				maxDepthReached = depth
			}
			if depth > p.options.MaxDepth {
				return fmt.Errorf("XML depth %d exceeds maximum allowed depth %d", depth, p.options.MaxDepth)
			}
		case xml.EndElement:
			depth--
		}
	}

	return nil
}

// ParseWithResult parses XML and returns detailed result with metadata
func (p *GenericParser[T]) ParseWithResult(reader io.Reader) (*ParseResult[T], error) {
	result := &ParseResult[T]{
		Metadata: &XMLMetadata{},
		Errors:   []string{},
		Warnings: []string{},
	}

	// Read all data
	data, err := io.ReadAll(reader)
	if err != nil {
		result.Errors = append(result.Errors, fmt.Sprintf("Failed to read XML data: %v", err))
		return result, err
	}

	// Extract XML metadata
	if err := p.extractMetadata(data, result.Metadata); err != nil {
		result.Warnings = append(result.Warnings, fmt.Sprintf("Failed to extract metadata: %v", err))
	}

	// Parse the XML
	parsed, err := p.ParseBytes(data)
	if err != nil {
		result.Errors = append(result.Errors, err.Error())
		return result, err
	}

	result.Data = parsed
	return result, nil
}

// extractMetadata extracts basic metadata from XML
func (p *GenericParser[T]) extractMetadata(xmlData []byte, metadata *XMLMetadata) error {
	// Basic extraction of XML declaration and root element
	xmlStr := string(xmlData)

	// Extract encoding from XML declaration
	if strings.HasPrefix(xmlStr, "<?xml") {
		endIdx := strings.Index(xmlStr, "?>")
		if endIdx > 0 {
			declaration := xmlStr[:endIdx+2]

			// Extract encoding
			if encodingIdx := strings.Index(declaration, "encoding="); encodingIdx > 0 {
				start := encodingIdx + 9
				if start < len(declaration) {
					quote := declaration[start]
					if quote == '"' || quote == '\'' {
						end := strings.IndexByte(declaration[start+1:], byte(quote))
						if end > 0 {
							metadata.Encoding = declaration[start+1 : start+1+end]
						}
					}
				}
			}

			// Extract version
			if versionIdx := strings.Index(declaration, "version="); versionIdx > 0 {
				start := versionIdx + 8
				if start < len(declaration) {
					quote := declaration[start]
					if quote == '"' || quote == '\'' {
						end := strings.IndexByte(declaration[start+1:], byte(quote))
						if end > 0 {
							metadata.Version = declaration[start+1 : start+1+end]
						}
					}
				}
			}
		}
	}

	// Extract root element namespace
	decoder := xml.NewDecoder(bytes.NewReader(xmlData))
	for {
		token, err := decoder.Token()
		if err != nil {
			break
		}

		if startElement, ok := token.(xml.StartElement); ok {
			metadata.XMLName = startElement.Name
			metadata.Namespace = startElement.Name.Space
			break
		}
	}

	return nil
}
