package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"strings"
	"time"
)

var (
	ldapDNSServers    []string
	ldapStaticHosts   map[string]string
	ldapNetworkingSet bool
)

func initLDAPNetworking() {
	if ldapNetworkingSet {
		return
	}
	ldapDNSServers = parseCSV(os.Getenv("LDAP_DNS_SERVERS"))
	ldapStaticHosts = parseStaticHostMap(os.Getenv("LDAP_EXTRA_HOSTS"))
	ldapNetworkingSet = true
}

func parseCSV(raw string) []string {
	parts := strings.Split(raw, ",")
	result := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part != "" {
			result = append(result, part)
		}
	}
	return result
}

func parseStaticHostMap(raw string) map[string]string {
	result := make(map[string]string)
	for _, part := range parseCSV(raw) {
		host, ip, ok := strings.Cut(part, ":")
		if !ok {
			continue
		}
		host = strings.TrimSpace(host)
		ip = strings.TrimSpace(ip)
		if host == "" || ip == "" {
			continue
		}
		result[strings.ToLower(host)] = ip
	}
	return result
}

func resolveHostAddress(host string) (string, error) {
	initLDAPNetworking()

	host = strings.TrimSpace(host)
	if host == "" {
		return "", fmt.Errorf("пустой LDAP host")
	}
	if net.ParseIP(host) != nil {
		return host, nil
	}

	lowerHost := strings.ToLower(host)
	if ip, ok := ldapStaticHosts[lowerHost]; ok {
		return ip, nil
	}

	if len(ldapDNSServers) > 0 {
		if ip, err := lookupViaCustomDNS(host, ldapDNSServers); err == nil {
			return ip, nil
		} else if len(ldapStaticHosts) == 0 {
			return "", err
		}
	}

	addrs, err := net.LookupHost(host)
	if err != nil {
		return "", err
	}
	if ip := firstIPv4(addrs); ip != "" {
		return ip, nil
	}
	if len(addrs) > 0 {
		return addrs[0], nil
	}
	return "", fmt.Errorf("не найден IP-адрес для %s", host)
}

func lookupViaCustomDNS(host string, servers []string) (string, error) {
	var lastErr error
	for _, server := range servers {
		resolver := &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				dialer := net.Dialer{Timeout: 4 * time.Second}
				return dialer.DialContext(ctx, "udp", net.JoinHostPort(server, "53"))
			},
		}

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		addrs, err := resolver.LookupHost(ctx, host)
		cancel()
		if err != nil {
			lastErr = err
			continue
		}
		if ip := firstIPv4(addrs); ip != "" {
			return ip, nil
		}
		if len(addrs) > 0 {
			return addrs[0], nil
		}
	}
	if lastErr != nil {
		return "", lastErr
	}
	return "", fmt.Errorf("DNS-серверы не вернули адрес для %s", host)
}

func firstIPv4(addrs []string) string {
	for _, addr := range addrs {
		if strings.Contains(addr, ".") {
			return addr
		}
	}
	return ""
}
