package backup

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"testing"

	"gitea.dooplex.hu/admin/felhom-controller/internal/config"
	"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)

// f5Manager wires a Manager with a real settings store holding the given enrolled drives, plus a
// sys drive, and a fake provider mapping each deployed app to its CURRENT HDD path.
func f5Manager(t *testing.T, sysDrive string, enrolled []string, deployed map[string]string) *Manager {
	t.Helper()
	sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0))
	if err != nil {
		t.Fatal(err)
	}
	for _, d := range enrolled {
		if err := sett.AddStoragePath(settings.StoragePath{Path: d, Label: filepath.Base(d), Schedulable: true}); err != nil {
			t.Fatal(err)
		}
	}
	var stacks []StackSummary
	for name := range deployed {
		stacks = append(stacks, StackSummary{Name: name})
	}
	cfg := &config.Config{}
	cfg.Paths.SystemDataPath = sysDrive
	fake := &volDumpFakeProvider{stacks: stacks, hdd: deployed}
	return &Manager{cfg: cfg, settings: sett, logger: log.New(io.Discard, "", 0),
		systemDataPath: sysDrive, stackProvider: fake}
}

// seedPrimaryDir creates a backups/primary/<app> dir (with a marker file) under a drive's namespace.
func seedPrimaryDir(t *testing.T, driveNsRoot, app string) string {
	t.Helper()
	dir := RecoveryUnitPath(driveNsRoot, app)
	if err := os.MkdirAll(filepath.Join(dir, "volume-dumps"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte("{}"), 0644); err != nil {
		t.Fatal(err)
	}
	return dir
}

// F5: an app redeployed from drive A to drive B → its stale primary dir on A is removed, its live
// dir on B is kept. Guard red-proof lives in the two "kept" assertions.
func TestPruneStalePrimaryDirs_RemovesRedeployedResidue(t *testing.T) {
	driveA := t.TempDir()
	driveB := t.TempDir()
	sys := filepath.Join(t.TempDir(), "sys")
	// nextcloud currently on B; a stale unit remains on A.
	m := f5Manager(t, sys, []string{driveA, driveB}, map[string]string{"nextcloud": driveB})

	nsA := NamespaceRoot(driveA, true)
	nsB := NamespaceRoot(driveB, true)
	staleA := seedPrimaryDir(t, nsA, "nextcloud")
	liveB := seedPrimaryDir(t, nsB, "nextcloud")

	m.pruneStalePrimaryDirs()

	if _, err := os.Stat(staleA); !os.IsNotExist(err) {
		t.Errorf("stale primary dir on the OLD drive was not removed (F5)")
	}
	if _, err := os.Stat(liveB); err != nil {
		t.Errorf("the LIVE primary dir on the current drive was wrongly removed: %v", err)
	}
}

// GUARD: an UNDEPLOYED app's primary dir is a restore point — it must be KEPT (companion: drop the
// `!deployed { continue }` guard → this dir is deleted → fail).
func TestPruneStalePrimaryDirs_KeepsUndeployedRestorePoint(t *testing.T) {
	driveA := t.TempDir()
	sys := filepath.Join(t.TempDir(), "sys")
	// Only "nextcloud" is deployed (on A); "removed-app" has a leftover unit but is NOT deployed.
	m := f5Manager(t, sys, []string{driveA}, map[string]string{"nextcloud": driveA})
	nsA := NamespaceRoot(driveA, true)
	seedPrimaryDir(t, nsA, "nextcloud")
	orphan := seedPrimaryDir(t, nsA, "removed-app")

	m.pruneStalePrimaryDirs()

	if _, err := os.Stat(orphan); err != nil {
		t.Errorf("an UNDEPLOYED app's restore point was wrongly deleted (guard failure): %v", err)
	}
}

// GUARD: the dir on an app's CURRENT drive is never touched (already covered above, but assert it
// directly with a single-drive app so no cross-drive move is involved).
func TestPruneStalePrimaryDirs_KeepsCurrentDriveDir(t *testing.T) {
	driveA := t.TempDir()
	sys := filepath.Join(t.TempDir(), "sys")
	m := f5Manager(t, sys, []string{driveA}, map[string]string{"nextcloud": driveA})
	nsA := NamespaceRoot(driveA, true)
	live := seedPrimaryDir(t, nsA, "nextcloud")

	m.pruneStalePrimaryDirs()

	if _, err := os.Stat(live); err != nil {
		t.Errorf("the current-drive primary dir was wrongly removed: %v", err)
	}
}
