package appexport

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

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

func fabWrite(t *testing.T, root, rel, content string) {
	t.Helper()
	p := filepath.Join(root, filepath.FromSlash(rel))
	if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(p, []byte(content), 0644); err != nil {
		t.Fatal(err)
	}
}

// Scenario C at the export level: the userdata.tar keeps the mandatory subtree and NOT the siblings,
// and the manifest still lists the single `userdata` basename (v1 unchanged). The bundle-level anchor
// for the SQ6 fix (mirrors the §13 before/after).
func TestFabExport_ExcludeScopedUserdataTar(t *testing.T) {
	drive := t.TempDir() // hddPath
	fabWrite(t, drive, "userdata/media/books/a.epub", "BOOK")
	fabWrite(t, drive, "userdata/media/movies/big.mkv", "MOVIE")
	fabWrite(t, drive, "userdata/music/s.flac", "SONG")

	ud := appbackup.UserdataDir(filepath.Clean(drive))
	prov := &fabProv{
		rtProvider: &rtProvider{}, hddPath: drive, has: true,
		binds:  []appbackup.ClassifiedBind{mUD("media/books"), xUD("media/movies")},
		mounts: []string{ud},
	}
	e := NewExporter(prov, log.New(io.Discard, "", 0), "test")

	dataDir := t.TempDir()
	man := &Manifest{}
	if err := e.exportHDDData(ExportRequest{StackName: "calibre-web"}, dataDir, man); err != nil {
		t.Fatalf("exportHDDData: %v", err)
	}

	entries := tarEntries(t, filepath.Join(dataDir, "hdd", "userdata.tar"))
	if !containsSuffix(entries, "media/books/a.epub") {
		t.Errorf("mandatory media/books missing from userdata.tar: %v", entries)
	}
	for _, sib := range []string{"media/movies/big.mkv", "media/movies", "music/s.flac", "music"} {
		if containsSuffix(entries, sib) {
			t.Errorf("sibling %q must NOT ride along (SQ6): %v", sib, entries)
		}
	}
	// v1 manifest: the single `userdata` basename, unchanged.
	if len(man.HDDSubdirs) != 1 || man.HDDSubdirs[0] != "userdata" {
		t.Errorf("manifest must list the single v1 `userdata` basename, got %v", man.HDDSubdirs)
	}
}

// Scenario A at the export level: a legacy (no-block) app tars the FULL userdata root (every sibling)
// — byte-identical to v0.130.0 (the SQ5 safety net).
func TestFabExport_LegacyFullRoot(t *testing.T) {
	drive := t.TempDir()
	fabWrite(t, drive, "userdata/media/books/a.epub", "BOOK")
	fabWrite(t, drive, "userdata/media/movies/big.mkv", "MOVIE")

	ud := appbackup.UserdataDir(filepath.Clean(drive))
	prov := &fabProv{rtProvider: &rtProvider{}, hddPath: drive, has: false, mounts: []string{ud}}
	e := NewExporter(prov, log.New(io.Discard, "", 0), "test")

	dataDir := t.TempDir()
	man := &Manifest{}
	if err := e.exportHDDData(ExportRequest{StackName: "sonarr"}, dataDir, man); err != nil {
		t.Fatalf("exportHDDData: %v", err)
	}
	entries := tarEntries(t, filepath.Join(dataDir, "hdd", "userdata.tar"))
	for _, want := range []string{"media/books/a.epub", "media/movies/big.mkv"} {
		if !containsSuffix(entries, want) {
			t.Errorf("legacy app must capture the FULL root — %q missing: %v", want, entries)
		}
	}
}

// Scenario B at the export level: an all-excluded app produces NO userdata.tar (root tar skipped).
func TestFabExport_AllExcludedNoUserdataTar(t *testing.T) {
	drive := t.TempDir()
	fabWrite(t, drive, "userdata/media/movies/big.mkv", "MOVIE")
	ud := appbackup.UserdataDir(filepath.Clean(drive))
	prov := &fabProv{
		rtProvider: &rtProvider{}, hddPath: drive, has: true,
		binds:  []appbackup.ClassifiedBind{xUD("media/movies")},
		mounts: []string{ud},
	}
	e := NewExporter(prov, log.New(io.Discard, "", 0), "test")
	dataDir := t.TempDir()
	man := &Manifest{}
	if err := e.exportHDDData(ExportRequest{StackName: "radarr"}, dataDir, man); err != nil {
		t.Fatalf("exportHDDData: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dataDir, "hdd", "userdata.tar")); !os.IsNotExist(err) {
		t.Errorf("all-excluded app must produce NO userdata.tar (Scenario B), stat err=%v", err)
	}
	if len(man.HDDSubdirs) != 0 {
		t.Errorf("no userdata leg → no manifest subdir, got %v", man.HDDSubdirs)
	}
}

// §7-F: EstimateExport populates the class split for a classified app (both web estimate pipelines
// call this shared function, so both surface it). du returns 0 on the Windows test host, so this
// asserts the STRUCTURE (HasClassification + item keys), not byte values.
func TestEstimateExport_ClassifiedSplit(t *testing.T) {
	drive := t.TempDir()
	stackDir := t.TempDir()
	os.WriteFile(filepath.Join(stackDir, "docker-compose.yml"), []byte("services: {}\n"), 0644)
	prov := &fabProv{
		rtProvider: &rtProvider{stackDir: stackDir, deployed: true}, hddPath: drive, has: true,
		binds: []appbackup.ClassifiedBind{mUD("media/books"), oUD("media/comics"), xUD("media/movies")},
	}
	e := NewExporter(prov, log.New(io.Discard, "", 0), "test")
	est, err := e.EstimateExport("calibre-web", drive)
	if err != nil {
		t.Fatalf("EstimateExport: %v", err)
	}
	if !est.HasClassification {
		t.Fatal("classified app estimate must carry HasClassification")
	}
	if len(est.MandatoryItems) != 1 || est.MandatoryItems[0].Key != "userdata/media/books" {
		t.Errorf("MandatoryItems = %+v", est.MandatoryItems)
	}
	if len(est.OptionalItems) != 1 || est.OptionalItems[0].Key != "userdata/media/comics" {
		t.Errorf("OptionalItems = %+v", est.OptionalItems)
	}
	if len(est.ExcludedItems) != 1 || est.ExcludedItems[0].Key != "userdata/media/movies" {
		t.Errorf("ExcludedItems = %+v", est.ExcludedItems)
	}
}
