package main

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync/atomic"
	"time"

	"github.com/gdamore/tcell/v2"
)

// ===========================================================================
// SILENT EYE MODULE (Module 8) — Passive Reconnaissance
// 8 vendors: crt.sh, Censys, Shodan, VirusTotal, SecurityTrails,
//            Hunter.io, BGPView, IPInfo
// ===========================================================================

// ---------------------------------------------------------------------------
// Config struct & management
// ---------------------------------------------------------------------------

type SilentAPIConfig struct {
	CensysID     string `json:"censys_id"`
	CensysSecret string `json:"censys_secret"`
	ShodanKey    string `json:"shodan_key"`
	VTKey        string `json:"virustotal_key"`
	STKey        string `json:"securitytrails_key"`
	HunterKey    string `json:"hunter_key"`
	IPInfoToken  string `json:"ipinfo_token"`
}

func silentConfigPath() string {
	exePath, _ := os.Executable()
	return filepath.Join(filepath.Dir(exePath), "silent-eye-keys.json")
}

func silentLoadConfig() SilentAPIConfig {
	var cfg SilentAPIConfig
	data, err := os.ReadFile(silentConfigPath())
	if err != nil {
		return cfg
	}
	json.Unmarshal(data, &cfg)
	return cfg
}

func silentSaveConfig(cfg SilentAPIConfig) error {
	data, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(silentConfigPath(), data, 0600)
}

// ---------------------------------------------------------------------------
// HTTP client
// ---------------------------------------------------------------------------

func silentNewHTTPClient() *http.Client {
	transport := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		MaxIdleConns:        100,
		MaxIdleConnsPerHost: 10,
		IdleConnTimeout:     90 * time.Second,
	}
	return &http.Client{
		Timeout:   30 * time.Second,
		Transport: transport,
	}
}

// ---------------------------------------------------------------------------
// Helper: check if string looks like an IP address
// ---------------------------------------------------------------------------

func silentIsIP(target string) bool {
	return net.ParseIP(target) != nil
}

// Helper: mask API key for display
func silentMaskKey(key string) string {
	if len(key) == 0 {
		return "(not set)"
	}
	if len(key) <= 8 {
		return key[:2] + "****"
	}
	return key[:4] + "..." + key[len(key)-4:]
}

// ---------------------------------------------------------------------------
// Vendor 1: crt.sh (Certificate Transparency) — FREE
// ---------------------------------------------------------------------------

