package proxmox

import (
	"context"
	"encoding/json"
	"fmt"
	"os/exec"
	"strconv"
)

// The Privileged backend is fenced to the THREE proven OS-root exceptions only
// (phase3 §B3 boundary, doc.go routing table):
//
//	(a) keyctl `pct create` for golden-image builds,
//	(b) USB mount-by-UUID / fstab,
//	(c) SMART / sensors reads.
//
// It runs host commands through a Runner (direct exec or sudo). It makes NO HTTP
// call — the fence between API ops and root ops is structural: Client owns the
// API, Privileged owns the shell. routing_test.go asserts neither crosses over.
//
// Everything else — the entire guest lifecycle including restore — goes through
// the API Client. Do NOT add non-exception methods here.

// Runner executes a host command and returns its stdout/stderr. *ExecRunner is the
// production implementation; tests inject a mock to assert which commands ran.
type Runner interface {
	Run(ctx context.Context, name string, args ...string) (stdout, stderr []byte, err error)
}

// RunnerMode selects how privileged commands are executed.
type RunnerMode string

const (
	// RunnerDirect: exec the binary directly (agent already runs as root — not the
	// recommended uid model, see README; useful in dev/CI).
	RunnerDirect RunnerMode = "direct"
	// RunnerSudo: prefix with sudo (the intended model — agent runs as a non-root
	// service user with a narrow sudoers allowlist, 03 §3/§12).
	RunnerSudo RunnerMode = "sudo"
)

// ExecRunner runs commands via os/exec, optionally through sudo.
type ExecRunner struct {
	Mode     RunnerMode
	SudoPath string // defaults to "sudo" when Mode == RunnerSudo
}

// Run implements Runner.
func (r *ExecRunner) Run(ctx context.Context, name string, args ...string) ([]byte, []byte, error) {
	var cmd *exec.Cmd
	if r.Mode == RunnerSudo {
		sudo := r.SudoPath
		if sudo == "" {
			sudo = "sudo"
		}
		cmd = exec.CommandContext(ctx, sudo, append([]string{"-n", name}, args...)...)
	} else {
		cmd = exec.CommandContext(ctx, name, args...)
	}
	var stdout, stderr capBuf
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	return stdout.b, stderr.b, err
}

// Privileged is the root-CLI backend.
type Privileged struct {
	runner Runner
	node   string
}

// NewPrivileged builds the fenced root backend.
func NewPrivileged(runner Runner, node string) *Privileged {
	return &Privileged{runner: runner, node: node}
}

// GoldenLXCSpec describes a golden-base CT to build fresh.
type GoldenLXCSpec struct {
	VMID       int
	OSTemplate string // CT template volid, e.g. "local:vztmpl/debian-13-standard_..._amd64.tar.zst"
	Storage    string // rootfs storage, e.g. "local-lvm"
	RootFSGB   int
	Cores      int
	MemoryMB   int
	Hostname   string
	// Features is forced to "nesting=1,keyctl=1" — keyctl is exactly why this is
	// root-fenced.
}

// CreateGoldenLXC builds a Docker-capable golden base CT with keyctl=1.
//
// WHY THIS CANNOT BE THE API: setting feature flags other than `nesting` on
// create is `root@pam`-only — `changing feature flags (except nesting) is only
// allowed for root@pam`. No API token qualifies, not even a non-privsep root@pam
// token (same 403). This is the ONLY root-fenced create; the per-customer path
// provisions by restore, which preserves keyctl with no root (phase3 §B3).
//
// This is a one-time/maintenance op at enrollment (03 §9), off the per-customer path.
func (p *Privileged) CreateGoldenLXC(ctx context.Context, spec GoldenLXCSpec) error {
	if spec.VMID == 0 || spec.OSTemplate == "" || spec.Storage == "" {
		return fmt.Errorf("proxmox: CreateGoldenLXC needs vmid, ostemplate and storage")
	}
	rootfs := spec.Storage
	if spec.RootFSGB > 0 {
		rootfs = fmt.Sprintf("%s:%d", spec.Storage, spec.RootFSGB)
	}
	args := []string{
		"create", strconv.Itoa(spec.VMID), spec.OSTemplate,
		"--unprivileged", "1",
		"--features", "nesting=1,keyctl=1",
		"--rootfs", rootfs,
	}
	if spec.Cores > 0 {
		args = append(args, "--cores", strconv.Itoa(spec.Cores))
	}
	if spec.MemoryMB > 0 {
		args = append(args, "--memory", strconv.Itoa(spec.MemoryMB))
	}
	if spec.Hostname != "" {
		args = append(args, "--hostname", spec.Hostname)
	}
	return p.run(ctx, "pct", args...)
}

