package settings

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

func roleTestSettings(t *testing.T) *Settings {
	t.Helper()
	s, err := Load(filepath.Join(t.TempDir(), "settings.json"), log.New(os.Stderr, "", 0))
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	return s
}

// SCENARIO C, the core of E-2 §3: a drive NEVER acquires the backup-target role by appearing.
//
// This is the invariant the whole role model exists for. Registration is the path a newly-plugged
// drive travels, and the failure it guards against is a USB stick silently becoming the destination
// of the whole-system backup. The test asserts the CONSEQUENCE (no target is assigned after
// registering drives) rather than the mechanism, so it still fails if a future caller sets the field
// through some other route in the registration path.
func TestRegisteringDrivesNeverAssignsTheBackupTarget(t *testing.T) {
	s := roleTestSettings(t)
	for _, p := range []string{"/mnt/hdd_1", "/mnt/usb_stick", "/mnt/nvme-1tb"} {
		if err := s.AddStoragePath(StoragePath{Path: p, Label: p, Schedulable: true}); err != nil {
			t.Fatalf("add %s: %v", p, err)
		}
	}
	if got := s.BackupTargetPath(); got != "" {
		t.Fatalf("registering drives assigned the backup target %q — a drive must never acquire a role "+
			"by appearing (E-2 §3); only an explicit customer choice may assign it", got)
	}
	if s.BackupTargetAssigned() {
		t.Fatal("BackupTargetAssigned() is true after registration alone")
	}
}

// Exactly one target at a time — assigning a second must move the role, not duplicate it. Two paths
// both claiming to be the destination is not a cosmetic defect: the renderer and the absent-check
// would each pick a different one.
func TestBackupTargetIsExclusive(t *testing.T) {
	s := roleTestSettings(t)
	for _, p := range []string{"/mnt/a", "/mnt/b"} {
		if err := s.AddStoragePath(StoragePath{Path: p, Schedulable: true}); err != nil {
			t.Fatalf("add: %v", err)
		}
	}
	if err := s.SetBackupTarget("/mnt/a"); err != nil {
		t.Fatalf("set a: %v", err)
	}
	if err := s.SetBackupTarget("/mnt/b"); err != nil {
		t.Fatalf("set b: %v", err)
	}
	var carriers []string
	for _, sp := range s.GetStoragePaths() {
		if sp.BackupTarget {
			carriers = append(carriers, sp.Path)
		}
	}
	if len(carriers) != 1 || carriers[0] != "/mnt/b" {
		t.Fatalf("expected exactly one carrier (/mnt/b), got %v", carriers)
	}
}

// STICKINESS (E-2 §3): a new drive appearing must not move an assigned target. This is the other half
// of "never by appearing" — the first covers an unassigned box, this covers an assigned one.
func TestNewDriveDoesNotStealAnAssignedTarget(t *testing.T) {
	s := roleTestSettings(t)
	if err := s.AddStoragePath(StoragePath{Path: "/mnt/hdd_1", Schedulable: true}); err != nil {
		t.Fatalf("add: %v", err)
	}
	if err := s.SetBackupTarget("/mnt/hdd_1"); err != nil {
		t.Fatalf("set: %v", err)
	}
	// A bigger, faster, newer drive appears. It must change nothing.
	if err := s.AddStoragePath(StoragePath{Path: "/mnt/nvme_huge", Schedulable: true}); err != nil {
		t.Fatalf("add: %v", err)
	}
	if got := s.BackupTargetPath(); got != "/mnt/hdd_1" {
		t.Fatalf("target moved to %q when a new drive appeared — the role is sticky", got)
	}
}

// An ABSENT target stays assigned. Clearing it on disconnect would be a silent retarget by omission:
// the box would read as "no target configured" (degraded) instead of "your target drive is missing"
// (alarm), and reconnecting would not restore the assignment.
func TestDisconnectDoesNotClearTheTarget(t *testing.T) {
	s := roleTestSettings(t)
	if err := s.AddStoragePath(StoragePath{Path: "/mnt/hdd_1", Schedulable: true}); err != nil {
		t.Fatalf("add: %v", err)
	}
	if err := s.SetBackupTarget("/mnt/hdd_1"); err != nil {
		t.Fatalf("set: %v", err)
	}
	if err := s.SetDisconnected("/mnt/hdd_1", true, nil); err != nil {
		t.Fatalf("disconnect: %v", err)
	}
	if got := s.BackupTargetPath(); got != "/mnt/hdd_1" {
		t.Fatalf("disconnect cleared the target (got %q) — an absent target must stay assigned and "+
			"alarm, never silently become 'unconfigured'", got)
	}
}

// A network share can never be the target: the role exists to survive a LOCAL disk failure, and a
// remote, credential-bound share mounted at its own root (R-108) is a different risk model.
func TestNetworkShareRefusedAsBackupTarget(t *testing.T) {
	s := roleTestSettings(t)
	if err := s.AddStoragePath(StoragePath{Path: "/mnt/felhom-drives/nas", Kind: StorageKindNetwork}); err != nil {
		t.Fatalf("add: %v", err)
	}
	if err := s.SetBackupTarget("/mnt/felhom-drives/nas"); err == nil {
		t.Fatal("a network share was accepted as the backup target")
	}
	if s.BackupTargetAssigned() {
		t.Fatal("a refused assignment still set the role")
	}
}
