package whois

import (
	"context"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os/exec"
	"regexp"
	"strings"
	"time"

	"github.com/henrygd/beszel/internal/entities/domain"
)

// LookupService handles WHOIS lookups with multiple fallback methods
type LookupService struct {
	whoisXMLAPIKey string
	rdapCache      map[string]string
}

// NewLookupService creates a new WHOIS lookup service
func NewLookupService(apiKey string) *LookupService {
	return &LookupService{
		whoisXMLAPIKey: apiKey,
		rdapCache:      make(map[string]string),
	}
}

// LookupDomain performs a comprehensive domain lookup (WHOIS, DNS, SSL, Host)
func (s *LookupService) LookupDomain(ctx context.Context, domainName string) (*domain.Domain, error) {
	// Clean domain name
	domainName = cleanDomain(domainName)

	// Initialize domain struct
	d := &domain.Domain{
		DomainName:      domainName,
		Active:          true,
		AlertDaysBefore: 30, // Default: alert 30 days before expiry
		Tags:            []string{},
		NameServers:     []string{},
		MXRecords:       []string{},
		TXTRecords:      []string{},
		IPv4Addresses:   []string{},
		IPv6Addresses:   []string{},
	}

	// Perform WHOIS lookup
	whoisData, err := s.LookupWHOIS(ctx, domainName)
	if err == nil && whoisData != nil {
		s.applyWHOISData(d, whoisData)
	}

	// Perform DNS lookups
	s.lookupDNS(ctx, domainName, d)

	// Perform SSL lookup
	s.lookupSSL(ctx, domainName, d)

	// Perform host lookup (using first IPv4)
	if len(d.IPv4Addresses) > 0 {
		s.lookupHost(d.IPv4Addresses[0], d)
	}

	// Fetch favicon
	d.FaviconURL = fmt.Sprintf("https://www.google.com/s2/favicons?domain=%s&sz=128", domainName)

	d.LastChecked = time.Now()
	return d, nil
}

// LookupWHOIS performs WHOIS lookup with multiple fallback methods
func (s *LookupService) LookupWHOIS(ctx context.Context, domainName string) (*domain.WHOISData, error) {
	var lastErr error

	// Try RDAP first (modern replacement for WHOIS)
	data, err := s.tryRDAP(ctx, domainName)
	if err == nil && data != nil && hasValidData(data) {
		return data, nil
	}
	lastErr = err

	// Try pure-Go TCP WHOIS (works in containers without whois binary)
	data, err = s.tryTCPWHOIS(ctx, domainName)
	if err == nil && data != nil && hasValidData(data) {
		return data, nil
	}
	if lastErr == nil {
		lastErr = err
	}

	// Try native whois command
	data, err = s.tryNativeWHOIS(ctx, domainName)
	if err == nil && data != nil && hasValidData(data) {
		return data, nil
	}
	if lastErr == nil {
		lastErr = err
	}

	// Try WhoisXML API if key is configured
	if s.whoisXMLAPIKey != "" {
		data, err = s.tryWhoisXML(ctx, domainName)
		if err == nil && data != nil {
			return data, nil
		}
	}

	return nil, fmt.Errorf("all WHOIS lookup methods failed for %s: %w", domainName, lastErr)
}