func silentQueryCrtSh(ctx context.Context, client *http.Client, domain string) ([]map[string]string, error) {
	apiURL := fmt.Sprintf("https://crt.sh/?q=%%25.%s&output=json", url.QueryEscape(domain))

	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("crt.sh request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("crt.sh returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var entries []map[string]interface{}
	if err := json.Unmarshal(body, &entries); err != nil {
		return nil, fmt.Errorf("crt.sh JSON parse error: %v", err)
	}

	seen := make(map[string]bool)
	var results []map[string]string

	for _, entry := range entries {
		nameValue, ok := entry["name_value"].(string)
		if !ok {
			continue
		}
		// name_value can contain multiple subdomains separated by newlines
		for _, sub := range strings.Split(nameValue, "\n") {
			sub = strings.TrimSpace(sub)
			sub = strings.TrimPrefix(sub, "*.")
			if sub == "" || seen[sub] {
				continue
			}
			seen[sub] = true
			results = append(results, map[string]string{
				"data_type":  "subdomain",
				"data_value": sub,
			})
		}
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 2: Censys — PAID (Basic Auth)
// ---------------------------------------------------------------------------

func silentQueryCensys(ctx context.Context, client *http.Client, domain string, cfg SilentAPIConfig) ([]map[string]string, error) {
	if cfg.CensysID == "" || cfg.CensysSecret == "" {
		return nil, fmt.Errorf("censys API credentials not configured")
	}

	query := fmt.Sprintf("services.tls.certificates.leaf.names: %s", domain)
	payload := fmt.Sprintf(`{"q":"%s","per_page":100}`, query)

	req, err := http.NewRequestWithContext(ctx, "GET", "https://search.censys.io/api/v2/hosts/search?q="+url.QueryEscape(query)+"&per_page=100", nil)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(cfg.CensysID, cfg.CensysSecret)
	req.Header.Set("User-Agent", UserAgent)
	req.Header.Set("Accept", "application/json")
	_ = payload // query passed via URL params

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("censys request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 401 {
		return nil, fmt.Errorf("censys: invalid API credentials")
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("censys returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var respData struct {
		Result struct {
			Hits []struct {
				IP       string `json:"ip"`
				Services []struct {
					Port        int    `json:"port"`
					ServiceName string `json:"service_name"`
					Transport   string `json:"transport_protocol"`
				} `json:"services"`
			} `json:"hits"`
		} `json:"result"`
	}

	if err := json.Unmarshal(body, &respData); err != nil {
		return nil, fmt.Errorf("censys JSON parse error: %v", err)
	}

	var results []map[string]string
	for _, hit := range respData.Result.Hits {
		results = append(results, map[string]string{
			"data_type":  "ip",
			"data_value": hit.IP,
		})
		for _, svc := range hit.Services {
			results = append(results, map[string]string{
				"data_type":  "service",
				"data_value": fmt.Sprintf("%s:%d/%s (%s)", hit.IP, svc.Port, svc.Transport, svc.ServiceName),
			})
		}
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 3: Shodan — PAID
// ---------------------------------------------------------------------------

func silentQueryShodan(ctx context.Context, client *http.Client, target string, cfg SilentAPIConfig) ([]map[string]string, error) {
	if cfg.ShodanKey == "" {
		return nil, fmt.Errorf("shodan API key not configured")
	}

	// If target is a domain, resolve to IP first
	queryIP := target
	if !silentIsIP(target) {
		// Use Shodan DNS resolve
		resolveURL := fmt.Sprintf("https://api.shodan.io/dns/resolve?hostnames=%s&key=%s", url.QueryEscape(target), url.QueryEscape(cfg.ShodanKey))
		req, err := http.NewRequestWithContext(ctx, "GET", resolveURL, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("User-Agent", UserAgent)

		resp, err := client.Do(req)
		if err != nil {
			return nil, fmt.Errorf("shodan DNS resolve failed: %v", err)
		}
		defer resp.Body.Close()

		body, _ := io.ReadAll(resp.Body)
		var resolved map[string]interface{}
		if err := json.Unmarshal(body, &resolved); err == nil {
			if ip, ok := resolved[target].(string); ok && ip != "" {
				queryIP = ip
			} else {
				return nil, fmt.Errorf("shodan: could not resolve %s to IP", target)
			}
		}
	}

	apiURL := fmt.Sprintf("https://api.shodan.io/shodan/host/%s?key=%s", url.QueryEscape(queryIP), url.QueryEscape(cfg.ShodanKey))
	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("shodan request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 401 {
		return nil, fmt.Errorf("shodan: invalid API key")
	}
	if resp.StatusCode == 404 {
		return nil, fmt.Errorf("shodan: no data for %s", queryIP)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("shodan returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var hostData struct {
		IP   string `json:"ip_str"`
		OS   string `json:"os"`
		Org  string `json:"org"`
		ISP  string `json:"isp"`
		Data []struct {
			Port    int    `json:"port"`
			Banner  string `json:"data"`
			Product string `json:"product"`
		} `json:"data"`
		Hostnames []string `json:"hostnames"`
		Ports     []int    `json:"ports"`
	}

	if err := json.Unmarshal(body, &hostData); err != nil {
		return nil, fmt.Errorf("shodan JSON parse error: %v", err)
	}

	rawJSON := string(body)
	if len(rawJSON) > 2000 {
		rawJSON = rawJSON[:2000]
	}

	var results []map[string]string

	// IP
	if hostData.IP != "" {
		results = append(results, map[string]string{
			"data_type":  "ip",
			"data_value": hostData.IP,
		})
	}

	// OS
	if hostData.OS != "" {
		results = append(results, map[string]string{
			"data_type":  "os",
			"data_value": hostData.OS,
		})
	}

	// Org
	if hostData.Org != "" {
		results = append(results, map[string]string{
			"data_type":  "org",
			"data_value": hostData.Org,
		})
	}

	// Hostnames
	for _, h := range hostData.Hostnames {
		results = append(results, map[string]string{
			"data_type":  "hostname",
			"data_value": h,
		})
	}

	// Ports + banners
	for _, svc := range hostData.Data {
		portStr := fmt.Sprintf("%d", svc.Port)
		bannerPreview := svc.Banner
		if len(bannerPreview) > 200 {
			bannerPreview = bannerPreview[:200]
		}
		bannerPreview = strings.ReplaceAll(bannerPreview, "\n", " ")
		bannerPreview = strings.ReplaceAll(bannerPreview, "\r", "")

		val := portStr
		if svc.Product != "" {
			val += " (" + svc.Product + ")"
		}
		if bannerPreview != "" {
			val += " | " + bannerPreview
		}
		results = append(results, map[string]string{
			"data_type":  "port",
			"data_value": val,
		})
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 4: VirusTotal — PAID
// ---------------------------------------------------------------------------

func silentQueryVirusTotal(ctx context.Context, client *http.Client, domain string, cfg SilentAPIConfig) ([]map[string]string, error) {
	if cfg.VTKey == "" {
		return nil, fmt.Errorf("virustotal API key not configured")
	}

	apiURL := fmt.Sprintf("https://www.virustotal.com/api/v3/domains/%s/subdomains?limit=40", url.QueryEscape(domain))
	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("x-apikey", cfg.VTKey)
	req.Header.Set("User-Agent", UserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("virustotal request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 401 || resp.StatusCode == 403 {
		return nil, fmt.Errorf("virustotal: invalid or unauthorized API key")
	}
	if resp.StatusCode == 429 {
		return nil, fmt.Errorf("virustotal: rate limit exceeded")
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("virustotal returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var respData struct {
		Data []struct {
			ID string `json:"id"`
		} `json:"data"`
	}

	if err := json.Unmarshal(body, &respData); err != nil {
		return nil, fmt.Errorf("virustotal JSON parse error: %v", err)
	}

	var results []map[string]string
	for _, item := range respData.Data {
		if item.ID != "" {
			results = append(results, map[string]string{
				"data_type":  "subdomain",
				"data_value": item.ID,
			})
		}
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 5: SecurityTrails — PAID
// ---------------------------------------------------------------------------

func silentQuerySecurityTrails(ctx context.Context, client *http.Client, domain string, cfg SilentAPIConfig) ([]map[string]string, error) {
	if cfg.STKey == "" {
		return nil, fmt.Errorf("securitytrails API key not configured")
	}

	apiURL := fmt.Sprintf("https://api.securitytrails.com/v1/domain/%s/subdomains", url.QueryEscape(domain))
	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("APIKEY", cfg.STKey)
	req.Header.Set("User-Agent", UserAgent)
	req.Header.Set("Accept", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("securitytrails request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 401 || resp.StatusCode == 403 {
		return nil, fmt.Errorf("securitytrails: invalid API key")
	}
	if resp.StatusCode == 429 {
		return nil, fmt.Errorf("securitytrails: rate limit exceeded")
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("securitytrails returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var respData struct {
		Subdomains []string `json:"subdomains"`
	}

	if err := json.Unmarshal(body, &respData); err != nil {
		return nil, fmt.Errorf("securitytrails JSON parse error: %v", err)
	}

	var results []map[string]string
	for _, sub := range respData.Subdomains {
		fullSub := sub + "." + domain
		results = append(results, map[string]string{
			"data_type":  "subdomain",
			"data_value": fullSub,
		})
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 6: Hunter.io — PAID
// ---------------------------------------------------------------------------

func silentQueryHunterIO(ctx context.Context, client *http.Client, domain string, cfg SilentAPIConfig) ([]map[string]string, error) {
	if cfg.HunterKey == "" {
		return nil, fmt.Errorf("hunter.io API key not configured")
	}

	apiURL := fmt.Sprintf("https://api.hunter.io/v2/domain-search?domain=%s&api_key=%s&limit=100", url.QueryEscape(domain), url.QueryEscape(cfg.HunterKey))
	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("hunter.io request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 401 {
		return nil, fmt.Errorf("hunter.io: invalid API key")
	}
	if resp.StatusCode == 429 {
		return nil, fmt.Errorf("hunter.io: rate limit exceeded")
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("hunter.io returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var respData struct {
		Data struct {
			Emails []struct {
				Value     string `json:"value"`
				Type      string `json:"type"`
				FirstName string `json:"first_name"`
				LastName  string `json:"last_name"`
				Position  string `json:"position"`
			} `json:"emails"`
			Organization string `json:"organization"`
		} `json:"data"`
	}

	if err := json.Unmarshal(body, &respData); err != nil {
		return nil, fmt.Errorf("hunter.io JSON parse error: %v", err)
	}

	var results []map[string]string

	if respData.Data.Organization != "" {
		results = append(results, map[string]string{
			"data_type":  "org",
			"data_value": respData.Data.Organization,
		})
	}

	for _, email := range respData.Data.Emails {
		val := email.Value
		if email.FirstName != "" || email.LastName != "" {
			val += " (" + strings.TrimSpace(email.FirstName+" "+email.LastName) + ")"
		}
		if email.Position != "" {
			val += " [" + email.Position + "]"
		}
		results = append(results, map[string]string{
			"data_type":  "email",
			"data_value": val,
		})
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 7: BGPView — FREE
// ---------------------------------------------------------------------------

func silentQueryBGPView(ctx context.Context, client *http.Client, target string) ([]map[string]string, error) {
	var apiURL string
	if strings.HasPrefix(strings.ToUpper(target), "AS") {
		// ASN lookup
		asn := strings.TrimPrefix(strings.TrimPrefix(target, "AS"), "as")
		apiURL = fmt.Sprintf("https://api.bgpview.io/asn/%s/prefixes", url.QueryEscape(asn))
	} else if silentIsIP(target) {
		apiURL = fmt.Sprintf("https://api.bgpview.io/ip/%s", url.QueryEscape(target))
	} else {
		// Try to resolve domain to IP first
		ips, err := net.LookupHost(target)
		if err != nil || len(ips) == 0 {
			return nil, fmt.Errorf("bgpview: could not resolve %s", target)
		}
		apiURL = fmt.Sprintf("https://api.bgpview.io/ip/%s", url.QueryEscape(ips[0]))
	}

	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("bgpview request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("bgpview returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	// Parse as generic JSON since response varies by endpoint
	var generic map[string]interface{}
	if err := json.Unmarshal(body, &generic); err != nil {
		return nil, fmt.Errorf("bgpview JSON parse error: %v", err)
	}

	var results []map[string]string

	dataRaw, ok := generic["data"]
	if !ok {
		return results, nil
	}

	// IP endpoint returns data as an object
	if dataMap, ok := dataRaw.(map[string]interface{}); ok {
		// Extract PTR records
		if ptrs, ok := dataMap["ptr_record"].(string); ok && ptrs != "" {
			results = append(results, map[string]string{
				"data_type":  "ptr",
				"data_value": ptrs,
			})
		}

		// Extract prefixes
		if prefixes, ok := dataMap["prefixes"].([]interface{}); ok {
			for _, p := range prefixes {
				if pm, ok := p.(map[string]interface{}); ok {
					prefix, _ := pm["prefix"].(string)
					name, _ := pm["name"].(string)
					asn := ""
					if asnData, ok := pm["asn"].(map[string]interface{}); ok {
						asnNum, _ := asnData["asn"].(float64)
						asnDesc, _ := asnData["description"].(string)
						asn = fmt.Sprintf("AS%.0f", asnNum)
						if asnDesc != "" {
							asn += " (" + asnDesc + ")"
						}
					}
					val := prefix
					if name != "" {
						val += " [" + name + "]"
					}
					if asn != "" {
						val += " " + asn
					}
					results = append(results, map[string]string{
						"data_type":  "prefix",
						"data_value": val,
					})
				}
			}
		}

		// Extract RIR allocation
		if rir, ok := dataMap["rir_allocation"].(map[string]interface{}); ok {
			if rirName, ok := rir["rir_name"].(string); ok && rirName != "" {
				results = append(results, map[string]string{
					"data_type":  "rir",
					"data_value": rirName,
				})
			}
		}
	}

	// ASN prefix endpoint returns data as object with ipv4/ipv6 prefix arrays
	if dataMap, ok := dataRaw.(map[string]interface{}); ok {
		for _, key := range []string{"ipv4_prefixes", "ipv6_prefixes"} {
			if prefixes, ok := dataMap[key].([]interface{}); ok {
				for _, p := range prefixes {
					if pm, ok := p.(map[string]interface{}); ok {
						prefix, _ := pm["prefix"].(string)
						name, _ := pm["name"].(string)
						desc, _ := pm["description"].(string)
						val := prefix
						if name != "" {
							val += " [" + name + "]"
						}
						if desc != "" {
							val += " - " + desc
						}
						results = append(results, map[string]string{
							"data_type":  "prefix",
							"data_value": val,
						})
					}
				}
			}
		}
	}

	return results, nil
}

// ---------------------------------------------------------------------------
// Vendor 8: IPInfo — FREE (token optional, enhances results)
// ---------------------------------------------------------------------------

func silentQueryIPInfo(ctx context.Context, client *http.Client, target string, cfg SilentAPIConfig) ([]map[string]string, error) {
	queryTarget := target
	if !silentIsIP(target) {
		ips, err := net.LookupHost(target)
		if err != nil || len(ips) == 0 {
			return nil, fmt.Errorf("ipinfo: could not resolve %s", target)
		}
		queryTarget = ips[0]
	}

	apiURL := fmt.Sprintf("https://ipinfo.io/%s/json", url.QueryEscape(queryTarget))
	if cfg.IPInfoToken != "" {
		apiURL += "?token=" + url.QueryEscape(cfg.IPInfoToken)
	}

	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)
	req.Header.Set("Accept", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("ipinfo request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 429 {
		return nil, fmt.Errorf("ipinfo: rate limit exceeded")
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("ipinfo returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var ipData struct {
		IP       string `json:"ip"`
		Hostname string `json:"hostname"`
		City     string `json:"city"`
		Region   string `json:"region"`
		Country  string `json:"country"`
		Loc      string `json:"loc"`
		Org      string `json:"org"`
		Postal   string `json:"postal"`
		Timezone string `json:"timezone"`
	}

	if err := json.Unmarshal(body, &ipData); err != nil {
		return nil, fmt.Errorf("ipinfo JSON parse error: %v", err)
	}

	var results []map[string]string

	if ipData.IP != "" {
		results = append(results, map[string]string{
			"data_type":  "ip",
			"data_value": ipData.IP,
		})
	}
	if ipData.Hostname != "" {
		results = append(results, map[string]string{
			"data_type":  "hostname",
			"data_value": ipData.Hostname,
		})
	}
	if ipData.City != "" || ipData.Region != "" || ipData.Country != "" {
		geo := strings.Join(silentFilterEmpty(ipData.City, ipData.Region, ipData.Country), ", ")
		results = append(results, map[string]string{
			"data_type":  "geo",
			"data_value": geo,
		})
	}
	if ipData.Org != "" {
		results = append(results, map[string]string{
			"data_type":  "org",
			"data_value": ipData.Org,
		})
	}
	if ipData.Loc != "" {
		results = append(results, map[string]string{
			"data_type":  "coordinates",
			"data_value": ipData.Loc,
		})
	}
	if ipData.Timezone != "" {
		results = append(results, map[string]string{
			"data_type":  "timezone",
			"data_value": ipData.Timezone,
		})
	}

	return results, nil
}

func silentFilterEmpty(vals ...string) []string {
	var out []string
	for _, v := range vals {
		if v != "" {
			out = append(out, v)
		}
	}
	return out
}

// ---------------------------------------------------------------------------
// DB: store result
// ---------------------------------------------------------------------------

func (tui *TUI) silentStoreResult(target, vendor, dataType, dataValue, rawJSON string) {
	tui.db.conn.Exec(`
		INSERT OR IGNORE INTO silent_recon (target, vendor, data_type, data_value, raw_json, discovered)
		VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
		target, vendor, dataType, dataValue, rawJSON)
}

// ---------------------------------------------------------------------------
// Orchestration: run recon on a single target
// ---------------------------------------------------------------------------

func (tui *TUI) runSilentRecon(ctx context.Context, target string, vendorFilter string) {
	tui.startLog("silent-recon")
	defer tui.closeLog()

	cfg := silentLoadConfig()
	client := silentNewHTTPClient()

	target = strings.TrimSpace(target)
	target = strings.TrimPrefix(target, "http://")
	target = strings.TrimPrefix(target, "https://")
	target = strings.TrimRight(target, "/")

	tui.silentCurrentTarget = target
	tui.writeLog("TARGET: %s | FILTER: %s", target, vendorFilter)

	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("[*] === SILENT EYE RECON: %s ===", target))
	tui.addOutput(fmt.Sprintf("[*] Vendor filter: %s", vendorFilter))
	tui.addOutput("")
	tui.Render()

	// Determine target type
	targetType := "domain"
	if silentIsIP(target) {
		targetType = "ip"
	} else if strings.HasPrefix(strings.ToUpper(target), "AS") {
		targetType = "asn"
	}

	// Build vendor list based on filter
	type vendorFunc struct {
		name string
		fn   func() ([]map[string]string, error)
		free bool
	}

	var vendors []vendorFunc

	addVendor := func(name string, fn func() ([]map[string]string, error), free bool) {
		if vendorFilter == "all" || vendorFilter == name {
			vendors = append(vendors, vendorFunc{name: name, fn: fn, free: free})
		}
	}

	// Only add domain-relevant vendors for domain targets
	if targetType == "domain" {
		addVendor("crtsh", func() ([]map[string]string, error) {
			return silentQueryCrtSh(ctx, client, target)
		}, true)
		addVendor("censys", func() ([]map[string]string, error) {
			return silentQueryCensys(ctx, client, target, cfg)
		}, false)
		addVendor("virustotal", func() ([]map[string]string, error) {
			return silentQueryVirusTotal(ctx, client, target, cfg)
		}, false)
		addVendor("securitytrails", func() ([]map[string]string, error) {
			return silentQuerySecurityTrails(ctx, client, target, cfg)
		}, false)
		addVendor("hunter", func() ([]map[string]string, error) {
			return silentQueryHunterIO(ctx, client, target, cfg)
		}, false)
	}

	// Shodan works for both domain and IP
	addVendor("shodan", func() ([]map[string]string, error) {
		return silentQueryShodan(ctx, client, target, cfg)
	}, false)

	// IP/network vendors
	addVendor("bgpview", func() ([]map[string]string, error) {
		return silentQueryBGPView(ctx, client, target)
	}, true)

	addVendor("ipinfo", func() ([]map[string]string, error) {
		return silentQueryIPInfo(ctx, client, target, cfg)
	}, true)

	atomic.StoreInt64(&tui.silentTotal, int64(len(vendors)))
	atomic.StoreInt64(&tui.silentProgress, 0)

	totalFindings := 0
	var vendorsRun []string

	for _, v := range vendors {
		if ctx.Err() != nil {
			tui.addOutput("[!] Recon cancelled by user")
			tui.writeLog("CANCELLED by user")
			break
		}

		freeTag := ""
		if v.free {
			freeTag = " (free)"
		}
		tui.addOutput(fmt.Sprintf("[*] Querying %s%s...", v.name, freeTag))
		tui.writeLog("VENDOR: %s", v.name)
		tui.Render()

		results, err := v.fn()
		atomic.AddInt64(&tui.silentProgress, 1)

		if err != nil {
			tui.addOutput(fmt.Sprintf("[-] %s: %v", v.name, err))
			tui.writeLog("ERROR: %s: %v", v.name, err)
			continue
		}

		if len(results) == 0 {
			tui.addOutput(fmt.Sprintf("[*] %s: no results", v.name))
			tui.writeLog("%s: 0 results", v.name)
			continue
		}

		tui.addOutput(fmt.Sprintf("[+] %s: %d findings", v.name, len(results)))
		tui.writeLog("%s: %d findings", v.name, len(results))

		for _, r := range results {
			dataType := r["data_type"]
			dataValue := r["data_value"]
			rawJSON := ""
			if rj, ok := r["raw_json"]; ok {
				rawJSON = rj
			}

			tui.silentStoreResult(target, v.name, dataType, dataValue, rawJSON)
			totalFindings++

			// Show findings in output (limit display to first 25 per vendor)
			if totalFindings <= 500 {
				tui.addOutput(fmt.Sprintf("    [%s] %s", dataType, dataValue))
			}
		}
		if len(results) > 25 {
			tui.addOutput(fmt.Sprintf("    ... and %d more stored in DB", len(results)-25))
		}

		vendorsRun = append(vendorsRun, v.name)
		tui.silentVendorsRun = strings.Join(vendorsRun, ",")
		tui.Render()
	}

	// Update target record
	tui.db.conn.Exec(`
		INSERT INTO silent_targets (target, target_type, vendors_queried, total_findings, first_scanned, last_scanned)
		VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
		ON CONFLICT(target) DO UPDATE SET
			vendors_queried = excluded.vendors_queried,
			total_findings = total_findings + excluded.total_findings,
			last_scanned = CURRENT_TIMESTAMP`,
		target, targetType, strings.Join(vendorsRun, ","), totalFindings)

	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("[+] === RECON COMPLETE: %s ===", target))
	tui.addOutput(fmt.Sprintf("[+] Vendors queried: %d | Total findings: %d", len(vendorsRun), totalFindings))
	tui.addOutput(fmt.Sprintf("[+] Vendors: %s", strings.Join(vendorsRun, ", ")))
	tui.writeLog("COMPLETE: %d vendors, %d findings", len(vendorsRun), totalFindings)
	tui.Render()
}

// ---------------------------------------------------------------------------
// Command handler
// ---------------------------------------------------------------------------

func (tui *TUI) startSilentCommand(cmdKey string) {
	tui.currentCmd = cmdKey
	tui.collectedInputs = make(map[string]string)

	switch cmdKey {
	case "1": // Full recon (all vendors)
		tui.inputFields = []string{"target"}
		tui.addOutput("[*] FULL RECON -- Enter domain, IP, or ASN")
		tui.addOutput("[*] All configured vendors will be queried")
		tui.inputPrompt = "Enter target"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.commandState = StateInput
		return

	case "2": // crt.sh only
		tui.inputFields = []string{"target"}
		tui.addOutput("[*] CRT.SH -- Certificate Transparency lookup (free, no key needed)")
		tui.inputPrompt = "Enter domain"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.commandState = StateInput
		return

	case "3": // Shodan
		cfg := silentLoadConfig()
		if cfg.ShodanKey == "" {
			tui.addOutput("[-] Shodan API key not configured. Use option 6 to set keys.")
			return
		}
		tui.inputFields = []string{"target"}
		tui.addOutput("[*] SHODAN -- Host intelligence lookup (paid)")
		tui.inputPrompt = "Enter domain or IP"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.commandState = StateInput
		return

	case "4": // VirusTotal
		cfg := silentLoadConfig()
		if cfg.VTKey == "" {
			tui.addOutput("[-] VirusTotal API key not configured. Use option 6 to set keys.")
			return
		}
		tui.inputFields = []string{"target"}
		tui.addOutput("[*] VIRUSTOTAL -- Subdomain enumeration (paid)")
		tui.inputPrompt = "Enter domain"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.commandState = StateInput
		return

	case "5": // IPInfo
		tui.inputFields = []string{"target"}
		tui.addOutput("[*] IPINFO -- IP geolocation & ASN lookup (free)")
		tui.inputPrompt = "Enter domain or IP"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.commandState = StateInput
		return

	case "6": // Config wizard
		tui.silentConfigWizard()
		return

	case "7": // Stats
		tui.showSilentStats()
		return

	case "8": // Export
		tui.exportSilentResults()
		return

	case "9": // Clear
		tui.clearOutput()
		tui.addOutput("[*] Output cleared")
		return

	case "Q", "q":
		tui.running = false
		return
	}
}

// ---------------------------------------------------------------------------
// Execute command (called from executeCommand() after input collection)
// ---------------------------------------------------------------------------

func (tui *TUI) executeSilentCommand() {
	// Handle config sub-commands
	if strings.HasPrefix(tui.currentCmd, "config_") && tui.currentCmd != "config" {
		value := tui.collectedInputs["key_value"]
		tui.silentSaveKeyValue(tui.currentCmd, value)
		return
	}
	if tui.currentCmd == "config" {
		input := tui.collectedInputs["config_input"]
		tui.silentApplyConfigInput(input)
		return
	}

	// Standard target-based commands
	target := tui.collectedInputs["target"]
	target = strings.TrimSpace(target)
	if target == "" {
		tui.addOutput("[-] No target provided")
		tui.commandState = StateMenu
		return
	}

	var vendorFilter string
	switch tui.currentCmd {
	case "1":
		vendorFilter = "all"
	case "2":
		vendorFilter = "crtsh"
	case "3":
		vendorFilter = "shodan"
	case "4":
		vendorFilter = "virustotal"
	case "5":
		vendorFilter = "ipinfo"
	default:
		vendorFilter = "all"
	}

	tui.commandState = StateRunning
	tui.Render()

	ctx, cancel := context.WithCancel(context.Background())
	tui.cancelScan = cancel

	go func() {
		defer func() { tui.cancelScan = nil }()
		tui.runSilentRecon(ctx, target, vendorFilter)
		if ctx.Err() == nil {
			tui.commandState = StateComplete
		}
		tui.Render()
	}()
}

// silentHandleInput is a convenience wrapper for direct input handling.
func (tui *TUI) silentHandleInput(input string) {
	tui.collectedInputs["target"] = input
	tui.executeSilentCommand()
}

// ---------------------------------------------------------------------------
// Config wizard: display and update API keys
// ---------------------------------------------------------------------------

func (tui *TUI) silentConfigWizard() {
	cfg := silentLoadConfig()

	tui.addOutput("")
	tui.addOutput("[*] === SILENT EYE API KEY CONFIGURATION ===")
	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("  1. Censys ID:         %s", silentMaskKey(cfg.CensysID)))
	tui.addOutput(fmt.Sprintf("  2. Censys Secret:     %s", silentMaskKey(cfg.CensysSecret)))
	tui.addOutput(fmt.Sprintf("  3. Shodan Key:        %s", silentMaskKey(cfg.ShodanKey)))
	tui.addOutput(fmt.Sprintf("  4. VirusTotal Key:    %s", silentMaskKey(cfg.VTKey)))
	tui.addOutput(fmt.Sprintf("  5. SecurityTrails:    %s", silentMaskKey(cfg.STKey)))
	tui.addOutput(fmt.Sprintf("  6. Hunter.io Key:     %s", silentMaskKey(cfg.HunterKey)))
	tui.addOutput(fmt.Sprintf("  7. IPInfo Token:      %s", silentMaskKey(cfg.IPInfoToken)))
	tui.addOutput("")
	tui.addOutput("[*] Free vendors (no key needed): crt.sh, BGPView, IPInfo (basic)")
	tui.addOutput("[*] Enter key number to update, or paste all keys as JSON")
	tui.addOutput("")

	tui.inputFields = []string{"config_input"}
	tui.inputPrompt = "Key # to update (1-7) or 'json' to paste full config"
	tui.currentField = 0
	tui.inputBuffer = ""
	tui.inputCursor = 0
	tui.currentCmd = "config"
	tui.commandState = StateInput
}

func (tui *TUI) silentApplyConfigInput(input string) {
	input = strings.TrimSpace(input)

	// Check if user pasted JSON directly
	if strings.HasPrefix(input, "{") {
		var newCfg SilentAPIConfig
		if err := json.Unmarshal([]byte(input), &newCfg); err != nil {
			tui.addOutput(fmt.Sprintf("[-] Invalid JSON: %v", err))
			tui.commandState = StateMenu
			return
		}
		if err := silentSaveConfig(newCfg); err != nil {
			tui.addOutput(fmt.Sprintf("[-] Failed to save config: %v", err))
		} else {
			tui.addOutput("[+] All API keys updated from JSON")
		}
		tui.commandState = StateMenu
		return
	}

	// Check for key number selection
	switch input {
	case "1":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter Censys API ID"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_censys_id"
		tui.commandState = StateInput
		return
	case "2":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter Censys API Secret"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_censys_secret"
		tui.commandState = StateInput
		return
	case "3":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter Shodan API Key"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_shodan"
		tui.commandState = StateInput
		return
	case "4":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter VirusTotal API Key"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_vt"
		tui.commandState = StateInput
		return
	case "5":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter SecurityTrails API Key"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_st"
		tui.commandState = StateInput
		return
	case "6":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter Hunter.io API Key"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_hunter"
		tui.commandState = StateInput
		return
	case "7":
		tui.inputFields = []string{"key_value"}
		tui.inputPrompt = "Enter IPInfo Token"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.currentCmd = "config_ipinfo"
		tui.commandState = StateInput
		return
	default:
		tui.addOutput("[-] Invalid selection. Enter 1-7 or paste JSON.")
		tui.commandState = StateMenu
		return
	}
}

func (tui *TUI) silentSaveKeyValue(configKey, value string) {
	cfg := silentLoadConfig()
	value = strings.TrimSpace(value)

	switch configKey {
	case "config_censys_id":
		cfg.CensysID = value
		tui.addOutput("[+] Censys API ID updated")
	case "config_censys_secret":
		cfg.CensysSecret = value
		tui.addOutput("[+] Censys API Secret updated")
	case "config_shodan":
		cfg.ShodanKey = value
		tui.addOutput("[+] Shodan API Key updated")
	case "config_vt":
		cfg.VTKey = value
		tui.addOutput("[+] VirusTotal API Key updated")
	case "config_st":
		cfg.STKey = value
		tui.addOutput("[+] SecurityTrails API Key updated")
	case "config_hunter":
		cfg.HunterKey = value
		tui.addOutput("[+] Hunter.io API Key updated")
	case "config_ipinfo":
		cfg.IPInfoToken = value
		tui.addOutput("[+] IPInfo Token updated")
	}

	if err := silentSaveConfig(cfg); err != nil {
		tui.addOutput(fmt.Sprintf("[-] Failed to save config: %v", err))
	} else {
		tui.addOutput(fmt.Sprintf("[+] Config saved to %s", silentConfigPath()))
	}
	tui.commandState = StateMenu
}

// ---------------------------------------------------------------------------
// Stats
// ---------------------------------------------------------------------------

func (tui *TUI) showSilentStats() {
	tui.addOutput("")
	tui.addOutput("[*] === SILENT EYE DATABASE STATISTICS ===")
	tui.addOutput("")

	var totalTargets int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM silent_targets").Scan(&totalTargets)
	tui.addOutput(fmt.Sprintf("  Targets scanned: %d", totalTargets))

	var totalFindings int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM silent_recon").Scan(&totalFindings)
	tui.addOutput(fmt.Sprintf("  Total findings:  %d", totalFindings))

	tui.addOutput("")
	tui.addOutput("  --- Findings by Vendor ---")

	rows, err := tui.db.conn.Query("SELECT vendor, COUNT(*) as cnt FROM silent_recon GROUP BY vendor ORDER BY cnt DESC")
	if err == nil {
		defer rows.Close()
		for rows.Next() {
			var vendor string
			var cnt int
			rows.Scan(&vendor, &cnt)
			tui.addOutput(fmt.Sprintf("    %-20s %d findings", vendor, cnt))
		}
	}

	tui.addOutput("")
	tui.addOutput("  --- Findings by Data Type ---")

	rows2, err := tui.db.conn.Query("SELECT data_type, COUNT(*) as cnt FROM silent_recon GROUP BY data_type ORDER BY cnt DESC")
	if err == nil {
		defer rows2.Close()
		for rows2.Next() {
			var dataType string
			var cnt int
			rows2.Scan(&dataType, &cnt)
			tui.addOutput(fmt.Sprintf("    %-20s %d", dataType, cnt))
		}
	}

	tui.addOutput("")
	tui.addOutput("  --- Recent Targets ---")

	rows3, err := tui.db.conn.Query("SELECT target, target_type, vendors_queried, total_findings, last_scanned FROM silent_targets ORDER BY last_scanned DESC LIMIT 10")
	if err == nil {
		defer rows3.Close()
		for rows3.Next() {
			var target, targetType, vendors, lastScanned string
			var findings int
			rows3.Scan(&target, &targetType, &vendors, &findings, &lastScanned)
			tui.addOutput(fmt.Sprintf("    %s (%s) | %d findings | vendors: %s | %s", target, targetType, findings, vendors, lastScanned))
		}
	}

	// Show configured keys status
	cfg := silentLoadConfig()
	tui.addOutput("")
	tui.addOutput("  --- API Key Status ---")
	tui.addOutput(fmt.Sprintf("    Censys:         %s", silentKeyStatus(cfg.CensysID != "" && cfg.CensysSecret != "")))
	tui.addOutput(fmt.Sprintf("    Shodan:         %s", silentKeyStatus(cfg.ShodanKey != "")))
	tui.addOutput(fmt.Sprintf("    VirusTotal:     %s", silentKeyStatus(cfg.VTKey != "")))
	tui.addOutput(fmt.Sprintf("    SecurityTrails: %s", silentKeyStatus(cfg.STKey != "")))
	tui.addOutput(fmt.Sprintf("    Hunter.io:      %s", silentKeyStatus(cfg.HunterKey != "")))
	tui.addOutput(fmt.Sprintf("    IPInfo:         %s", silentKeyStatus(cfg.IPInfoToken != "")))
	tui.addOutput(fmt.Sprintf("    crt.sh:         CONFIGURED (free, no key)"))
	tui.addOutput(fmt.Sprintf("    BGPView:        CONFIGURED (free, no key)"))

	tui.addOutput("")
}

func silentKeyStatus(configured bool) string {
	if configured {
		return "CONFIGURED"
	}
	return "NOT SET"
}

// ---------------------------------------------------------------------------
// CSV Export
// ---------------------------------------------------------------------------

func (tui *TUI) exportSilentResults() {
	var totalFindings int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM silent_recon").Scan(&totalFindings)

	if totalFindings == 0 {
		tui.addOutput("[-] No findings to export")
		return
	}

	filename := filepath.Join(tui.logsDir, fmt.Sprintf("silent_eye_export_%s.csv", time.Now().Format("2006-01-02_150405")))
	file, err := os.Create(filename)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error creating export file: %v", err))
		return
	}
	defer file.Close()

	// Write CSV header
	file.WriteString("target,vendor,data_type,data_value,discovered\n")

	rows, err := tui.db.conn.Query("SELECT target, vendor, data_type, data_value, discovered FROM silent_recon ORDER BY target, vendor, data_type")
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error querying findings: %v", err))
		return
	}
	defer rows.Close()

	count := 0
	for rows.Next() {
		var target, vendor, dataType, dataValue, discovered string
		rows.Scan(&target, &vendor, &dataType, &dataValue, &discovered)

		// Escape CSV fields that might contain commas or quotes
		escapedValue := silentCSVEscape(dataValue)
		file.WriteString(fmt.Sprintf("%s,%s,%s,%s,%s\n",
			silentCSVEscape(target),
			silentCSVEscape(vendor),
			silentCSVEscape(dataType),
			escapedValue,
			silentCSVEscape(discovered)))
		count++
	}

	tui.addOutput(fmt.Sprintf("[+] Exported %d findings to %s", count, filename))

	// Also export targets summary
	summaryFile := filepath.Join(tui.logsDir, fmt.Sprintf("silent_eye_targets_%s.csv", time.Now().Format("2006-01-02_150405")))
	sf, err := os.Create(summaryFile)
	if err == nil {
		defer sf.Close()
		sf.WriteString("target,target_type,vendors_queried,total_findings,first_scanned,last_scanned\n")
		rows2, err := tui.db.conn.Query("SELECT target, target_type, vendors_queried, total_findings, first_scanned, last_scanned FROM silent_targets ORDER BY last_scanned DESC")
		if err == nil {
			defer rows2.Close()
			tCount := 0
			for rows2.Next() {
				var target, targetType, vendors, firstScanned, lastScanned string
				var findings int
				rows2.Scan(&target, &targetType, &vendors, &findings, &firstScanned, &lastScanned)
				sf.WriteString(fmt.Sprintf("%s,%s,%s,%d,%s,%s\n",
					silentCSVEscape(target),
					silentCSVEscape(targetType),
					silentCSVEscape(vendors),
					findings,
					silentCSVEscape(firstScanned),
					silentCSVEscape(lastScanned)))
				tCount++
			}
			tui.addOutput(fmt.Sprintf("[+] Exported %d targets to %s", tCount, summaryFile))
		}
	}
}

func silentCSVEscape(s string) string {
	if strings.ContainsAny(s, ",\"\n\r") {
		return "\"" + strings.ReplaceAll(s, "\"", "\"\"") + "\""
	}
	return s
}

// ---------------------------------------------------------------------------
// Dashboard renderer
// ---------------------------------------------------------------------------

func (tui *TUI) renderSilentEyeDashboard() {
	modColor := moduleColors[ModuleSilentEye]
	borderStyle := tcell.StyleDefault.Foreground(modColor)
	titleStyle := tcell.StyleDefault.Foreground(ColorPrimary).Bold(true)
	textStyle := tcell.StyleDefault.Foreground(ColorText)
	highlightStyle := tcell.StyleDefault.Foreground(modColor)
	successStyle := tcell.StyleDefault.Foreground(ColorSuccess)
	dimStyle := tcell.StyleDefault.Foreground(ColorDim)

	menuWidth := 52
	outputX := menuWidth + 1
	outputWidth := tui.width - menuWidth - 2
	inputHeight := 3
	outputHeight := tui.height - inputHeight - 2

	// Header
	header := fmt.Sprintf(" MOTHERFUCKIN TERMINATOR BOT 9000 v%s | MODULE: Silent Eye ", Version)
	tui.drawString(1, 0, header, titleStyle)

	// DB stats in header
	var dbTargets, dbFindings int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM silent_targets").Scan(&dbTargets)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM silent_recon").Scan(&dbFindings)
	statsStr := fmt.Sprintf("Targets: %d | Findings: %d ", dbTargets, dbFindings)
	tui.drawString(tui.width-len(statsStr)-1, 0, statsStr, dimStyle)

	// Menu box
	tui.drawBox(0, 1, menuWidth, tui.height-inputHeight-1, "SILENT EYE COMMANDS", borderStyle)

	menuY := 3
	items := tui.menuItems
	for i, item := range items {
		y := menuY + i
		if y >= tui.height-inputHeight-2 {
			break
		}

		keyStyle := highlightStyle
		nameStyle := textStyle
		if i == tui.selectedItem && !tui.focusOutput {
			tui.fillRect(1, y, menuWidth-2, 1, ' ', tcell.StyleDefault.Background(ColorBorder))
			keyStyle = successStyle.Bold(true).Background(ColorBorder)
			nameStyle = tcell.StyleDefault.Foreground(ColorText).Bold(true).Background(ColorBorder)
		}

		tui.drawString(2, y, fmt.Sprintf("[%s]", item.Key), keyStyle)
		tui.drawStringClipped(6, y, menuWidth-8, item.Name+" - "+item.Desc, nameStyle)
	}

	// Vendor info panel
	infoY := menuY + len(items) + 1
	tui.drawString(2, infoY, "--- 8 Recon Vendors ---", dimStyle)
	vendors := []string{
		"crt.sh         FREE  Cert Transparency",
		"Censys         PAID  Host/cert search",
		"Shodan         PAID  Ports/banners/OS",
		"VirusTotal     PAID  Subdomain enum",
		"SecurityTrails PAID  Subdomain history",
		"Hunter.io      PAID  Email discovery",
		"BGPView        FREE  ASN/prefix data",
		"IPInfo         FREE  IP geolocation",
	}
	for i, v := range vendors {
		if infoY+1+i < tui.height-inputHeight-2 {
			vStyle := tcell.StyleDefault.Foreground(ColorInfo)
			if strings.Contains(v, "FREE") {
				vStyle = tcell.StyleDefault.Foreground(ColorSuccess)
			}
			tui.drawString(2, infoY+1+i, " "+v, vStyle)
		}
	}

	// Shortcuts
	shortcutY := infoY + len(vendors) + 2
	if shortcutY < tui.height-inputHeight-2 {
		tui.drawString(2, shortcutY, "--- Shortcuts ---", dimStyle)
		if shortcutY+1 < tui.height-inputHeight-2 {
			tui.drawString(2, shortcutY+1, "F1: Settings  Tab: Module Switch", tcell.StyleDefault.Foreground(ColorInfo))
		}
		if shortcutY+2 < tui.height-inputHeight-2 {
			tui.drawString(2, shortcutY+2, "ESC: Cancel / Back", tcell.StyleDefault.Foreground(ColorInfo))
		}
		if shortcutY+3 < tui.height-inputHeight-2 {
			tui.drawString(2, shortcutY+3, "PgUp/PgDn: Scroll", tcell.StyleDefault.Foreground(ColorInfo))
		}
	}

	// Output box
	outputTitle := "OUTPUT"
	outputBorder := borderStyle
	if tui.focusOutput {
		outputTitle = "OUTPUT [FOCUSED]"
		outputBorder = tcell.StyleDefault.Foreground(ColorAccent)
	}
	tui.drawBox(outputX, 1, outputWidth, outputHeight, outputTitle, outputBorder)

	tui.outputMutex.Lock()
	maxLines := outputHeight - 2
	startLine := tui.outputScroll
	for i := 0; i < maxLines && startLine+i < len(tui.outputLines); i++ {
		line := tui.outputLines[startLine+i]
		y := 2 + i

		lineStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite)
		if strings.HasPrefix(line, "[+]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorSuccess)
		} else if strings.HasPrefix(line, "[-]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorDanger)
		} else if strings.HasPrefix(line, "[ERR]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorWarning)
		} else if strings.HasPrefix(line, "[*]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorInfo)
		} else if strings.HasPrefix(line, "[!]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorSecondary)
		} else if strings.HasPrefix(line, "    [") {
			// Indented findings get cyan color
			lineStyle = tcell.StyleDefault.Foreground(modColor)
		}

		tui.drawStringClipped(outputX+1, y, outputWidth-2, line, lineStyle)
	}

	if len(tui.outputLines) > maxLines {
		scrollInfo := fmt.Sprintf(" %d/%d ", tui.outputScroll+maxLines, len(tui.outputLines))
		tui.drawString(outputX+outputWidth-len(scrollInfo)-1, 1, scrollInfo, dimStyle)
	}
	tui.outputMutex.Unlock()

	// Input box
	inputY := tui.height - inputHeight - 1
	tui.drawBox(0, inputY, tui.width, inputHeight+1, "INPUT", borderStyle)

	promptStyle := tcell.StyleDefault.Foreground(ColorWarning)
	inputStyle := tcell.StyleDefault.Foreground(ColorText)

	if tui.commandState == StateInput {
		prompt := tui.inputPrompt + ": "
		tui.drawString(2, inputY+1, prompt, promptStyle)
		tui.drawString(2+len(prompt), inputY+1, tui.inputBuffer, inputStyle)
		cursorX := 2 + len(prompt) + tui.inputCursor
		if cursorX < tui.width-2 {
			tui.screen.SetContent(cursorX, inputY+1, '\u258C', nil, tcell.StyleDefault.Foreground(ColorPrimary))
		}
	} else if tui.commandState == StateRunning {
		progress := atomic.LoadInt64(&tui.silentProgress)
		total := atomic.LoadInt64(&tui.silentTotal)
		target := tui.silentCurrentTarget
		runMsg := fmt.Sprintf("Scanning %s... [%d/%d vendors] press ESC to cancel", target, progress, total)
		tui.drawString(2, inputY+1, runMsg, tcell.StyleDefault.Foreground(ColorWarning))
	} else if tui.commandState == StateComplete {
		tui.drawString(2, inputY+1, "Complete! Press any key to continue", successStyle)
	} else {
		hint := "Tab: Module | Arrows: Navigate | Enter: Select | Q: Quit"
		tui.drawStringClipped(2, inputY+1, tui.width-4, hint, dimStyle)
	}

	// Status bar
	statusY := tui.height - 1
	statusStyle := tcell.StyleDefault.Foreground(tcell.ColorBlack).Background(modColor)
	tui.fillRect(0, statusY, tui.width, 1, ' ', statusStyle)

	progress := atomic.LoadInt64(&tui.silentProgress)
	total := atomic.LoadInt64(&tui.silentTotal)
	vendorsRun := tui.silentVendorsRun
	if vendorsRun == "" {
		vendorsRun = "none"
	}
	status := fmt.Sprintf(" Silent Eye | Targets: %d | Findings: %d | Progress: %d/%d | Vendors: %s ",
		dbTargets, dbFindings, progress, total, vendorsRun)
	tui.drawString(0, statusY, status, statusStyle)
}

// ---------------------------------------------------------------------------
// Bulk scan from file — called from handleFilePickerInput
// ---------------------------------------------------------------------------

func (tui *TUI) runSilentBulkFromFile(ctx context.Context, filePath string) {
	data, err := os.ReadFile(filePath)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error reading file: %v", err))
		return
	}
	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
	var targets []string
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line != "" && !strings.HasPrefix(line, "#") {
			targets = append(targets, line)
		}
	}
	if len(targets) == 0 {
		tui.addOutput("[-] No targets found in file")
		return
	}
	tui.addOutput(fmt.Sprintf("[*] Loaded %d targets from %s", len(targets), filepath.Base(filePath)))
	atomic.StoreInt64(&tui.silentTotal, int64(len(targets)))
	for i, target := range targets {
		if ctx.Err() != nil {
			break
		}
		atomic.StoreInt64(&tui.silentProgress, int64(i+1))
		tui.runSilentRecon(ctx, target, "all")
	}
}
