package stacks

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

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

func writeClassCompose(t *testing.T, body string) string {
	t.Helper()
	dir := t.TempDir()
	p := filepath.Join(dir, "docker-compose.yml")
	if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
		t.Fatal(err)
	}
	return p
}

func findBind(binds []appbackup.ComposeBind, root appbackup.BindRoot, rel string) (appbackup.ComposeBind, bool) {
	for _, b := range binds {
		if b.Root == root && b.RelPath == rel {
			return b, true
		}
	}
	return appbackup.ComposeBind{}, false
}

// TestParseClassifiableBinds_Variants (Group C): short-syntax variants, quotes, mode tokens
// (ro/rw/ro,z/z), dedupe, bare root, both roots, and non-volume sections ignored.
func TestParseClassifiableBinds_Variants(t *testing.T) {
	compose := `
services:
  app:
    image: x:1
    environment:
      - HDD_PATH=${HDD_PATH}         # NOT a volume — must be ignored
    volumes:
      - ${HDD_PATH}/appdata/immich:/upload
      - "${USERDATA_PATH}/media/photos:/external:ro"
      - ${USERDATA_PATH}/media/music:/music:ro,z
      - ${USERDATA_PATH}/downloads:/dl:z
      - ${USERDATA_PATH}/downloads:/dl2         # duplicate (root,relpath) — deduped, first wins
      - ${HDD_PATH}:/hddroot                    # bare root
      - ./local-only:/x                         # not a classifiable var — ignored
      - named_vol:/data                         # named volume — ignored
    ports:
      - 8080:80                                 # ports section — not volumes
`
	binds := ParseComposeClassifiableBinds(writeClassCompose(t, compose))

	// Expect exactly: hdd/appdata/immich, userdata/media/photos(ro), userdata/media/music(ro),
	// userdata/downloads(writable, first occ), hdd/"" (bare root).
	if len(binds) != 5 {
		t.Fatalf("got %d binds, want 5: %+v", len(binds), binds)
	}
	if b, ok := findBind(binds, appbackup.RootHDD, "appdata/immich"); !ok || b.ReadOnly {
		t.Errorf("appdata/immich = %+v, want writable", b)
	}
	if b, ok := findBind(binds, appbackup.RootUserdata, "media/photos"); !ok || !b.ReadOnly {
		t.Errorf("media/photos = %+v, want ro", b)
	}
	if b, ok := findBind(binds, appbackup.RootUserdata, "media/music"); !ok || !b.ReadOnly {
		t.Errorf("media/music (ro,z) = %+v, want ro", b)
	}
	// downloads: first occurrence was `:z` (writable) — dedupe keeps the first, so writable wins.
	if b, ok := findBind(binds, appbackup.RootUserdata, "downloads"); !ok || b.ReadOnly {
		t.Errorf("downloads = %+v, want single writable entry (first-wins dedupe)", b)
	}
	if b, ok := findBind(binds, appbackup.RootHDD, ""); !ok || b.ReadOnly {
		t.Errorf("bare ${HDD_PATH} = %+v, want RelPath '' writable", b)
	}
}

// TestParseClassifiableBinds_CleanAndSubpaths: nested paths clean correctly; a false-prefix var
// (${HDD_PATH_X}) does NOT match.
func TestParseClassifiableBinds_CleanAndSubpaths(t *testing.T) {
	compose := `
services:
  app:
    volumes:
      - ${HDD_PATH}/appdata/paperless/media:/m
      - ${HDD_PATH_EXTRA}/nope:/n      # false prefix — must NOT match ${HDD_PATH}
      - ${USERDATA_PATH}/import/calibre:/i:rw
`
	binds := ParseComposeClassifiableBinds(writeClassCompose(t, compose))
	if len(binds) != 2 {
		t.Fatalf("got %d binds, want 2 (false-prefix excluded): %+v", len(binds), binds)
	}
	if _, ok := findBind(binds, appbackup.RootHDD, "appdata/paperless/media"); !ok {
		t.Errorf("nested hdd path missing: %+v", binds)
	}
	if b, ok := findBind(binds, appbackup.RootUserdata, "import/calibre"); !ok || b.ReadOnly {
		t.Errorf("import/calibre (rw) = %+v, want writable", b)
	}
}

// TestParseClassifiableBinds_Missing: an unreadable compose yields nil (no panic).
func TestParseClassifiableBinds_Missing(t *testing.T) {
	if b := ParseComposeClassifiableBinds(filepath.Join(t.TempDir(), "nope.yml")); b != nil {
		t.Errorf("missing compose should yield nil, got %+v", b)
	}
}
