package storage

import (
	"context"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

// The §8 decision table, encoded exactly (CAMPAIGN-3 F11: fstype-driven, automount state IGNORED).
func TestNetReassertClassify_Table(t *testing.T) {
	cases := []struct {
		fstype string
		want   string
	}{
		{"nfs4", NetReassertSkipActive},   // real mount — inherited by fresh namespaces
		{"nfs", NetReassertSkipActive},    // real mount
		{"cifs", NetReassertSkipActive},   // real mount
		{"autofs", NetReassertRearmed},    // idle trigger — NOT inherited, re-arm to propagate
		{"", NetReassertRearmed},          // F10: a failed/disarmed automount leaves NO mount entry — re-arm
		{"ext4", NetReassertSkipForeign},  // a foreign local fs at the path is not ours to churn
		{"tmpfs", NetReassertSkipForeign}, //
	}
	for _, c := range cases {
		if got := netReassertClassify(c.fstype); got != c.want {
			t.Errorf("netReassertClassify(%q) = %q, want %q", c.fstype, got, c.want)
		}
	}
}

// installNetUnitFile writes a rendered network .mount unit straight into unitDir (bypassing the
// runner-mediated install — the reassert only READS unit files).
func installNetUnitFile(t *testing.T, unitDir string, spec NetworkMountSpec) (where, automountUnit string) {
	t.Helper()
	where = spec.Where()
	mountUnit, err := UnitNameForMount(where)
	if err != nil {
		t.Fatalf("unit name: %v", err)
	}
	if err := os.WriteFile(filepath.Join(unitDir, mountUnit), []byte(renderNetworkMountUnit(spec)), 0o644); err != nil {
		t.Fatalf("write unit: %v", err)
	}
	return where, strings.TrimSuffix(mountUnit, ".mount") + ".automount"
}

// netReassertOps builds ops with a hermetic unitFailed seam (default: nothing failed — no shelling out
// to a real systemctl is-failed). failedUnits, if set, marks specific unit names as failed.
func netReassertOps(t *testing.T, unitDir string, mounts []Mount, failedUnits ...string) (*SudoHostOps, *recordingRunner) {
	t.Helper()
	rr := &recordingRunner{}
	ops := NewSudoHostOps(SudoHostOpsConfig{
		Runner: rr, Bins: Binaries{}.withDefaults(), UnitDir: unitDir, StageDir: t.TempDir(),
		Host: &fakeHostReader{mounts: mounts}, Logger: quietLogger(),
	})
	failed := map[string]bool{}
	for _, u := range failedUnits {
		failed[u] = true
	}
	ops.unitFailed = func(_ context.Context, unit string) bool { return failed[unit] }
	return ops, rr
}

// An idle trigger (autofs at the mountpoint) must be re-armed with EXACTLY the granted verbs:
// `systemctl stop -- <unit>.automount` then `systemctl enable --now -- <unit>.automount`. No
// reset-failed when nothing is failed.
func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	where, autoUnit := installNetUnitFile(t, unitDir, spec)
	ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})

	results := ops.ReassertNetworkAutomounts(context.Background())
	if len(results) != 1 || results[0].Action != NetReassertRearmed || results[0].Err != nil {
		t.Fatalf("want one rearmed result, got %+v", results)
	}
	if len(rr.calls) != 2 {
		t.Fatalf("want exactly stop + enable --now (no reset-failed when clean), got %d calls: %v", len(rr.calls), rr.calls)
	}
	stop, enable := strings.Join(rr.calls[0], " "), strings.Join(rr.calls[1], " ")
	if !strings.Contains(stop, "systemctl stop -- "+autoUnit) {
		t.Errorf("first call must be `systemctl stop -- %s`, got: %s", autoUnit, stop)
	}
	if !strings.Contains(enable, "systemctl enable --now -- "+autoUnit) {
		t.Errorf("second call must be `systemctl enable --now -- %s`, got: %s", autoUnit, enable)
	}
}

// F10: a failed/start-limited automount (the campaign's unexport→idle-timeout→access×5 sequence leaves
// NO mount entry, fstype "") must be reset-failed FIRST, then re-armed — verdict reset-failed+rearmed.
// Companion to the campaign's reboots #2–#4: the pre-0.85 code returned skip-none here and left it dead.
func TestReassertNetworkAutomounts_ResetsFailedThenRearms(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	_, autoUnit := installNetUnitFile(t, unitDir, spec)
	// Nothing mounted (fstype "" — a start-limited automount), and the automount unit is in failed state.
	ops, rr := netReassertOps(t, unitDir, nil, autoUnit)

	results := ops.ReassertNetworkAutomounts(context.Background())
	if len(results) != 1 || results[0].Action != NetReassertResetRearmed || results[0].Err != nil {
		t.Fatalf("want one reset-failed+rearmed result, got %+v", results)
	}
	// reset-failed <automount>, then stop + enable --now.
	var sawReset, sawEnable bool
	for _, c := range rr.calls {
		j := strings.Join(c, " ")
		if strings.Contains(j, "reset-failed -- "+autoUnit) {
			sawReset = true
		}
		if strings.Contains(j, "enable --now -- "+autoUnit) {
			sawEnable = true
		}
	}
	if !sawReset {
		t.Errorf("a failed unit must be reset-failed before re-arm (F10); calls: %v", rr.calls)
	}
	if !sawEnable {
		t.Errorf("the trigger must still be re-armed after reset-failed; calls: %v", rr.calls)
	}
}

