package proxmox

import (
	"context"
	"io"
	"strings"
	"testing"
)

// F-LEAK (Campaign 8): the fourth root-fenced exception. Its whole justification is that the band is
// enforced rather than assumed, so these tests are about the REFUSALS, not the happy path.
//
// The band is checked in three independent places on purpose: sudoers matches the vmid literally
// (`pct destroy 99000[0-9] --purge`), this method re-checks before exec, and the caller checks journal
// provenance. These tests pin the middle one; the sudoers glob is proven live.

type recordingRunner struct {
	calls [][]string
	err   error
}

func (r *recordingRunner) Run(_ context.Context, name string, args ...string) ([]byte, []byte, error) {
	r.calls = append(r.calls, append([]string{name}, args...))
	return nil, nil, r.err
}

func (r *recordingRunner) RunStdin(_ context.Context, _ io.Reader, name string, args ...string) ([]byte, []byte, error) {
	r.calls = append(r.calls, append([]string{name}, args...))
	return nil, nil, r.err
}

// A vmid inside the band is destroyed, with --purge so no config/ACL/firewall residue survives.
//
// RED-PROOF: drop "--purge" from the args → this fails with "destroy is not --purge", and the live
// sudoers rule (which matches the FULL vector including --purge) would refuse the call outright.
func TestDestroyScratchLXC_InBandDestroysWithPurge(t *testing.T) {
	r := &recordingRunner{}
	if err := NewPrivileged(r, "").DestroyScratchLXC(context.Background(), 990003, 990000, 990009); err != nil {
		t.Fatalf("in-band destroy failed: %v", err)
	}
	if len(r.calls) != 1 {
		t.Fatalf("want exactly 1 exec, got %d: %v", len(r.calls), r.calls)
	}
	got := strings.Join(r.calls[0], " ")
	if got != "pct destroy 990003 --purge" {
		t.Errorf("exec vector = %q, want %q (it must match the sudoers rule byte for byte)",
			got, "pct destroy 990003 --purge")
	}
}

// THE ONE THAT MATTERS. A vmid outside the band must be refused WITHOUT EXECUTING ANYTHING — a real
// customer guest, the golden image, a co-tenant's VM.
//
// RED-PROOF: remove the `vmid < bandMin || vmid > bandMax` check → this fails with
// "REFUSAL FAILED: executed [pct destroy 9201 --purge] for out-of-band vmid 9201".
func TestDestroyScratchLXC_RefusesOutOfBandWithoutExecuting(t *testing.T) {
	for _, vmid := range []int{
		1,      // arbitrary
		9201,   // the LIVE customer guest on both demo boxes
		9100,   // golden image
		9999,   // reserved
		989999, // one below the band
		990010, // one ABOVE the band — the off-by-one
	} {
		r := &recordingRunner{}
		err := NewPrivileged(r, "").DestroyScratchLXC(context.Background(), vmid, 990000, 990009)
		if err == nil {
			t.Errorf("vmid %d was NOT refused — the fence is open", vmid)
		}
		if len(r.calls) != 0 {
			t.Errorf("REFUSAL FAILED: executed %v for out-of-band vmid %d", r.calls, vmid)
		}
		if err != nil && !strings.Contains(err.Error(), "outside the scratch band") {
			t.Errorf("vmid %d refused with an unhelpful error: %v", vmid, err)
		}
	}
}

// An unconfigured or inverted band must refuse everything rather than defaulting to something. A zero
// band is what a mis-wired caller looks like, and "destroy vmid 0" must never become reachable.
//
// RED-PROOF: drop the `bandMin <= 0 || bandMax < bandMin` check → the [0,0] case admits vmid 0 and
// this fails with "an unconfigured band admitted vmid 0".
func TestDestroyScratchLXC_RefusesUnconfiguredBand(t *testing.T) {
	cases := []struct{ vmid, min, max int }{
		{0, 0, 0},                // nothing configured at all
		{990000, 0, 0},           // band absent, real scratch vmid
		{990000, 0, 990009},      // min unset
		{990005, 990009, 990000}, // inverted
		{990000, -1, 990009},     // negative
	}
	for _, c := range cases {
		r := &recordingRunner{}
		if err := NewPrivileged(r, "").DestroyScratchLXC(context.Background(), c.vmid, c.min, c.max); err == nil {
			t.Errorf("band [%d,%d] admitted vmid %d — an unconfigured band must refuse", c.min, c.max, c.vmid)
		}
		if len(r.calls) != 0 {
			t.Errorf("an unconfigured band admitted vmid %d and EXECUTED %v", c.vmid, r.calls)
		}
	}
}