// MountUSBByUUID mounts a filesystem by UUID at target (creating the mountpoint).
//
// WHY THIS CANNOT BE THE API: a physical host mount is not a Proxmox API op; it is
// a host-level mount handled by OS root / a narrow sudoers entry (phase3 §B3).
// fstab persistence is a later-slice concern (03 §7 storage manifest).
func (p *Privileged) MountUSBByUUID(ctx context.Context, uuid, target string) error {
	if uuid == "" || target == "" {
		return fmt.Errorf("proxmox: MountUSBByUUID needs uuid and target")
	}
	if err := p.run(ctx, "mkdir", "-p", target); err != nil {
		return err
	}
	return p.run(ctx, "mount", "UUID="+uuid, target)
}

// SMART returns parsed `smartctl -a -j` JSON for a device.
//
// WHY THIS CANNOT BE THE API: disk SMART data is not exposed by the Proxmox API;
// it is read with OS root via smartctl (phase3 §B3).
func (p *Privileged) SMART(ctx context.Context, device string) (map[string]any, error) {
	if device == "" {
		return nil, fmt.Errorf("proxmox: SMART needs a device")
	}
	out, stderr, err := p.runner.Run(ctx, "smartctl", "-a", "-j", device)
	if err != nil {
		// smartctl uses nonzero exit codes as bitmask warnings even on success;
		// trust parseable JSON output over the exit code.
		if len(out) == 0 {
			return nil, fmt.Errorf("proxmox: smartctl %s: %w: %s", device, err, stderr)
		}
	}
	var m map[string]any
	if err := json.Unmarshal(out, &m); err != nil {
		return nil, fmt.Errorf("proxmox: parsing smartctl JSON: %w", err)
	}
	return m, nil
}

// Sensors returns parsed `sensors -j` JSON (hardware temperatures/fans).
//
// WHY THIS CANNOT BE THE API: hardware sensors are not API-exposed (phase3 §B3).
func (p *Privileged) Sensors(ctx context.Context) (map[string]any, error) {
	out, stderr, err := p.runner.Run(ctx, "sensors", "-j")
	if err != nil && len(out) == 0 {
		return nil, fmt.Errorf("proxmox: sensors: %w: %s", err, stderr)
	}
	var m map[string]any
	if err := json.Unmarshal(out, &m); err != nil {
		return nil, fmt.Errorf("proxmox: parsing sensors JSON: %w", err)
	}
	return m, nil
}

// run executes a command and wraps a nonzero exit with its stderr.
func (p *Privileged) run(ctx context.Context, name string, args ...string) error {
	_, stderr, err := p.runner.Run(ctx, name, args...)
	if err != nil {
		return fmt.Errorf("proxmox: %s %v: %w: %s", name, args, err, trimBody(string(stderr)))
	}
	return nil
}

// capBuf is a tiny capped buffer so a runaway command can't blow memory.
type capBuf struct{ b []byte }

func (c *capBuf) Write(p []byte) (int, error) {
	const max = 1 << 20 // 1 MiB
	if len(c.b) < max {
		room := max - len(c.b)
		if room >= len(p) {
			c.b = append(c.b, p...)
		} else {
			c.b = append(c.b, p[:room]...)
		}
	}
	return len(p), nil // always report full consumption
}
