package proxy

import (
	"log/slog"
	"math/rand"
	"sort"
	"strings"

	"yobble-gateway-go/internal/config"
	"yobble-gateway-go/internal/logger"
)

// findBestMatch ищет самый длинный совпадающий префикс в заданных маршрутах.
// Он возвращает найденный префикс и остаток пути (хвост).
func findBestMatch(versionRoutes map[string][]config.Backend, path string) (string, string) {
	op := "proxy.findBestMatch"
	log := logger.NewLoggerWithOp(op)

	var prefixes []string
	for prefix := range versionRoutes {
		prefixes = append(prefixes, prefix)
	}

	// Сортируем префиксы по длине в порядке убывания, чтобы сначала найти самый длинный
	sort.Slice(prefixes, func(i, j int) bool {
		return len(prefixes[i]) > len(prefixes[j])
	})

	for _, prefix := range prefixes {
		if strings.HasPrefix(path, prefix) {
			log.Debug("found best match", slog.String("path", path), slog.String("prefix", prefix))
			return prefix, path[len(prefix):]
		}
	}

	log.Debug("no best match found", slog.String("path", path))
	return "", ""
}

// SelectBackend выбирает бэкенд из списка доступных серверов.
// В настоящее время он выбирает случайный, что соответствует реализации на Python.
func selectBackend(backends []config.Backend) *config.Backend {
	op := "proxy.selectBackend"
	log := logger.NewLoggerWithOp(op)

	if len(backends) == 0 {
		log.Warn("no backends available")
		return nil
	}
	selected := &backends[rand.Intn(len(backends))]
	log.Debug("selected backend", slog.String("url", selected.URL))
	return selected
}

// ResolveBackend находит подходящий сервис бэкенда для заданного пути запроса.
// Он анализирует версию, находит наиболее подходящий маршрут и выбирает сервер бэкенда.
func ResolveBackend(path string, routes config.Routes) (*config.Backend, string) {
	op := "proxy.ResolveBackend"
	log := logger.NewLoggerWithOp(op)

	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) == 0 {
		log.Debug("empty path after trimming", slog.String("path", path))
		return nil, ""
	}

	var versionRoutes map[string][]config.Backend
	var pathToMatch string

	// Проверяем, является ли первая часть пути строкой версии
	if routes[parts[0]] != nil {
		versionRoutes = routes[parts[0]]
		if len(parts) > 1 {
			pathToMatch = "/" + strings.Join(parts[1:], "/")
		} else {
			pathToMatch = "/"
		}
		log.Debug("versioned route detected", slog.String("version", parts[0]), slog.String("pathToMatch", pathToMatch))
	} else {
		// Возвращаемся к версии по умолчанию
		versionRoutes = routes["default"]
		pathToMatch = path
		log.Debug("default route used", slog.String("pathToMatch", pathToMatch))
	}

	if versionRoutes == nil {
		log.Warn("no version routes found", slog.String("path", path))
		return nil, ""
	}

	routePrefix, tailPath := findBestMatch(versionRoutes, pathToMatch)
	if routePrefix == "" {
		log.Warn("no route prefix found", slog.String("path", pathToMatch))
		return nil, ""
	}

	backend := selectBackend(versionRoutes[routePrefix])
	if backend == nil {
		log.Warn("no backend selected for route prefix", slog.String("route_prefix", routePrefix))
		return nil, ""
	}

	log.Debug("resolved backend", slog.String("backend_url", backend.URL), slog.String("tail_path", tailPath))
	return backend, tailPath
}