// tryRDAP attempts RDAP lookup
func (s *LookupService) tryRDAP(ctx context.Context, domainName string) (*domain.WHOISData, error) {
	// Get TLD
	parts := strings.Split(domainName, ".")
	if len(parts) < 2 {
		return nil, fmt.Errorf("invalid domain format")
	}
	tld := parts[len(parts)-1]

	// Get RDAP base URL
	baseURL, err := s.getRDAPBaseURL(ctx, tld)
	if err != nil {
		return nil, err
	}

	// Make RDAP request
	url := fmt.Sprintf("%s/domain/%s", baseURL, domainName)
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/rdap+json")

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("RDAP returned status %d", resp.StatusCode)
	}

	var rdapResp struct {
		LdhName string   `json:"ldhName"`
		Handle  string   `json:"handle"`
		Status  []string `json:"status"`
		Events  []struct {
			EventAction string `json:"eventAction"`
			EventDate   string `json:"eventDate"`
		} `json:"events"`
		Entities []struct {
			Roles     []string `json:"roles"`
			PublicIds []struct {
				Type       string `json:"type"`
				Identifier string `json:"identifier"`
			} `json:"publicIds"`
			VCardArray []interface{} `json:"vcardArray"`
		} `json:"entities"`
		SecureDNS struct {
			ZoneSigned bool `json:"zoneSigned"`
		} `json:"secureDNS"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&rdapResp); err != nil {
		return nil, err
	}

	// Parse events
	var creationDate, expiryDate, updatedDate *time.Time
	for _, event := range rdapResp.Events {
		t, err := time.Parse(time.RFC3339, event.EventDate)
		if err != nil || t.IsZero() {
			continue
		}
		switch event.EventAction {
		case "registration":
			creationDate = &t
		case "expiration":
			expiryDate = &t
		case "last changed":
			updatedDate = &t
		}
	}

	// Find registrar
	var registrarName, registrarID string
	for _, entity := range rdapResp.Entities {
		for _, role := range entity.Roles {
			if role == "registrar" {
				// Try to get name from vCard
				if len(entity.VCardArray) > 1 {
					if vcard, ok := entity.VCardArray[1].([]interface{}); ok {
						for _, item := range vcard {
							if arr, ok := item.([]interface{}); ok && len(arr) >= 4 {
								if arr[0] == "fn" {
									if name, ok := arr[3].(string); ok {
										registrarName = name
									}
								}
							}
						}
					}
				}
				// Get IANA ID
				for _, pid := range entity.PublicIds {
					if pid.Type == "IANA Registrar ID" {
						registrarID = pid.Identifier
					}
				}
			}
		}
	}

	dnssec := ""
	if rdapResp.SecureDNS.ZoneSigned {
		dnssec = "signed"
	}

	return &domain.WHOISData{
		DomainName: rdapResp.LdhName,
		Status:     rdapResp.Status,
		DNSSEC:     dnssec,
		Dates: domain.WHOISDates{
			ExpiryDate:   expiryDate,
			CreationDate: creationDate,
			UpdatedDate:  updatedDate,
		},
		Registrar: domain.WHOISRegistrar{
			Name:             registrarName,
			ID:               registrarID,
			URL:              "",
			RegistryDomainID: rdapResp.Handle,
		},
	}, nil
}

// tryNativeWHOIS tries the native whois command
func (s *LookupService) tryNativeWHOIS(ctx context.Context, domainName string) (*domain.WHOISData, error) {
	// Check if whois command exists
	_, err := exec.LookPath("whois")
	if err != nil {
		return nil, fmt.Errorf("whois command not found")
	}

	// Execute whois with timeout
	cmdCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	cmd := exec.CommandContext(cmdCtx, "whois", domainName)
	output, err := cmd.Output()
	if err != nil {
		return nil, err
	}

	return s.parseWHOISOutput(string(output), domainName)
}

// whoisServers maps common TLDs to their WHOIS servers
var whoisServers = map[string]string{
	"com":    "whois.verisign-grs.com",
	"net":    "whois.verisign-grs.com",
	"org":    "whois.pir.org",
	"io":     "whois.nic.io",
	"co":     "whois.nic.co",
	"dev":    "whois.nic.google",
	"app":    "whois.nic.google",
	"xyz":    "whois.nic.xyz",
	"info":   "whois.afilias.net",
	"biz":    "whois.biz",
	"us":     "whois.nic.us",
	"uk":     "whois.nic.uk",
	"de":     "whois.denic.de",
	"fr":     "whois.nic.fr",
	"eu":     "whois.eu",
	"nl":     "whois.domain-registry.nl",
	"ca":     "whois.cira.ca",
	"au":     "whois.auda.org.au",
	"me":     "whois.nic.me",
	"tv":     "whois.nic.tv",
	"cc":     "whois.nic.cc",
	"ws":     "whois.website.ws",
	"name":   "whois.nic.name",
	"mobi":   "whois.dotmobiregistry.net",
	"asia":   "whois.nic.asia",
	"pro":    "whois.nic.pro",
	"jobs":   "whois.nic.jobs",
	"travel": "whois.nic.travel",
}

// tryTCPWHOIS performs WHOIS lookup via direct TCP connection (port 43)
func (s *LookupService) tryTCPWHOIS(ctx context.Context, domainName string) (*domain.WHOISData, error) {
	parts := strings.Split(domainName, ".")
	if len(parts) < 2 {
		return nil, fmt.Errorf("invalid domain format")
	}
	tld := strings.ToLower(parts[len(parts)-1])

	server, ok := whoisServers[tld]
	if !ok {
		// Fallback to IANA for unknown TLDs
		server = "whois.iana.org"
	}

	addr := net.JoinHostPort(server, "43")

	dialer := &net.Dialer{Timeout: 10 * time.Second}
	conn, err := dialer.DialContext(ctx, "tcp", addr)
	if err != nil {
		return nil, fmt.Errorf("tcp whois dial failed: %w", err)
	}
	defer conn.Close()

	// Some servers require the domain followed by \r\n
	query := domainName + "\r\n"
	if _, err := conn.Write([]byte(query)); err != nil {
		return nil, fmt.Errorf("tcp whois write failed: %w", err)
	}

	// Read response with deadline
	if err := conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil {
		return nil, err
	}

	var output strings.Builder
	buf := make([]byte, 4096)
	for {
		n, err := conn.Read(buf)
		if n > 0 {
			output.Write(buf[:n])
		}
		if err != nil {
			break
		}
	}

	return s.parseWHOISOutput(output.String(), domainName)
}

// tryWhoisXML tries the WhoisXML API
func (s *LookupService) tryWhoisXML(ctx context.Context, domainName string) (*domain.WHOISData, error) {
	if s.whoisXMLAPIKey == "" {
		return nil, fmt.Errorf("no API key configured")
	}

	url := fmt.Sprintf(
		"https://www.whoisxmlapi.com/whoisserver/WhoisService?apiKey=%s&outputFormat=json&domainName=%s",
		s.whoisXMLAPIKey, domainName,
	)

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("WhoisXML API returned %d", resp.StatusCode)
	}

	var result struct {
		WhoisRecord struct {
			DomainName      string `json:"domainName"`
			RegistrarName   string `json:"registrarName"`
			RegistrarIANAID string `json:"registrarIANAID"`
			RegistryData    struct {
				Status                string `json:"status"`
				CreatedDateNormalized string `json:"createdDateNormalized"`
				ExpiresDateNormalized string `json:"expiresDateNormalized"`
				UpdatedDateNormalized string `json:"updatedDateNormalized"`
				WhoisServer           string `json:"whoisServer"`
			} `json:"registryData"`
		} `json:"WhoisRecord"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}

	record := result.WhoisRecord
	registry := record.RegistryData

	creationDate, _ := time.Parse("2006-01-02", registry.CreatedDateNormalized)
	expiryDate, _ := time.Parse("2006-01-02", registry.ExpiresDateNormalized)
	updatedDate, _ := time.Parse("2006-01-02", registry.UpdatedDateNormalized)

	return &domain.WHOISData{
		DomainName: record.DomainName,
		Status:     strings.Split(registry.Status, ", "),
		Dates: domain.WHOISDates{
			ExpiryDate:   &expiryDate,
			CreationDate: &creationDate,
			UpdatedDate:  &updatedDate,
		},
		Registrar: domain.WHOISRegistrar{
			Name: record.RegistrarName,
			ID:   record.RegistrarIANAID,
			URL:  fmt.Sprintf("https://%s", registry.WhoisServer),
		},
	}, nil
}

