package auth

import (
	"sort"
	"strconv"
	"strings"
)

// acceptValue represents a parsed accept header value
type acceptValue struct {
	value       string  // the media type or value
	quality     float64 // quality factor (0.0 to 1.0)
	specificity int     // specificity level for sorting
	order       int     // original order in the accept header
}

// NegotiateContent returns the best content to offer from a set of possible
// values, based on the preferences represented by the accept values.
func NegotiateContent(accepts []string, offers []string) string {
	if len(offers) == 0 {
		return ""
	}

	// If no accept values, return first offer
	if len(accepts) == 0 {
		return offers[0]
	}

	// Parse accept values (limit to first 32 to avoid DOS)
	acceptValues := parseAcceptValues(accepts)
	if len(acceptValues) > 32 {
		acceptValues = acceptValues[:32]
	}

	// Find best match
	bestMatch := ""
	bestScore := -1.0
	bestSpecificity := -1
	bestOrder := len(offers) // use offer order as tiebreaker

	for offerIdx, offer := range offers {
		score, specificity := calculateScore(offer, acceptValues)

		// Check if this is a better match
		if score > bestScore ||
			(score == bestScore && specificity > bestSpecificity) ||
			(score == bestScore && specificity == bestSpecificity && offerIdx < bestOrder) {
			bestMatch = offer
			bestScore = score
			bestSpecificity = specificity
			bestOrder = offerIdx
		}
	}

	if bestScore <= 0 {
		return ""
	}

	return bestMatch
}

// parseAcceptValues parses accept header values into structured format
func parseAcceptValues(accepts []string) []acceptValue {
	var values []acceptValue
	order := 0

	for _, accept := range accepts {
		// Split by comma to handle multiple values in one string
		parts := strings.Split(accept, ",")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if part == "" {
				continue
			}

			value := acceptValue{
				quality: 1.0, // default quality
				order:   order,
			}

			// Split by semicolon to separate value from parameters
			sections := strings.Split(part, ";")
			value.value = strings.TrimSpace(sections[0])

			// Parse quality parameter
			for i := 1; i < len(sections); i++ {
				param := strings.TrimSpace(sections[i])
				if strings.HasPrefix(param, "q=") {
					if q, err := strconv.ParseFloat(param[2:], 64); err == nil {
						if q >= 0.0 && q <= 1.0 {
							value.quality = q
						}
					}
					break
				}
			}

			// Calculate specificity for media types
			value.specificity = calculateSpecificity(value.value)

			values = append(values, value)
			order++
		}
	}

	// Sort by quality (desc), then specificity (desc), then order (asc)
	sort.Slice(values, func(i, j int) bool {
		if values[i].quality != values[j].quality {
			return values[i].quality > values[j].quality
		}
		if values[i].specificity != values[j].specificity {
			return values[i].specificity > values[j].specificity
		}
		return values[i].order < values[j].order
	})

	return values
}

// calculateSpecificity returns specificity level for media type matching
func calculateSpecificity(mediaType string) int {
	if mediaType == "*/*" {
		return 0 // least specific
	}
	if strings.HasSuffix(mediaType, "/*") {
		return 1 // type wildcard
	}
	return 2 // exact match (most specific)
}

// calculateScore returns the quality score and specificity for an offer against accept values
func calculateScore(offer string, acceptValues []acceptValue) (float64, int) {
	for _, accept := range acceptValues {
		if matches(offer, accept.value) {
			return accept.quality, accept.specificity
		}
	}
	return 0.0, 0
}

// matches checks if an offer matches an accept value (including wildcards)
func matches(offer, accept string) bool {
	if accept == "*/*" {
		return true
	}

	if strings.HasSuffix(accept, "/*") {
		// Type wildcard (e.g., "text/*")
		offerType := strings.Split(offer, "/")[0]
		acceptType := strings.Split(accept, "/")[0]
		return offerType == acceptType
	}

	// Exact match
	return offer == accept
}
