package backup

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

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

// newTestManager builds a Manager backed by a real (temp-file) settings store and the given
// system-data path as the SSD fallback source (no stackProvider → source = systemDataPath).
func newTestManager(t *testing.T, systemDataPath string) (*Manager, *settings.Settings) {
	t.Helper()
	logger := log.New(os.Stderr, "", 0)
	sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), logger)
	if err != nil {
		t.Fatalf("settings.Load: %v", err)
	}
	cfg := &config.Config{}
	cfg.Paths.SystemDataPath = systemDataPath
	return NewManager(cfg, sett, logger), sett
}

// A customer-pinned PreferredTarget must win over the auto-pick (which would take the first
// off-disk drive), and be reported with the "kézi választás" reason.
func TestSelectTier2Target_HonorsPreferred(t *testing.T) {
	m, sett := newTestManager(t, "/srv/sys")
	// Two eligible drives; auto-pick would take the alphabetically-first ("/mnt/a").
	if err := sett.AddStoragePath(settings.StoragePath{Path: "/mnt/a", Label: "A", Schedulable: true}); err != nil {
		t.Fatal(err)
	}
	if err := sett.AddStoragePath(settings.StoragePath{Path: "/mnt/b", Label: "B", Schedulable: true}); err != nil {
		t.Fatal(err)
	}
	if err := sett.SetTier2Preference("app", false, "/mnt/b"); err != nil {
		t.Fatal(err)
	}
	target, err := m.selectTier2Target("app", 1024)
	if err != nil {
		t.Fatalf("selectTier2Target: %v", err)
	}
	if target.NamespaceRoot != filepath.FromSlash("/mnt/b") {
		t.Errorf("NamespaceRoot = %q, want /mnt/b (pinned)", target.NamespaceRoot)
	}
	if target.Reason != "kézi választás" {
		t.Errorf("Reason = %q, want 'kézi választás'", target.Reason)
	}
}

// An invalid pin (path not registered) silently falls through to the auto-pick.
func TestSelectTier2Target_InvalidPreferredFallsBack(t *testing.T) {
	m, sett := newTestManager(t, "/srv/sys")
	if err := sett.AddStoragePath(settings.StoragePath{Path: "/mnt/a", Label: "A", Schedulable: true}); err != nil {
		t.Fatal(err)
	}
	if err := sett.SetTier2Preference("app", false, "/mnt/gone"); err != nil {
		t.Fatal(err)
	}
	target, err := m.selectTier2Target("app", 1024)
	if err != nil {
		t.Fatalf("selectTier2Target: %v", err)
	}
	if target.NamespaceRoot != filepath.FromSlash("/mnt/a") || target.Reason != "másik adatmeghajtó" {
		t.Errorf("got %q/%q, want /mnt/a auto-pick", target.NamespaceRoot, target.Reason)
	}
}

// A runner status write must NOT clobber the customer's preference fields.
func TestRecordTier2_PreservesPreference(t *testing.T) {
	m, sett := newTestManager(t, "/srv/sys")
	if err := sett.SetTier2Preference("app", true, "/mnt/b"); err != nil {
		t.Fatal(err)
	}
	m.recordTier2NoTarget("app", "teszt")
	cd := sett.GetCrossDriveConfig("app")
	if cd == nil {
		t.Fatal("config missing after status write")
	}
	if !cd.UserDisabled || cd.PreferredTarget != "/mnt/b" {
		t.Errorf("preference clobbered: UserDisabled=%v PreferredTarget=%q", cd.UserDisabled, cd.PreferredTarget)
	}
	if cd.LastStatus != "no_target" {
		t.Errorf("LastStatus = %q, want no_target", cd.LastStatus)
	}
}

// TestTier2FitsHeadroom covers the size-aware rootfs-headroom guard that protects the ~8 GB guest
// rootfs from being filled by a Tier 2 SSD copy (reserve = max(2 GB, 20% of total)).
func TestTier2FitsHeadroom(t *testing.T) {
	cases := []struct {
		name                     string
		availGB, totalGB, unitGB float64
		want                     bool
	}{
		// 8 GB rootfs, ~2.4 GB free: a tiny unit fits (reserve = 2 GB), a 1 GB unit does NOT.
		{"8G rootfs, tiny unit fits", 2.4, 8.0, 0.02, true},
		{"8G rootfs, 1G unit refused", 2.4, 8.0, 1.0, false},
		{"8G rootfs, 0.3G unit fits", 2.4, 8.0, 0.3, true},
		// Reserve is the larger of 2 GB and 20%: on 8 GB, 20% = 1.6 GB < 2 GB, so 2 GB applies.
		{"8G rootfs exactly at 2G reserve", 2.0, 8.0, 0.0, true},
		{"8G rootfs just under reserve", 2.0, 8.0, 0.01, false},
		// Large drive: 20% reserve dominates (204.8 GB on a 1 TB drive).
		{"1TB drive, 50G unit fits", 500.0, 1024.0, 50.0, true},
		{"1TB drive, 320G unit refused (under 20% reserve)", 500.0, 1024.0, 320.0, false},
	}
	for _, c := range cases {
		if got := tier2FitsHeadroom(c.availGB, c.totalGB, c.unitGB); got != c.want {
			t.Errorf("%s: tier2FitsHeadroom(avail=%.2f,total=%.2f,unit=%.2f)=%v want %v",
				c.name, c.availGB, c.totalGB, c.unitGB, got, c.want)
		}
	}
}