// parseWHOISOutput parses the raw WHOIS text output
func (s *LookupService) parseWHOISOutput(output, domainName string) (*domain.WHOISData, error) {
	lines := strings.Split(output, "\n")
	data := make(map[string]string)

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "%") {
			continue
		}

		// Parse "Key: Value" format
		if idx := strings.Index(line, ":"); idx > 0 {
			key := strings.ToLower(strings.TrimSpace(line[:idx]))
			value := strings.TrimSpace(line[idx+1:])
			// Normalize key
			key = strings.ReplaceAll(key, " ", "_")
			key = strings.ReplaceAll(key, "/", "_")
			if value != "" && !strings.HasPrefix(value, "REDACTED") {
				data[key] = value
			}
		}
	}

	// Extract dates - try many field name variations used by different registries
	expiryDate := s.parseDate(
		data["registry_expiry_date"],
		data["registrar_registration_expiration_date"],
		data["expiry_date"],
		data["expiration_time"],
		data["expire"],
		data["paid_until"],
		data["expire_date"],
		data["renewal_date"],
		data["valid_until"],
	)
	creationDate := s.parseDate(
		data["creation_date"],
		data["created_date"],
		data["registration_time"],
		data["registered_on"],
		data["domain_registered"],
	)
	updatedDate := s.parseDate(
		data["updated_date"],
		data["last_updated"],
		data["last_modified"],
		data["modified_date"],
	)

	// Extract registrar - try multiple field names used by different WHOIS servers
	registrarName := data["registrar"]
	if registrarName == "" {
		registrarName = data["registrar_name"]
	}
	if registrarName == "" {
		registrarName = data["sponsoring_registrar"]
	}
	if registrarName == "" {
		registrarName = data["registrar_organization"]
	}
	if registrarName == "" {
		registrarName = data["registrant_organization"]
	}
	if registrarName == "" {
		registrarName = data["registrar_url"]
	}
	if registrarName == "" {
		registrarName = data["registrar_abuse_contact_email"]
	}
	if registrarName == "" {
		registrarName = "Unknown"
	}

	// Parse status
	statusStr := data["domain_status"]
	var statuses []string
	if statusStr != "" {
		statuses = s.parseStatus(statusStr)
	}

	// Extract registrant contact info
	registrant := domain.WHOISContact{
		Name:         data["registrant_name"],
		Organization: data["registrant_organization"],
		Street:       data["registrant_street"],
		City:         data["registrant_city"],
		State:        data["registrant_state_province"],
		Country:      data["registrant_country"],
		PostalCode:   data["registrant_postal_code"],
	}

	// Try alternate field names for registrant (.eu uses "holder", other variations)
	if registrant.Name == "" {
		registrant.Name = data["registrant"]
	}
	if registrant.Name == "" {
		registrant.Name = data["holder"]
	}
	if registrant.Name == "" {
		registrant.Name = data["domain_holder"]
	}
	if registrant.Organization == "" {
		registrant.Organization = data["org"]
	}
	if registrant.Organization == "" {
		registrant.Organization = data["organization"]
	}
	if registrant.Organization == "" {
		registrant.Organization = data["holder_org"]
	}
	if registrant.Country == "" {
		registrant.Country = data["country"]
	}
	if registrant.Country == "" {
		registrant.Country = data["holder_country"]
	}

	// Parse DNSSEC more thoroughly
	dnssec := data["dnssec"]
	if dnssec == "" {
		// Try alternate field names
		dnssec = data["dnssec_signed"]
	}
	if dnssec == "" {
		dnssec = data["signed_dnssec"]
	}
	// Normalize DNSSEC value
	dnssec = strings.ToLower(strings.TrimSpace(dnssec))
	if dnssec == "signed" || dnssec == "yes" || dnssec == "true" {
		dnssec = "signed"
	} else if dnssec == "unsigned" || dnssec == "no" || dnssec == "false" {
		dnssec = "unsigned"
	}

	return &domain.WHOISData{
		DomainName: domainName,
		Status:     statuses,
		DNSSEC:     dnssec,
		Dates: domain.WHOISDates{
			ExpiryDate:   expiryDate,
			CreationDate: creationDate,
			UpdatedDate:  updatedDate,
		},
		Registrar: domain.WHOISRegistrar{
			Name:             registrarName,
			ID:               data["registrar_iana_id"],
			URL:              data["registrar_url"],
			RegistryDomainID: data["registry_domain_id"],
		},
		Registrant: registrant,
		Abuse: domain.WHOISAbuse{
			Email: data["registrar_abuse_contact_email"],
			Phone: data["registrar_abuse_contact_phone"],
		},
	}, nil
}

