package webui

import (
	"context"
	"crypto/tls"
	"io"
	"net"
	"net/http"
	"strings"
	"time"

	"gopkg.in/ini.v1"
)

// getPublicIP mirrors server_web_ui/utils.py's get_public_ip: try ifconfig.me, then
// httpbin.org, then the configured SPF_SERVER_IP fallback, then 127.0.0.1.
func getPublicIP(cfg *ini.File) string {
	client := &http.Client{
		Timeout:   3 * time.Second,
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
	}
	if ip := fetchBody(client, "http://ifconfig.me/ip"); ip != "" {
		return strings.TrimSpace(ip)
	}
	if ip := fetchBody(client, "http://httpbin.org/ip"); ip != "" {
		return strings.TrimSpace(ip)
	}
	fallback := cfg.Section("DKIM").Key("SPF_SERVER_IP").MustString("")
	if net.ParseIP(fallback) != nil {
		return fallback
	}
	return "127.0.0.1"
}

func fetchBody(client *http.Client, url string) string {
	resp, err := client.Get(url)
	if err != nil {
		return ""
	}
	defer resp.Body.Close()
	b, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
	if err != nil {
		return ""
	}
	return string(b)
}

// resolverAt builds a resolver pinned to a specific DNS server, mirroring
// utils.check_dns_record's hardcoded Cloudflare resolver (1.1.1.1), 5s timeout.
func resolverAt(serverIP string) *net.Resolver {
	return &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{Timeout: 5 * time.Second}
			return d.DialContext(ctx, network, net.JoinHostPort(serverIP, "53"))
		},
	}
}

var pinnedResolver = resolverAt("1.1.1.1")

type dnsCheckResult struct {
	Success bool
	Message string
	Records []string
}

// checkDNSRecord mirrors utils.check_dns_record for TXT lookups.
func checkDNSRecord(domain string) dnsCheckResult {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	recs, err := pinnedResolver.LookupTXT(ctx, domain)
	if err != nil {
		return dnsCheckResult{Success: false, Message: err.Error()}
	}
	if len(recs) == 0 {
		return dnsCheckResult{Success: false, Message: "No TXT records found"}
	}
	return dnsCheckResult{Success: true, Records: recs}
}

// verificationRecordName is the DNS TXT record name a domain's ownership proof lives
// at, e.g. "_pymta-verify.example.com".
func verificationRecordName(domain string) string {
	return "_pymta-verify." + domain
}

func verificationRecordValue(token string) string {
	return "pymta-verify=" + token
}

// checkDomainOwnership looks up the verification TXT record via two independent public
// resolvers (1.1.1.1 and 8.8.8.8, per the user's requirement) and considers the domain
// verified if the expected token shows up via either — a domain that's genuinely been
// updated can otherwise show as unverified for a while against whichever resolver has
// a stale cache, so requiring both to agree at the same instant would be a flaky check.
func checkDomainOwnership(domain, token string) (verified bool, checkedRecords []string, err error) {
	recordName := verificationRecordName(domain)
	expected := verificationRecordValue(token)

	var lastErr error
	for _, resolverIP := range []string{"1.1.1.1", "8.8.8.8"} {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		recs, lookupErr := resolverAt(resolverIP).LookupTXT(ctx, recordName)
		cancel()
		if lookupErr != nil {
			lastErr = lookupErr
			continue
		}
		checkedRecords = append(checkedRecords, recs...)
		for _, r := range recs {
			if strings.TrimSpace(r) == expected {
				return true, checkedRecords, nil
			}
		}
	}
	if len(checkedRecords) == 0 && lastErr != nil {
		return false, nil, lastErr
	}
	return false, checkedRecords, nil
}

// generateSPFRecord mirrors utils.generate_spf_record.
func generateSPFRecord(serverIP, existingSPF string) string {
	if existingSPF == "" {
		return "v=spf1 ip4:" + serverIP + " ~all"
	}
	if strings.Contains(existingSPF, "ip4:"+serverIP) {
		return existingSPF
	}
	for _, all := range []string{"-all", "~all", "all"} {
		if idx := strings.LastIndex(existingSPF, all); idx >= 0 {
			return strings.TrimSpace(existingSPF[:idx]) + " ip4:" + serverIP + " " + all
		}
	}
	return existingSPF + " ip4:" + serverIP + " ~all"
}
