package scanner

import (
	"bufio"
	"context"
	"fmt"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// DefaultSubdomains is a built-in fallback list used when no wordlist file
// is available. The file at projectRoot/subdomains.txt (one name per line,
// # for comments) is preferred and can contain thousands of entries.
var DefaultSubdomains = []string{
	"api", "www", "admin", "mail", "webmail",
	"dev", "staging", "app", "dashboard", "portal",
	"blog", "docs", "support", "git", "cdn",
	"static", "assets", "demo", "vpn", "remote",
	"secure", "login", "auth", "gateway", "console",
	"monitor", "status", "jenkins", "wiki", "forum",
	"corp", "internal", "partner", "help", "mx",
	"ns1", "ns2", "smtp", "imap", "pop3",
}

// hasWildcardDNS checks whether the domain uses wildcard DNS by resolving
// a random non-existent subdomain. If it resolves, wildcard is active.
func hasWildcardDNS(ctx context.Context, domain string) bool {
	probes := []string{
		fmt.Sprintf("zzz%d%d.%s", time.Now().UnixNano()%1000, 9999, domain),
		fmt.Sprintf("x%sx.%s", fmt.Sprintf("%x", time.Now().UnixNano()%65535), domain),
	}
	for _, p := range probes {
		addrs, err := net.DefaultResolver.LookupHost(ctx, p)
		if err == nil && len(addrs) > 0 {
			return true
		}
	}
	return false
}

// ProbeSubdomains checks common subdomain names and returns those
// confirmed to serve real HTTPS content (valid TLS certificate).
//
// TLS certificate validation is the definitive signal — a wildcard DNS
// entry resolves for any name but has no valid certificate for arbitrary
// subdomains. Only real web services pass. Any real API in 2026 runs HTTPS.
//
// Loads subdomain names from projectRoot/subdomains.txt (one per line, # for comments).
// Falls back to a built-in list of ~40 names if the file doesn't exist.
//
// Health check timeout 3s, concurrency max 10, total probe < 15s for 40 names,
// scales linearly with wordlist size (e.g. ~2min for 500 names).
//
// If onProgress is non-nil, it is called periodically with (checked, total) counts
// so the caller can report progress to the user (e.g. "checking 142/5000").
func ProbeSubdomains(ctx context.Context, projectRoot, domain string, onProgress func(checked, total int)) []string {
	var (
		alive []string
		mu    sync.Mutex
		wg    sync.WaitGroup
	)

	if wildcard := hasWildcardDNS(ctx, domain); wildcard {
		fmt.Printf("probe: wildcard DNS for %s — filtering by TLS cert\n", domain)
	}

	// Load subdomain names from file or fall back to built-in list
	names := loadSubdomainList(projectRoot)
	fmt.Printf("probe: checking %d subdomain names against %s\n", len(names), domain)

	total := len(names)

	sem := make(chan struct{}, 10)
	client := &http.Client{Timeout: 3 * time.Second}

	// Atomically count completed probes for progress reporting
	var completed int32

	for _, name := range names {
		wg.Add(1)
		go func(s string) {
			defer wg.Done()
			select {
			case sem <- struct{}{}:
			case <-ctx.Done():
				return
			}
			defer func() { <-sem }()

			// HTTPS GET — does DNS + TLS in one shot.
			// Go's http.Client validates cert + hostname by default.
			resp, err := client.Get("https://" + s + "." + domain)
			if err == nil {
				resp.Body.Close()
				mu.Lock()
				alive = append(alive, s+"."+domain)
				mu.Unlock()
			}

			// Report progress
			c := int(atomic.AddInt32(&completed, 1))
			if onProgress != nil {
				onProgress(c, total)
			}
		}(name)
	}
	wg.Wait()

	return alive
}

// loadSubdomainList reads subdomain names from projectRoot/subdomains.txt.
// Each line is one name; lines starting with # and empty lines are ignored.
// Falls back to DefaultSubdomains if the file doesn't exist.
func loadSubdomainList(projectRoot string) []string {
	path := filepath.Join(projectRoot, "subdomains.txt")
	f, err := os.Open(path)
	if err != nil {
		fmt.Printf("probe: no subdomains.txt found, using default list (%d names)\n", len(DefaultSubdomains))
		return DefaultSubdomains
	}
	defer f.Close()

	seen := make(map[string]bool)
	var names []string
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		name := strings.ToLower(line)
		if !seen[name] {
			seen[name] = true
			names = append(names, name)
		}
	}
	if err := scanner.Err(); err != nil {
		fmt.Printf("probe: error reading subdomains.txt: %v, using default list\n", err)
		return DefaultSubdomains
	}
	if len(names) == 0 {
		fmt.Printf("probe: subdomains.txt is empty, using default list\n")
		return DefaultSubdomains
	}
	fmt.Printf("probe: loaded %d names from subdomains.txt\n", len(names))
	return names
}