// parseDate attempts to parse a date from multiple possible formats
func (s *LookupService) parseDate(dates ...string) *time.Time {
	formats := []string{
		// Standard ISO formats
		"2006-01-02",
		"2006-01-02T15:04:05Z",
		"2006-01-02T15:04:05-07:00",
		"2006-01-02 15:04:05",
		"2006-01-02 15:04:05.0",
		// US formats
		"01/02/2006",
		"01/02/2006 15:04:05",
		// European formats
		"02/01/2006",
		"02.01.2006",
		// Verbal formats
		"Jan 2 2006",
		"January 2 2006",
		"2 Jan 2006",
		"2 January 2006",
		"Jan 02 2006",
		"02-Jan-2006",
		"2-Jan-2006",
		// With timezone names
		"2006-01-02 15:04:05 MST",
		"2006-01-02 15:04:05 UTC",
		// Common registrar formats
		"Monday, January 2, 2006",
		"Mon, 02 Jan 2006 15:04:05 MST",
		"Mon, 2 Jan 2006 15:04:05 MST",
		// Additional formats
		"20060102",
		"20060102150405",
	}

	for _, dateStr := range dates {
		if dateStr == "" || dateStr == "REDACTED" || strings.Contains(dateStr, "REDACTED") {
			continue
		}
		dateStr = strings.TrimSpace(dateStr)
		// Remove common prefixes/suffixes that don't help
		dateStr = strings.TrimPrefix(dateStr, "before ")
		dateStr = strings.TrimPrefix(dateStr, "after ")

		for _, format := range formats {
			if t, err := time.Parse(format, dateStr); err == nil {
				return &t
			}
		}
	}
	return nil
}

