package updater

import (
	"context"
	"fmt"
	"sort"
	"strings"

	"github.com/qdm12/gluetun/internal/constants/vpn"
	"github.com/qdm12/gluetun/internal/models"
	"github.com/qdm12/gluetun/internal/provider/common"
)

func (u *Updater) FetchServers(ctx context.Context, minServers int) (
	servers []models.Server, err error,
) {
	data, err := fetchAPI(ctx, u.client)
	if err != nil {
		return nil, fmt.Errorf("fetching API: %w", err)
	}

	hosts := make(map[string]struct{}, len(data.Servers))

	for _, serverData := range data.Servers {
		openVPNHost := serverData.Hostnames.OpenVPN
		if openVPNHost != "" {
			hosts[openVPNHost] = struct{}{}
		}

		wireguardHost := serverData.Hostnames.Wireguard
		if wireguardHost != "" {
			hosts[wireguardHost] = struct{}{}
		}
	}

	if len(hosts) < minServers {
		return nil, fmt.Errorf("%w: %d and expected at least %d",
			common.ErrNotEnoughServers, len(hosts), minServers)
	}

	hostsSlice := make(sort.StringSlice, 0, len(hosts))
	for host := range hosts {
		hostsSlice = append(hostsSlice, host)
	}
	hostsSlice.Sort() // for predictable unit tests

	resolveSettings := parallelResolverSettings(hostsSlice)
	hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
	for _, warning := range warnings {
		u.warner.Warn(warning)
	}
	if err != nil {
		return nil, err
	}

	if len(hostToIPs) < minServers {
		return nil, fmt.Errorf("%w: %d and expected at least %d",
			common.ErrNotEnoughServers, len(servers), minServers)
	}

	servers = make([]models.Server, 0, len(hostToIPs))
	for _, serverData := range data.Servers {
		city, region := parseCity(serverData.City)
		server := models.Server{
			Country: serverData.Country,
			City:    city,
			Region:  region,
			ISP:     serverData.ISP,
		}

		openVPNHostname := serverData.Hostnames.OpenVPN
		wireguardHostname := serverData.Hostnames.Wireguard
		if openVPNHostname == "" && wireguardHostname == "" {
			warning := fmt.Sprintf("server data %v has no OpenVPN nor Wireguard hostname", serverData)
			warnings = append(warnings, warning)
			continue
		}

		if openVPNHostname != "" {
			openVPNServer := server
			openVPNServer.Hostname = openVPNHostname
			openVPNServer.VPN = vpn.OpenVPN
			openVPNServer.UDP = true
			openVPNServer.TCP = true
			openVPNServer.IPs = hostToIPs[openVPNHostname]
			servers = append(servers, openVPNServer)
		}

		if wireguardHostname != "" {
			wireguardServer := server
			wireguardServer.Hostname = wireguardHostname
			wireguardServer.VPN = vpn.Wireguard
			wireguardServer.IPs = hostToIPs[wireguardHostname]
			wireguardServer.WgPubKey = serverData.WgPubKey
			servers = append(servers, wireguardServer)
		}
	}

	sort.Sort(models.SortableServers(servers))

	return servers, nil
}

func parseCity(city string) (parsedCity, region string) {
	commaIndex := strings.Index(city, ", ")
	if commaIndex == -1 {
		return city, ""
	}
	return city[:commaIndex], city[commaIndex+2:]
}
