// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"log/slog"
	"net"
	"os"
	"strings"
	"time"

	"github.com/xmppo/go-xmpp"        // BSD-3-Clause
	"golang.org/x/net/idna"           // BSD-3-Clause
	"salsa.debian.org/mdosch/xmppsrv" // BSD-2-Clause
)

func connect(options xmpp.Options, directTLS bool, anon bool, retryWait int, retryMax int) (client *xmpp.Client, err error) {
	var server string
	proxy := os.Getenv("HTTP_PROXY")
	slog.Info("getting environment variable:", "HTTP_PROXY", proxy)
	if !anon {
		server = options.User[strings.Index(options.User, "@")+1:]
	} else {
		server = options.User
	}
	retry := true
	for i := 0; retry; i++ {
		// Look up SRV records or hostmeta2 if server is not specified manually
		// or if anon authentication is used.
		if options.Host == "" || anon {
			// Don't do SRV or hostmeta2 look ups if proxy is set.
			// TODO: 2024-10-30: Support hostmeta2 look ups using proxy.
			if proxy == "" {
				// Look up xmpp-client SRV records.
				slog.Info("looking up client SRV records for", "server", server)
				serverASCII, err := idna.ToASCII(server)
				if err != nil {
					return client, err
				}
				srvMixed, err := xmppsrv.LookupClient(serverASCII)
				if len(srvMixed) > 0 && err == nil {
					for _, adr := range srvMixed {
						switch {
						case directTLS && adr.Type == "xmpp-client":
							slog.Info("skipping as direct TLS is requested:", "type", adr.Type,
								"target", adr.Target, "port", adr.Port)
							continue
						case adr.Type == "xmpp-client":
							// Use StartTLS
							options.NoTLS = true
							slog.Info("received SRV record:", "type", adr.Type, "target", adr.Target, "port", adr.Port)
							slog.Info("setting xmpp connection option:", "NoTLS", options.NoTLS)
							slog.Info("setting xmpp connection option:", "StartTLS", options.StartTLS)
							options.StartTLS = true
						case adr.Type == "xmpps-client":
							// Use direct TLS
							options.NoTLS = false
							options.StartTLS = false
							slog.Info("received SRV record::", "type", adr.Type, "target", adr.Target, "port", adr.Port)
							slog.Info("setting xmpp connection option:", "NoTLS", options.NoTLS)
							slog.Info("setting xmpp connection option:", "StartTLS", options.StartTLS)
						default:
							continue
						}
						options.Host = net.JoinHostPort(adr.Target, fmt.Sprint(adr.Port))
						// Connect to server
						slog.Info("connecting to", "host", options.Host)
						client, err = options.NewClient()
						if err == nil || strings.Contains(err.Error(), "not-authorized") {
							// Don't try to connect on other port if error contains
							// "not-authorized".
							return client, err
						}
						slog.Info("connecting failed")
					}
				}
				// Look up hostmeta2 file.
				slog.Info("looking up host meta 2 for", "server", server)
				hm2, httpStatus, err := xmppsrv.LookupHostmeta2(serverASCII)
				if httpStatus != 404 && err == nil {
					for _, link := range hm2.Links {
						if link.Rel == nsC2SdTLS {
							options.NoTLS = false
							options.StartTLS = false
							slog.Info("setting xmpp connection option:", "NoTLS", options.NoTLS)
							slog.Info("setting xmpp connection option:", "StartTLS", options.StartTLS)
							options.Host = net.JoinHostPort(link.Sni, fmt.Sprint(link.Port))
							// Connect to server
							slog.Info("connecting to", "host", options.Host)
							client, err = options.NewClient()
							if err == nil || strings.Contains(err.Error(), "not-authorized") {
								// Don't try to connect on other port if error contains
								// "not-authorized".
								return client, err
							}
						}
					}
				}
			}
		}
		_, port, _ := net.SplitHostPort(options.Host)
		if port == "" {
			if options.Host == "" {
				options.Host = server
			}
			// Try port 5223 if directTLS is set and no port is provided.
			if directTLS {
				options.NoTLS = false
				options.StartTLS = false
				options.Host = net.JoinHostPort(options.Host, "5223")
				slog.Info("trying direct TLS fallback on port 5223")
			} else {
				// Try port 5222 if no port is provided and directTLS is not set.
				options.NoTLS = true
				options.StartTLS = true
				options.Host = net.JoinHostPort(options.Host, "5222")
				slog.Info("trying StartTLS fallback on port 5222")
			}
		}
		// Connect to server
		slog.Info("connecting to", "host", options.Host)
		client, err = options.NewClient()
		if err == nil {
			return client, err
		}
		if retryWait == 0 {
			slog.Info("giving up trying to connect…")
			break
		}
		if retryMax != 0 && retryMax == i {
			retry = false
		}
		// Reset options.Host as otherwise DNS look up is skipped.
		options.Host = ""
		sleepDuration := time.Duration(retryWait) * time.Second
		slog.Info("connecting failed, retry after", "sleep", sleepDuration)
		time.Sleep(sleepDuration)
		slog.Info("connecting to server:", "retry", i)
	}
	return client, fmt.Errorf("connect: failed to connect to server %s: %w", options.Host, err)
}
