package storage

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

// F2/F1 (CAMPAIGN-3): a remove (and, via the same path, a rolled-back add) must leave ZERO residue —
// no failed-state units, no leftover mountpoint dir. RemoveNetworkMount must reset-failed the stuck
// unit BEFORE removing the files (or systemd keeps it as not-found/failed) and rmdir the mountpoint.
func TestRemoveNetworkMount_ZeroResidue(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()
	stageDir := t.TempDir()
	spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
	where := spec.Where()
	mountName, err := UnitNameForMount(where)
	if err != nil {
		t.Fatalf("unit name: %v", err)
	}
	autoName := strings.TrimSuffix(mountName, ".mount") + ".automount"
	// Both unit files present on disk (rm is recorded, so they stay for the assertion).
	if err := os.WriteFile(filepath.Join(unitDir, mountName), []byte(renderNetworkMountUnit(spec)), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(unitDir, autoName), []byte(renderNetworkAutomountUnit(spec)), 0o644); err != nil {
		t.Fatal(err)
	}

	rr := &recordingRunner{}
	ops := NewSudoHostOps(SudoHostOpsConfig{Runner: rr, Bins: Binaries{}.withDefaults(), UnitDir: unitDir, StageDir: stageDir, Host: &fakeHostReader{}, Logger: quietLogger()})
	// The automount is in the failed state (start-limit residue) — must be reset-failed.
	ops.unitFailed = func(_ context.Context, unit string) bool { return unit == autoName }

	if err := ops.RemoveNetworkMount(context.Background(), "media"); err != nil {
		t.Fatalf("remove: %v", err)
	}

	var sawReset, sawRmUnit, sawRmdir, sawReload bool
	for _, c := range rr.calls {
		j := strings.Join(c, " ")
		switch {
		case strings.Contains(j, "reset-failed -- "+autoName):
			sawReset = true
		case strings.Contains(j, "rm -f") && strings.Contains(j, autoName):
			sawRmUnit = true
		case strings.Contains(j, "rmdir") && strings.Contains(j, where):
			sawRmdir = true
		case strings.Contains(j, "daemon-reload"):
			sawReload = true
		}
	}
	if !sawReset {
		t.Errorf("F2: a failed unit must be reset-failed on remove; calls: %v", rr.calls)
	}
	if !sawRmUnit {
		t.Errorf("the unit files must be removed; calls: %v", rr.calls)
	}
	if !sawRmdir {
		t.Errorf("F1: the empty mountpoint dir must be rmdir'd; calls: %v", rr.calls)
	}
	if !sawReload {
		t.Errorf("daemon-reload must run after removal; calls: %v", rr.calls)
	}
}

// rmdir is used (never rm -rf) — the fail-safe: a non-empty dir is left in place, not force-removed.
func TestRemoveNetworkMount_NeverForceRemovesMountpoint(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}
	mountName, _ := UnitNameForMount(spec.Where())
	autoName := strings.TrimSuffix(mountName, ".mount") + ".automount"
	_ = os.WriteFile(filepath.Join(unitDir, mountName), []byte(renderNetworkMountUnit(spec)), 0o644)
	_ = os.WriteFile(filepath.Join(unitDir, autoName), []byte(renderNetworkAutomountUnit(spec)), 0o644)

	rr := &recordingRunner{}
	ops := NewSudoHostOps(SudoHostOpsConfig{Runner: rr, Bins: Binaries{}.withDefaults(), UnitDir: unitDir, StageDir: t.TempDir(), Host: &fakeHostReader{}, Logger: quietLogger()})
	if err := ops.RemoveNetworkMount(context.Background(), "media"); err != nil {
		t.Fatalf("remove: %v", err)
	}
	for _, c := range rr.calls {
		j := strings.Join(c, " ")
		if strings.Contains(j, "rm -rf") || (strings.Contains(j, "rm ") && strings.Contains(j, "/mnt/felhom-drives/media") && !strings.Contains(j, "rmdir")) {
			t.Errorf("mountpoint cleanup must be rmdir-only (never rm -rf); offending call: %s", j)
		}
	}
}
