package appbackup

import (
	"path/filepath"
	"reflect"
	"testing"
)

// fp joins the elements under an HDD path with OS separators — mounts in the ParseComposeHDDMounts
// shape are already filepath.Clean'd, so tests build them the same way.
func fp(elems ...string) string { return filepath.Join(elems...) }

// TestAppDataDirNames is the pure derivation table (Group A). Every case asserts the RESOLVED name
// list, never mere absence of error. Companion RP-1: a resolver that ignores mounts and returns
// []string{stackName} fails the paperless, two-name, and dedupe cases.
func TestAppDataDirNames(t *testing.T) {
	const hdd = "/mnt/felhom-usb"
	cases := []struct {
		name   string
		stack  string
		mounts []string
		want   []string
	}{
		{
			// paperless shape: stack "paperless-ngx", dir "paperless" (F-S2/F-S3 core).
			name:  "paperless mismatch",
			stack: "paperless-ngx",
			mounts: []string{
				fp(hdd, "appdata", "paperless", "media"),
				fp(hdd, "appdata", "paperless", "export"),
			},
			want: []string{"paperless"}, // media+export dedupe to one name
		},
		{
			// match shape: dir name == stack name (immich/nextcloud/romm).
			name:   "matching name",
			stack:  "nextcloud",
			mounts: []string{fp(hdd, "appdata", "nextcloud")},
			want:   []string{"nextcloud"},
		},
		{
			// two DISTINCT names → both, sorted (no catalog app does this today).
			name:  "two distinct names sorted",
			stack: "weird",
			mounts: []string{
				fp(hdd, "appdata", "zebra", "x"),
				fp(hdd, "appdata", "alpha", "y"),
			},
			want: []string{"alpha", "zebra"},
		},
		{
			// non-appdata HDD binds + a foreign-drive mount are filtered → fallback.
			name:  "non-appdata and foreign filtered",
			stack: "romm",
			mounts: []string{
				fp(hdd, "roms"), // under HDD but not appdata/
				fp("/mnt/other-drive", "appdata", "ghost"), // foreign drive — wrong prefix
			},
			want: []string{"romm"},
		},
		{
			// whole-appdata-root bind (no name derivable) → ignored → fallback.
			name:   "whole appdata root bind",
			stack:  "root-binder",
			mounts: []string{fp(hdd, "appdata")},
			want:   []string{"root-binder"},
		},
		{
			name:   "empty mounts fallback",
			stack:  "vaultwarden",
			mounts: nil,
			want:   []string{"vaultwarden"},
		},
		{
			// unclean paths still resolve (Clean applied both sides).
			name:   "unclean path",
			stack:  "paperless-ngx",
			mounts: []string{hdd + "/appdata/paperless/../paperless/media"},
			want:   []string{"paperless"},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := AppDataDirNames(hdd, tc.stack, tc.mounts)
			if !reflect.DeepEqual(got, tc.want) {
				t.Errorf("AppDataDirNames(%q, %q, %v) = %v, want %v", hdd, tc.stack, tc.mounts, got, tc.want)
			}
		})
	}
}

// TestAppDataBindsPresent pins the WARN predicate: true only when a mount sits under appdata/.
func TestAppDataBindsPresent(t *testing.T) {
	const hdd = "/mnt/felhom-usb"
	if !AppDataBindsPresent(hdd, []string{fp(hdd, "appdata", "paperless", "media")}) {
		t.Error("declared appdata bind should report present")
	}
	if AppDataBindsPresent(hdd, []string{fp(hdd, "roms")}) {
		t.Error("non-appdata bind should NOT report present")
	}
	if AppDataBindsPresent(hdd, nil) {
		t.Error("no mounts should NOT report present")
	}
}
