package hosted

import (
	"context"
	"fmt"
	"net"
	"sort"
	"strings"
)

// DNSCheckReport summarizes live DNS checks for a hosted mail domain.
type DNSCheckReport struct {
	Domain      string   `json:"domain"`
	TXTVerified bool     `json:"txt_verified"`
	TXTRecords  []string `json:"txt_records,omitempty"`
	TXTExpected string   `json:"txt_expected,omitempty"`
	MXVerified  bool     `json:"mx_verified"`
	MXRecords   []string `json:"mx_records"`
	ExpectedMX  []string `json:"expected_mx"`
	Warnings    []string `json:"warnings,omitempty"`
	Errors      []string `json:"errors,omitempty"`
}

func LookupDomainMX(ctx context.Context, domain string) ([]string, error) {
	domain = strings.ToLower(strings.TrimSpace(domain))
	if domain == "" {
		return nil, fmt.Errorf("domain required")
	}
	mxRecords, err := (&net.Resolver{}).LookupMX(ctx, domain)
	if err != nil {
		return nil, err
	}
	sort.Slice(mxRecords, func(i, j int) bool {
		return mxRecords[i].Pref < mxRecords[j].Pref
	})
	out := make([]string, 0, len(mxRecords))
	for _, mx := range mxRecords {
		host := strings.TrimSuffix(strings.ToLower(mx.Host), ".")
		if host != "" {
			out = append(out, host)
		}
	}
	return out, nil
}

func LookupDomainTXT(ctx context.Context, name string) ([]string, error) {
	name = strings.ToLower(strings.TrimSpace(name))
	if name == "" {
		return nil, fmt.Errorf("txt name required")
	}
	records, err := (&net.Resolver{}).LookupTXT(ctx, name)
	if err != nil {
		return nil, err
	}
	out := make([]string, 0, len(records))
	for _, record := range records {
		record = strings.TrimSpace(record)
		if record != "" {
			out = append(out, record)
		}
	}
	return out, nil
}

func MXMatchesExpected(mxHosts, expected []string) bool {
	if len(mxHosts) == 0 || len(expected) == 0 {
		return false
	}
	for _, mx := range mxHosts {
		mx = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(mx)), ".")
		for _, want := range expected {
			want = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(want)), ".")
			if want == "" {
				continue
			}
			if mx == want || strings.HasSuffix(mx, "."+want) {
				return true
			}
		}
	}
	return false
}

func TXTContainsToken(records []string, token string) bool {
	token = strings.TrimSpace(token)
	if token == "" {
		return false
	}
	for _, record := range records {
		if strings.TrimSpace(record) == token {
			return true
		}
	}
	return false
}

func (s *Service) CheckDomainDNS(ctx context.Context, domainID string, expectedMX []string) (DomainRow, DNSCheckReport, error) {
	row, err := s.GetDomain(ctx, domainID)
	if err != nil {
		return DomainRow{}, DNSCheckReport{}, err
	}
	report := DNSCheckReport{
		Domain:     row.Name,
		ExpectedMX: append([]string(nil), expectedMX...),
		TXTExpected: strings.TrimSpace(row.VerificationToken),
	}

	txtName := "_ultisuite-verify." + row.Name
	txtRecords, err := LookupDomainTXT(ctx, txtName)
	if err != nil {
		report.Errors = append(report.Errors, "txt lookup: "+err.Error())
	} else {
		report.TXTRecords = txtRecords
		report.TXTVerified = TXTContainsToken(txtRecords, row.VerificationToken)
		if !report.TXTVerified && row.TXTVerifiedAt != nil {
			report.Warnings = append(report.Warnings, "txt record not found but domain was previously verified")
			report.TXTVerified = true
		}
	}

	mxRecords, err := LookupDomainMX(ctx, row.Name)
	if err != nil {
		report.Errors = append(report.Errors, "mx lookup: "+err.Error())
	} else {
		report.MXRecords = mxRecords
		report.MXVerified = MXMatchesExpected(mxRecords, expectedMX)
		if !report.MXVerified && row.MXVerifiedAt != nil && len(expectedMX) == 0 {
			report.MXVerified = len(mxRecords) > 0
		}
	}
	return row, report, nil
}

func (s *Service) VerifyDomainTXTRecord(ctx context.Context, domainID string) (DomainRow, DNSCheckReport, error) {
	row, report, err := s.CheckDomainDNS(ctx, domainID, nil)
	if err != nil {
		return DomainRow{}, DNSCheckReport{}, err
	}
	if !report.TXTVerified {
		return row, report, fmt.Errorf("txt verification token not found at _ultisuite-verify.%s", row.Name)
	}
	updated, err := s.MarkDomainVerified(ctx, domainID)
	if err != nil {
		return row, report, err
	}
	return updated, report, nil
}

func (s *Service) VerifyDomainMXRecord(ctx context.Context, domainID string, expectedMX []string) (DomainRow, DNSCheckReport, error) {
	row, report, err := s.CheckDomainDNS(ctx, domainID, expectedMX)
	if err != nil {
		return DomainRow{}, DNSCheckReport{}, err
	}
	if len(expectedMX) == 0 {
		report.Warnings = append(report.Warnings, "expected mx hosts not configured")
		return row, report, fmt.Errorf("expected mx hosts not configured")
	}
	if !report.MXVerified {
		return row, report, fmt.Errorf("mx records %v do not match expected %v", report.MXRecords, expectedMX)
	}
	updated, err := s.MarkDomainMXVerified(ctx, domainID)
	if err != nil {
		return row, report, err
	}
	return updated, report, nil
}
