// Package autoconfig provides a somewhat smart way of automatically
// determining the mailservers for a given address.
//
// It works by first checking for a Thunderbird autoconfiguration, and if not
// found checking common names (imap|smtp|mail).example.com and performing a TCP
// Ping on their ports.
//
// The first part is described here: https://wiki.mozilla.org/Thunderbird:Autoconfiguration
package autoconfig

import (
	"context"
	"encoding/xml"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"git.sr.ht/~rjarry/aerc/lib/log"
)

// getFromAutoconfig retrieves the config from the provider using a Mozilla
// Thunderbird autoconfig service
func getFromAutoconfig(ctx context.Context, localpart, domain string, result chan<- *Config) {
	defer log.PanicHandler()
	defer close(result)

	res := make(chan *Config, 1)
	go func(res chan *Config) {
		defer log.PanicHandler()
		defer close(res)

		var addresses []*url.URL
		u, err := url.Parse(fmt.Sprintf(
			"https://autoconfig.%s/mail/config-v1.1.xml?emailaddress=%s",
			domain, url.QueryEscape(localpart+"@"+domain)))
		if err == nil {
			addresses = append(addresses, u)
		}
		u, err = url.Parse(fmt.Sprintf(
			"https://%s/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=%s",
			domain, url.QueryEscape(localpart+"@"+domain)))
		if err == nil {
			addresses = append(addresses, u)
		}

		log.Debugf("checking for autoconfig files at %v", addresses)
		cc := new(ClientConfig)
		for _, u := range addresses {
			response, err := httpGet(u.String())
			if err != nil {
				continue
			}
			err = xml.NewDecoder(response.Body).Decode(cc)
			if err != nil {
				continue
			}
			// IMAP sanity check
			var incoming *IncomingServer
			for i := range cc.EmailProvider.IncomingServer {
				if strings.ToLower(cc.EmailProvider.IncomingServer[i].Type) != "imap" {
					continue
				}
				incoming = &cc.EmailProvider.IncomingServer[i]
				break
			}
			if incoming == nil {
				// no imap server found
				continue
			}
			var incomingPort int
			if incomingPort, err = strconv.Atoi(incoming.Port); err != nil {
				continue
			}
			inenc := EncryptionSTARTTLS
			switch strings.ToLower(incoming.SocketType) {
			case "plain":
				inenc = EncryptionInsecure
			case "ssl":
				inenc = EncryptionTLS
			}
			if strings.ToLower(incoming.Username) == "%emailaddress%" {
				incoming.Username = localpart + "@" + domain
			}

			var outport int
			if outport, err = strconv.Atoi(cc.EmailProvider.OutgoingServer.Port); err != nil {
				continue
			}
			outenc := EncryptionSTARTTLS
			switch strings.ToLower(cc.EmailProvider.OutgoingServer.SocketType) {
			case "plain":
				outenc = EncryptionInsecure
			case "ssl":
				outenc = EncryptionTLS
			}
			if strings.ToLower(cc.EmailProvider.OutgoingServer.Username) == "%emailaddress%" {
				cc.EmailProvider.OutgoingServer.Username = localpart + "@" + domain
			}

			log.Debugf("found HTTP-based autoconfig at %s", u)

			res <- &Config{
				Found: ProtocolIMAP,
				IMAP: Credentials{
					Encryption: inenc,
					Address:    incoming.Hostname,
					Port:       incomingPort,
					Username:   incoming.Username,
				},
				SMTP: Credentials{
					Encryption: outenc,
					Address:    cc.EmailProvider.OutgoingServer.Hostname,
					Port:       outport,
					Username:   cc.EmailProvider.OutgoingServer.Username,
				},
			}
			return
		}
	}(res)

	select {
	case r, isValue := <-res:
		if isValue {
			result <- r
		}
	case <-ctx.Done():
	}
}

var httpGet = http.Get

///////////////////////////////////////////////////////////////////////////////
//        Autogenerated struct… you probably don't want to touch this.       //
///////////////////////////////////////////////////////////////////////////////

type ClientConfig struct {
	XMLName       xml.Name      `xml:"clientConfig"`
	Text          string        `xml:",chardata"`
	Version       string        `xml:"version,attr"`
	EmailProvider EmailProvider `xml:"emailProvider"`
}

type EmailProvider struct {
	Text           string           `xml:",chardata"`
	ID             string           `xml:"id,attr"`
	Domain         []string         `xml:"domain"`
	IncomingServer []IncomingServer `xml:"incomingServer"`
	OutgoingServer OutgoingServer   `xml:"outgoingServer"`
}

type IncomingServer struct {
	Text string `xml:",chardata"`
	// can only be "imap" or "pop3", only imap is supported by aerc
	Type           string `xml:"type,attr"`
	Hostname       string `xml:"hostname"`
	Port           string `xml:"port"`
	SocketType     string `xml:"socketType"`
	Username       string `xml:"username"`
	Authentication string `xml:"authentication"`
	Password       string `xml:"password"`
}

type OutgoingServer struct {
	Text                     string `xml:",chardata"`
	Type                     string `xml:"type,attr"`
	Hostname                 string `xml:"hostname"`
	Port                     string `xml:"port"`
	SocketType               string `xml:"socketType"`
	Username                 string `xml:"username"`
	Authentication           string `xml:"authentication"`
	Restriction              string `xml:"restriction"`
	AddThisServer            string `xml:"addThisServer"`
	UseGlobalPreferredServer string `xml:"useGlobalPreferredServer"`
	Password                 string `xml:"password"`
}