// parseStatus parses domain status from WHOIS output
func (s *LookupService) parseStatus(statusStr string) []string {
	knownStatuses := []string{
		"clientDeleteProhibited", "clientHold", "clientRenewProhibited",
		"clientTransferProhibited", "clientUpdateProhibited",
		"serverDeleteProhibited", "serverHold", "serverRenewProhibited",
		"serverTransferProhibited", "serverUpdateProhibited",
		"inactive", "ok", "pendingCreate", "pendingDelete", "pendingRenew",
		"pendingRestore", "pendingTransfer", "pendingUpdate",
		"addPeriod", "autoRenewPeriod", "renewPeriod", "transferPeriod",
	}

	statusStr = strings.ToLower(statusStr)
	var matches []string
	for _, status := range knownStatuses {
		if strings.Contains(statusStr, strings.ToLower(status)) {
			matches = append(matches, status)
		}
	}
	return matches
}

// getRDAPBaseURL gets the RDAP base URL for a TLD
func (s *LookupService) getRDAPBaseURL(ctx context.Context, tld string) (string, error) {
	// Check cache
	if url, ok := s.rdapCache[tld]; ok {
		return url, nil
	}

	// Fetch IANA RDAP bootstrap
	url := "https://data.iana.org/rdap/dns.json"
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return "", err
	}

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var bootstrap struct {
		Services [][]interface{} `json:"services"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&bootstrap); err != nil {
		return "", err
	}

	// Populate cache and find URL for this TLD
	for _, service := range bootstrap.Services {
		if len(service) >= 2 {
			tlds, ok1 := service[0].([]interface{})
			urls, ok2 := service[1].([]interface{})
			if ok1 && ok2 && len(urls) > 0 {
				if urlStr, ok := urls[0].(string); ok {
					for _, t := range tlds {
						if tldStr, ok := t.(string); ok {
							s.rdapCache[tldStr] = strings.TrimSuffix(urlStr, "/")
						}
					}
				}
			}
		}
	}

	if url, ok := s.rdapCache[tld]; ok {
		return url, nil
	}
	return "", fmt.Errorf("no RDAP server found for TLD .%s", tld)
}

// lookupDNS performs DNS lookups
func (s *LookupService) lookupDNS(ctx context.Context, domainName string, d *domain.Domain) {
	// NS records
	nsRecords, _ := net.LookupNS(domainName)
	for _, ns := range nsRecords {
		d.NameServers = append(d.NameServers, ns.Host)
	}

	// MX records
	mxRecords, _ := net.LookupMX(domainName)
	for _, mx := range mxRecords {
		d.MXRecords = append(d.MXRecords, fmt.Sprintf("%s (priority: %d)", mx.Host, mx.Pref))
	}

	// TXT records
	txtRecords, _ := net.LookupTXT(domainName)
	d.TXTRecords = txtRecords

	// CNAME record
	cname, err := net.LookupCNAME(domainName)
	if err == nil && cname != domainName && cname != "" {
		d.CNAMERecord = cname
	}

	// SRV records (common services)
	srvServices := []string{"sip", "xmpp-server", "ldap", "autodiscover", "imap", "smtp", "caldavs", "carddavs"}
	srvProtos := []string{"tcp", "udp", "tls"}
	for _, service := range srvServices {
		for _, proto := range srvProtos {
			_, addrs, err := net.LookupSRV(service, proto, domainName)
			if err == nil {
				for _, addr := range addrs {
					d.SRVRecords = append(d.SRVRecords, fmt.Sprintf("_%s._%s %s:%d (priority: %d, weight: %d)", service, proto, addr.Target, addr.Port, addr.Priority, addr.Weight))
				}
			}
		}
	}

	// IPv4
	ipv4Addrs, _ := net.LookupHost(domainName)
	for _, ip := range ipv4Addrs {
		if strings.Contains(ip, ".") {
			d.IPv4Addresses = append(d.IPv4Addresses, ip)
		}
	}

	// IPv6
	ipv6Addrs, _ := net.LookupIP(domainName)
	for _, ip := range ipv6Addrs {
		if ip.To4() == nil {
			d.IPv6Addresses = append(d.IPv6Addresses, ip.String())
		}
	}
}

// lookupSSL fetches SSL certificate info
func (s *LookupService) lookupSSL(ctx context.Context, domainName string, d *domain.Domain) {
	conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, "tcp", domainName+":443", &tls.Config{
		ServerName:         domainName,
		InsecureSkipVerify: true,
	})
	if err != nil {
		return
	}
	defer conn.Close()

	cert := conn.ConnectionState().PeerCertificates[0]
	if cert != nil {
		if len(cert.Issuer.Organization) > 0 {
			d.SSLIssuer = cert.Issuer.Organization[0]
		}
		if len(cert.Issuer.Country) > 0 {
			d.SSLIssuerCountry = cert.Issuer.Country[0]
		}
		d.SSLValidFrom = &cert.NotBefore
		d.SSLValidTo = &cert.NotAfter
		d.SSLSubject = cert.Subject.CommonName

		fingerprint := sha256.Sum256(cert.Raw)
		d.SSLFingerprint = strings.ToUpper(strings.Join(splitHex(hex.EncodeToString(fingerprint[:])), ":"))

		d.SSLSignatureAlgo = cert.SignatureAlgorithm.String()

		switch key := cert.PublicKey.(type) {
		case *rsa.PublicKey:
			d.SSLKeySize = key.N.BitLen()
		case *ecdsa.PublicKey:
			d.SSLKeySize = key.Curve.Params().BitSize
		default:
			d.SSLKeySize = 0
		}
	}
}

func splitHex(value string) []string {
	parts := make([]string, 0, len(value)/2)
	for i := 0; i < len(value); i += 2 {
		end := i + 2
		if end > len(value) {
			end = len(value)
		}
		parts = append(parts, value[i:end])
	}
	return parts
}

// lookupHost fetches host/geolocation info
func (s *LookupService) lookupHost(ip string, d *domain.Domain) {
	// Use ip-api.com (free, no auth required for non-commercial use)
	url := fmt.Sprintf("http://ip-api.com/json/%s?fields=status,message,country,regionName,city,lat,lon,isp,org,as", ip)

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	var result struct {
		Status  string  `json:"status"`
		Message string  `json:"message"`
		Country string  `json:"country"`
		Region  string  `json:"regionName"`
		City    string  `json:"city"`
		Lat     float64 `json:"lat"`
		Lon     float64 `json:"lon"`
		ISP     string  `json:"isp"`
		Org     string  `json:"org"`
		AS      string  `json:"as"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return
	}

	if result.Status == "success" {
		d.HostCountry = result.Country
		d.HostRegion = result.Region
		d.HostCity = result.City
		d.HostLat = result.Lat
		d.HostLon = result.Lon
		d.HostISP = result.ISP
		d.HostOrg = result.Org
		d.HostAS = result.AS
	}
}

