package runner

import (
	"archive/zip"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// writeZip builds a zip at path from the given name→content entries (dirs implied
// by trailing slash). Returns the path.
func writeZip(t *testing.T, path string, entries map[string]string) {
	t.Helper()
	f, err := os.Create(path)
	if err != nil {
		t.Fatalf("create zip: %v", err)
	}
	defer f.Close()
	zw := zip.NewWriter(f)
	for name, content := range entries {
		w, err := zw.Create(name)
		if err != nil {
			t.Fatalf("zip create entry %s: %v", name, err)
		}
		if _, err := w.Write([]byte(content)); err != nil {
			t.Fatalf("zip write %s: %v", name, err)
		}
	}
	if err := zw.Close(); err != nil {
		t.Fatalf("close zip: %v", err)
	}
}

func TestExtractAndFindAEP(t *testing.T) {
	dir := t.TempDir()
	zipPath := filepath.Join(dir, "bundle.zip")
	// Realistic bundle: project + footage, plus a macOS resource-fork decoy that
	// must NOT be chosen as the project file.
	writeZip(t, zipPath, map[string]string{
		"MyTemplate/template.aep":          "AEP-DATA",
		"MyTemplate/footage/clip.mp4":      "VIDEO",
		"MyTemplate/fonts/Vazir.ttf":       "FONT",
		"__MACOSX/MyTemplate/._template.aep": "RESOURCE-FORK",
	})

	dest := filepath.Join(dir, "extracted")
	if err := ExtractZip(zipPath, dest); err != nil {
		t.Fatalf("ExtractZip: %v", err)
	}

	// footage + font extracted
	if _, err := os.Stat(filepath.Join(dest, "MyTemplate", "footage", "clip.mp4")); err != nil {
		t.Errorf("footage not extracted: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dest, "MyTemplate", "fonts", "Vazir.ttf")); err != nil {
		t.Errorf("font not extracted: %v", err)
	}

	aep, err := FindAEP(dest)
	if err != nil {
		t.Fatalf("FindAEP: %v", err)
	}
	if filepath.Base(aep) != "template.aep" {
		t.Errorf("expected template.aep, got %s", aep)
	}
	if strings.Contains(aep, "__MACOSX") || strings.Contains(filepath.Base(aep), "._") {
		t.Errorf("FindAEP picked a macOS resource-fork decoy: %s", aep)
	}
}

func TestFindAEPPrefersShallowAndAepOverAepx(t *testing.T) {
	dir := t.TempDir()
	// deep .aepx + shallow .aep — shallow .aep must win.
	mustWrite(t, filepath.Join(dir, "a", "b", "deep.aepx"), "x")
	mustWrite(t, filepath.Join(dir, "root.aep"), "x")
	aep, err := FindAEP(dir)
	if err != nil {
		t.Fatalf("FindAEP: %v", err)
	}
	if filepath.Base(aep) != "root.aep" {
		t.Errorf("expected root.aep (shallow, .aep), got %s", aep)
	}
}

func TestFindAEPNoneFound(t *testing.T) {
	dir := t.TempDir()
	mustWrite(t, filepath.Join(dir, "readme.txt"), "no project here")
	if _, err := FindAEP(dir); err == nil {
		t.Error("expected error when no .aep present, got nil")
	}
}

func TestExtractZipRejectsZipSlip(t *testing.T) {
	dir := t.TempDir()
	zipPath := filepath.Join(dir, "evil.zip")
	writeZip(t, zipPath, map[string]string{
		"../escape.txt": "pwned",
	})
	dest := filepath.Join(dir, "extracted")
	if err := ExtractZip(zipPath, dest); err == nil {
		t.Error("expected zip-slip to be rejected, got nil error")
	}
	if _, err := os.Stat(filepath.Join(dir, "escape.txt")); err == nil {
		t.Error("zip-slip wrote a file outside the destination dir")
	}
}

func mustWrite(t *testing.T, path, content string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
}