// An ACTIVE real mount must not be touched — stopping the automount of a live mount would churn it.
// (Red-proof companion: a naive always-rearm implementation fails this with recorded calls.)
func TestReassertNetworkAutomounts_ActiveMountUntouched(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	where, _ := installNetUnitFile(t, unitDir, spec)
	ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "nfs4"}})

	results := ops.ReassertNetworkAutomounts(context.Background())
	if len(results) != 1 || results[0].Action != NetReassertSkipActive {
		t.Fatalf("want one skip-active result, got %+v", results)
	}
	if len(rr.calls) != 0 {
		t.Fatalf("an actively-mounted share must trigger ZERO systemctl calls, got: %v", rr.calls)
	}
}

// The automount unit's own state is IGNORED (F11 red-proof): even though a mutant that consulted
// `systemctl is-active <automount>` would see an armed trigger as "active" and skip it, the fstype at
// the path is autofs (idle) so the correct code RE-ARMS. Encoded as: an idle trigger re-arms regardless
// of failed/armed unit state — the decision is fstype only.
func TestReassertNetworkAutomounts_IgnoresAutomountUnitState(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	where, _ := installNetUnitFile(t, unitDir, spec)
	// fstype autofs (idle-armed): the correct decision is re-arm, NOT skip — a state-of-the-automount
	// check would mis-skip an armed trigger (which always reports "active").
	ops, _ := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})
	results := ops.ReassertNetworkAutomounts(context.Background())
	if len(results) != 1 || (results[0].Action != NetReassertRearmed && results[0].Action != NetReassertResetRearmed) {
		t.Fatalf("an idle (autofs) trigger must re-arm regardless of automount unit state, got %+v", results)
	}
}

// A foreign filesystem at the path (ext4/tmpfs) is skipped — re-arming over it would fail (busy).
func TestReassertNetworkAutomounts_ForeignFSSkipped(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	where, _ := installNetUnitFile(t, unitDir, spec)
	ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "ext4"}})
	results := ops.ReassertNetworkAutomounts(context.Background())
	if len(results) != 1 || results[0].Action != NetReassertSkipForeign {
		t.Fatalf("want one skip-foreign result, got %+v", results)
	}
	if len(rr.calls) != 0 {
		t.Fatalf("a foreign fs at the path must trigger zero systemctl calls, got: %v", rr.calls)
	}
}

// F9: the pass returns exactly one verdict per installed unit — including a failed one. An
// empty-looking sweep over N shares is structurally impossible (the campaign's silent zero-line sweep).
func TestReassertNetworkAutomounts_VerdictPerUnit(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	specs := []NetworkMountSpec{
		{Name: "alpha", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/a", MappedUID: 1000, MappedGID: 1000},
		{Name: "beta", Protocol: ProtocolNFS, Server: "10.0.0.6", Export: "/srv/b", MappedUID: 1000, MappedGID: 1000},
		{Name: "gamma", Protocol: ProtocolNFS, Server: "10.0.0.7", Export: "/srv/c", MappedUID: 1000, MappedGID: 1000},
	}
	for _, s := range specs {
		installNetUnitFile(t, unitDir, s)
	}
	// alpha active (skip-active), beta idle (rearm), gamma failed-and-unmounted (reset-failed+rearm).
	gammaAuto := func() string {
		u, _ := UnitNameForMount(specs[2].Where())
		return strings.TrimSuffix(u, ".mount") + ".automount"
	}()
	ops, _ := netReassertOps(t, unitDir, []Mount{
		{MountPoint: specs[0].Where(), FSType: "nfs4"},
		{MountPoint: specs[1].Where(), FSType: "autofs"},
	}, gammaAuto)

	results := ops.ReassertNetworkAutomounts(context.Background())
	if len(results) != len(specs) {
		t.Fatalf("verdict count must equal unit count (%d), got %d: %+v", len(specs), len(results), results)
	}
	for _, r := range results {
		if r.Action == "" {
			t.Errorf("every share must carry a verdict (F9), got empty for %s", r.Name)
		}
	}
}

// Idempotency: two consecutive passes over an idle trigger both re-arm with the same action, no error.
func TestReassertNetworkAutomounts_Idempotent(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
	}
	unitDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	where, _ := installNetUnitFile(t, unitDir, spec)
	ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})

	first := ops.ReassertNetworkAutomounts(context.Background())
	second := ops.ReassertNetworkAutomounts(context.Background())
	if first[0].Action != NetReassertRearmed || second[0].Action != NetReassertRearmed {
		t.Fatalf("both passes must re-arm: first=%+v second=%+v", first, second)
	}
	if len(rr.calls) != 4 {
		t.Fatalf("two passes = 2×(stop+enable), got %d: %v", len(rr.calls), rr.calls)
	}
}

// Zero configured network units → empty pass, zero commands.
func TestReassertNetworkAutomounts_NoUnitsNoOp(t *testing.T) {
	ops, rr := netReassertOps(t, t.TempDir(), nil)
	if results := ops.ReassertNetworkAutomounts(context.Background()); len(results) != 0 {
		t.Fatalf("no units must yield no results, got %+v", results)
	}
	if len(rr.calls) != 0 {
		t.Fatalf("no units must construct zero commands, got: %v", rr.calls)
	}
}
