package config

import (
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/service-proxy/pkg/audit"
)

// Validate validates the configuration.
func Validate(cfg *Config) error {
	if cfg == nil {
		return errors.New("config is nil")
	}

	if err := validateServer(&cfg.Server); err != nil {
		return fmt.Errorf("server config: %w", err)
	}

	if cfg.Metrics.Enabled {
		if err := validateMetrics(&cfg.Metrics); err != nil {
			return fmt.Errorf("metrics config: %w", err)
		}
	}

	for i, route := range cfg.ClusterRoutes {
		if err := validateRoute(&route); err != nil {
			return fmt.Errorf("clusterRoutes[%d]: %w", i, err)
		}
	}

	for i, route := range cfg.ServiceRoutes {
		if err := validateRoute(&route); err != nil {
			return fmt.Errorf("serviceRoutes[%d]: %w", i, err)
		}
	}

	// Validate route name uniqueness across all routes
	if err := validateRouteNameUniqueness(cfg.ClusterRoutes, cfg.ServiceRoutes); err != nil {
		return err
	}

	// Validate route prefix conflicts
	if err := validateRoutePrefixConflicts(cfg.ClusterRoutes, cfg.ServiceRoutes); err != nil {
		return err
	}

	// Validate audit config if enabled
	if err := audit.ValidateConfig(&cfg.Audit); err != nil {
		return fmt.Errorf("audit config: %w", err)
	}

	return nil
}

func validateServer(cfg *ServerConfig) error {
	if cfg.Addr == "" {
		return errors.New("addr is required")
	}
	if cfg.Port <= 0 || cfg.Port > 65535 {
		return fmt.Errorf("port must be between 1 and 65535, got %d", cfg.Port)
	}
	return nil
}

func validateMetrics(cfg *MetricsConfig) error {
	if cfg.Port <= 0 || cfg.Port > 65535 {
		return fmt.Errorf("port must be between 1 and 65535, got %d", cfg.Port)
	}
	if cfg.Path == "" {
		return errors.New("path is required when metrics enabled")
	}
	return nil
}

func validateRoute(cfg *RouteConfig) error {
	if cfg.Name == "" {
		return errors.New("name is required")
	}
	if cfg.Match.Prefix == "" {
		return errors.New("match.prefix is required")
	}
	if cfg.Backend.ServiceNamePattern != "" && cfg.Backend.ServiceNameTemplate != "" {
		return errors.New("backend.serviceNamePattern and backend.serviceNameTemplate are mutually exclusive")
	}
	params, err := ExtractPathParams(cfg.Match.Prefix)
	if err != nil {
		return fmt.Errorf("match.prefix: %w", err)
	}
	if len(params) > 0 {
		if cfg.Backend.ServiceNameTemplate == "" {
			return errors.New("backend.serviceNameTemplate is required when match.prefix contains path parameters")
		}
		if err := validateTemplateContainsParams(cfg.Backend.ServiceNameTemplate, params); err != nil {
			return err
		}
	}
	if cfg.TLS.Enabled && !cfg.TLS.InsecureSkipVerify && cfg.TLS.SecretRef.Name == "" {
		return errors.New("tls.secretRef.name is required when tls enabled")
	}
	return nil
}

// validateRouteNameUniqueness checks that all route names are unique across
// both clusterRoutes and serviceRoutes.
func validateRouteNameUniqueness(clusterRoutes, serviceRoutes []RouteConfig) error {
	names := make(map[string]bool)

	for _, route := range clusterRoutes {
		if names[route.Name] {
			return fmt.Errorf("duplicate route name: %s", route.Name)
		}
		names[route.Name] = true
	}

	for _, route := range serviceRoutes {
		if names[route.Name] {
			return fmt.Errorf("duplicate route name: %s", route.Name)
		}
		names[route.Name] = true
	}

	return nil
}

// validateRoutePrefixConflicts checks for duplicate prefixes and overlapping
// prefix patterns (e.g., /pd and /pd/api would conflict).
func validateRoutePrefixConflicts(clusterRoutes, serviceRoutes []RouteConfig) error {
	// prefix -> route name mapping
	prefixes := make(map[string]string)

	allRoutes := make([]RouteConfig, 0, len(clusterRoutes)+len(serviceRoutes))
	allRoutes = append(allRoutes, clusterRoutes...)
	allRoutes = append(allRoutes, serviceRoutes...)

	for _, route := range allRoutes {
		prefix := route.Match.Prefix

		// Check for exact duplicate prefix
		if existingName, exists := prefixes[prefix]; exists {
			return fmt.Errorf("duplicate prefix %q: routes %q and %q", prefix, existingName, route.Name)
		}

		// Check for prefix overlap conflicts
		// e.g., /pd and /pd/api would conflict because one is a prefix of the other
		for existingPrefix, existingName := range prefixes {
			if hasPrefixConflict(prefix, existingPrefix) {
				return fmt.Errorf("prefix conflict: %q (%s) overlaps with %q (%s)",
					prefix, route.Name, existingPrefix, existingName)
			}
		}

		prefixes[prefix] = route.Name
	}

	return nil
}

// hasPrefixConflict checks if two prefixes conflict with each other.
// Two prefixes conflict if one is a prefix of the other followed by a path separator.
func hasPrefixConflict(prefix1, prefix2 string) bool {
	// Normalize prefixes to ensure they start with /
	p1 := normalizePrefix(prefix1)
	p2 := normalizePrefix(prefix2)

	// Check if one is a prefix of the other
	if strings.HasPrefix(p1, p2+"/") || strings.HasPrefix(p2, p1+"/") {
		return true
	}

	return false
}

// normalizePrefix ensures the prefix starts with /
func normalizePrefix(prefix string) string {
	if !strings.HasPrefix(prefix, "/") {
		return "/" + prefix
	}
	return prefix
}

// ExtractPathParams extracts path parameter names from a prefix pattern.
// Exported for use by route package to avoid code duplication.
func ExtractPathParams(prefix string) ([]string, error) {
	var params []string
	seen := make(map[string]bool)
	for idx := 0; idx < len(prefix); {
		start := strings.Index(prefix[idx:], "{")
		if start == -1 {
			break
		}
		start += idx
		end := strings.Index(prefix[start:], "}")
		if end == -1 {
			return nil, fmt.Errorf("unmatched '{' in prefix %q", prefix)
		}
		end += start
		name := prefix[start+1 : end]
		if name == "" {
			return nil, fmt.Errorf("empty path parameter in prefix %q", prefix)
		}
		if strings.ContainsRune(name, '/') {
			return nil, fmt.Errorf("invalid path parameter %q in prefix %q", name, prefix)
		}
		if seen[name] {
			return nil, fmt.Errorf("duplicate path parameter %q in prefix %q", name, prefix)
		}
		seen[name] = true
		params = append(params, name)
		idx = end + 1
	}
	// Check for unmatched braces using count comparison
	openCount := strings.Count(prefix, "{")
	closeCount := strings.Count(prefix, "}")
	if openCount != closeCount {
		return nil, fmt.Errorf("unmatched braces in prefix %q", prefix)
	}
	return params, nil
}

func validateTemplateContainsParams(template string, params []string) error {
	for _, p := range params {
		placeholder := fmt.Sprintf("{%s}", p)
		if !strings.Contains(template, placeholder) {
			return fmt.Errorf("backend.serviceNameTemplate must contain placeholder %s", placeholder)
		}
	}
	return nil
}
