package agentd

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
)

// logf is the agent's structured-ish logger. It deliberately never logs
// dp_uuids, peers' addresses or any payload — only operational events — to honour
// the no-log policy.
func logf(format string, args ...any) {
	log.Printf("[pangolin-agent] "+format, args...)
}

// atomicWrite writes data to a temp file in the same directory and renames it into
// place, so a crash mid-write never leaves a truncated config/state file.
func atomicWrite(path string, data []byte, perm os.FileMode) error {
	dir := filepath.Dir(path)
	tmp, err := os.CreateTemp(dir, ".tmp-*")
	if err != nil {
		return fmt.Errorf("agentd: temp file: %w", err)
	}
	tmpName := tmp.Name()
	defer os.Remove(tmpName) // no-op if the rename succeeded
	if _, err := tmp.Write(data); err != nil {
		tmp.Close()
		return fmt.Errorf("agentd: write temp: %w", err)
	}
	if err := tmp.Chmod(perm); err != nil {
		tmp.Close()
		return fmt.Errorf("agentd: chmod temp: %w", err)
	}
	if err := tmp.Close(); err != nil {
		return fmt.Errorf("agentd: close temp: %w", err)
	}
	if err := os.Rename(tmpName, path); err != nil {
		return fmt.Errorf("agentd: rename temp: %w", err)
	}
	return nil
}

// SystemdRestarter restarts the sing-box unit via `systemctl restart`.
type SystemdRestarter struct {
	Unit string // e.g. "sing-box"
}

// Restart runs `systemctl restart <unit>` (cold start: ~1-2s data-plane blip).
func (r SystemdRestarter) Restart(ctx context.Context) error {
	unit := r.unit()
	cmd := exec.CommandContext(ctx, "systemctl", "restart", unit)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("agentd: restart %s: %w: %s", unit, err, out)
	}
	return nil
}

// Reload hot-reloads sing-box via SIGHUP to its MainPID. sing-box validates the
// new config first and keeps the OLD instance running on error (no full
// process restart). agent 与 sing-box 同 pangolin 用户,直接发信号即可(免
// polkit/不改 unit)。PID 取不到或发信号失败时回退到 Restart(冷启动)。
func (r SystemdRestarter) Reload(ctx context.Context) error {
	unit := r.unit()
	pid, err := singboxMainPID(ctx, unit)
	if err != nil || pid <= 0 {
		return r.Restart(ctx)
	}
	if err := syscall.Kill(pid, syscall.SIGHUP); err != nil {
		return r.Restart(ctx)
	}
	return nil
}

func (r SystemdRestarter) unit() string {
	if r.Unit == "" {
		return "sing-box"
	}
	return r.Unit
}

// singboxMainPID 读 systemd 记录的主进程 PID(0 表示未在跑)。
func singboxMainPID(ctx context.Context, unit string) (int, error) {
	out, err := exec.CommandContext(ctx, "systemctl", "show", "-p", "MainPID", "--value", unit).Output()
	if err != nil {
		return 0, err
	}
	return strconv.Atoi(strings.TrimSpace(string(out)))
}