// applyWHOISData applies WHOIS data to domain struct
func (s *LookupService) applyWHOISData(d *domain.Domain, whois *domain.WHOISData) {
	d.DomainName = whois.DomainName
	d.Status = strings.Join(whois.Status, ", ")
	d.DNSSEC = whois.DNSSEC
	d.ExpiryDate = whois.Dates.ExpiryDate
	d.CreationDate = whois.Dates.CreationDate
	d.UpdatedDate = whois.Dates.UpdatedDate
	d.RegistrarName = whois.Registrar.Name
	d.RegistrarID = whois.Registrar.ID
	d.RegistrarURL = whois.Registrar.URL
	d.RegistryDomainID = whois.Registrar.RegistryDomainID

	// Apply registrant contact info if available
	if whois.Registrant.Name != "" || whois.Registrant.Organization != "" {
		d.RegistrantName = whois.Registrant.Name
		d.RegistrantOrg = whois.Registrant.Organization
		d.RegistrantStreet = whois.Registrant.Street
		d.RegistrantCity = whois.Registrant.City
		d.RegistrantState = whois.Registrant.State
		d.RegistrantCountry = whois.Registrant.Country
		d.RegistrantPostal = whois.Registrant.PostalCode
	}

	// Apply abuse contact info
	if whois.Abuse.Email != "" || whois.Abuse.Phone != "" {
		d.AbuseEmail = whois.Abuse.Email
		d.AbusePhone = whois.Abuse.Phone
	}
}

// cleanDomain cleans and normalizes a domain name
func cleanDomain(domain string) string {
	// Remove protocol
	domain = regexp.MustCompile(`^https?://`).ReplaceAllString(domain, "")
	// Remove www prefix
	domain = regexp.MustCompile(`^www\.`).ReplaceAllString(domain, "")
	// Remove path and query
	if idx := strings.IndexAny(domain, "/?#"); idx != -1 {
		domain = domain[:idx]
	}
	// Remove port
	if idx := strings.Index(domain, ":"); idx != -1 {
		domain = domain[:idx]
	}
	return strings.ToLower(strings.TrimSpace(domain))
}

// hasValidData checks if WHOIS data has useful parsed fields
func hasValidData(data *domain.WHOISData) bool {
	if data == nil {
		return false
	}
	// Accept if we got any meaningful date (non-nil and not zero)
	if data.Dates.ExpiryDate != nil && !data.Dates.ExpiryDate.IsZero() {
		return true
	}
	if data.Dates.CreationDate != nil && !data.Dates.CreationDate.IsZero() {
		return true
	}
	if data.Registrar.Name != "" && data.Registrar.Name != "Unknown" {
		return true
	}
	if len(data.Status) > 0 {
		return true
	}
	return false
}
