package platform

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

// streamAll runs the full Begin → Append* → Finalize cycle for one session into
// dir and returns the final path. CDROP_NO_REVEAL keeps Finder/Explorer shut.
func streamAll(t *testing.T, dir, sessionID, name string, chunks ...[]byte) string {
	t.Helper()
	t.Setenv("CDROP_NO_REVEAL", "1")
	if err := BeginStreamingDownload(dir, sessionID); err != nil {
		t.Fatalf("Begin: %v", err)
	}
	for i, c := range chunks {
		if err := AppendStreamingDownload(sessionID, c); err != nil {
			t.Fatalf("Append %d: %v", i, err)
		}
	}
	path, err := FinalizeStreamingDownload(sessionID, name)
	if err != nil {
		t.Fatalf("Finalize: %v", err)
	}
	return path
}

func TestStreamingDownload_WritesAllBytes(t *testing.T) {
	dir := t.TempDir()
	path := streamAll(t, dir, "sess-1", "greeting.txt",
		[]byte("hello "), []byte("streamed "), []byte("world"))

	if got, want := path, filepath.Join(dir, "greeting.txt"); got != want {
		t.Errorf("path = %q, want %q", got, want)
	}
	got, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}
	if want := "hello streamed world"; string(got) != want {
		t.Errorf("content = %q, want %q", got, want)
	}
	// The .part temp must be gone after a successful finalize.
	if _, err := os.Stat(filepath.Join(dir, ".cdrop-sess-1.part")); !os.IsNotExist(err) {
		t.Errorf("temp file not cleaned up: %v", err)
	}
}

func TestStreamingDownload_NoCollisionOverwrite(t *testing.T) {
	dir := t.TempDir()
	// Pre-existing file with the same name must not be overwritten.
	if err := os.WriteFile(filepath.Join(dir, "f.bin"), []byte("original"), 0o644); err != nil {
		t.Fatal(err)
	}
	path := streamAll(t, dir, "sess-2", "f.bin", []byte("new"))

	if got, want := path, filepath.Join(dir, "f (1).bin"); got != want {
		t.Errorf("path = %q, want %q", got, want)
	}
	orig, _ := os.ReadFile(filepath.Join(dir, "f.bin"))
	if string(orig) != "original" {
		t.Errorf("original file was overwritten: %q", orig)
	}
}

func TestStreamingDownload_SanitizesTraversalName(t *testing.T) {
	dir := t.TempDir()
	// A crafted name with path components must collapse to a bare filename under
	// dir — it must never escape via "../".
	path := streamAll(t, dir, "sess-3", "../../etc/evil", []byte("x"))

	if filepath.Dir(path) != dir {
		t.Errorf("escaped dir: path = %q, dir = %q", path, dir)
	}
	if got := filepath.Base(path); got != "evil" {
		t.Errorf("base = %q, want %q", got, "evil")
	}
}

func TestStreamingDownload_AbortRemovesTemp(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("CDROP_NO_REVEAL", "1")
	if err := BeginStreamingDownload(dir, "sess-4"); err != nil {
		t.Fatalf("Begin: %v", err)
	}
	if err := AppendStreamingDownload("sess-4", []byte("partial")); err != nil {
		t.Fatalf("Append: %v", err)
	}
	tmp := filepath.Join(dir, ".cdrop-sess-4.part")
	if _, err := os.Stat(tmp); err != nil {
		t.Fatalf("temp should exist mid-stream: %v", err)
	}
	AbortStreamingDownload("sess-4")
	if _, err := os.Stat(tmp); !os.IsNotExist(err) {
		t.Errorf("temp file not removed after abort: %v", err)
	}
	// A second append after abort must error, not panic or write a stray file.
	if err := AppendStreamingDownload("sess-4", []byte("late")); err == nil {
		t.Error("Append after abort: want error, got nil")
	}
}

func TestStreamingDownload_ReBeginReplacesStale(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("CDROP_NO_REVEAL", "1")
	// First Begin + partial write, then a second Begin for the same session id
	// (a retried transfer) must truncate, not append to, the stale temp.
	if err := BeginStreamingDownload(dir, "sess-5"); err != nil {
		t.Fatalf("Begin 1: %v", err)
	}
	if err := AppendStreamingDownload("sess-5", []byte("STALE")); err != nil {
		t.Fatalf("Append 1: %v", err)
	}
	if err := BeginStreamingDownload(dir, "sess-5"); err != nil {
		t.Fatalf("Begin 2: %v", err)
	}
	if err := AppendStreamingDownload("sess-5", []byte("fresh")); err != nil {
		t.Fatalf("Append 2: %v", err)
	}
	path, err := FinalizeStreamingDownload("sess-5", "out.bin")
	if err != nil {
		t.Fatalf("Finalize: %v", err)
	}
	got, _ := os.ReadFile(path)
	if !bytes.Equal(got, []byte("fresh")) {
		t.Errorf("content = %q, want %q (stale bytes leaked)", got, "fresh")
	}
}

func TestStreamingDownload_FinalizeUnknownSession(t *testing.T) {
	if _, err := FinalizeStreamingDownload("never-began", "x"); err == nil {
		t.Error("Finalize unknown session: want error, got nil")
	}
}
