package backend

import (
	"context"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func TestNewMediaExtractWorkspaceDefaultsToSystemTempDir(t *testing.T) {
	w, err := NewMediaExtractWorkspace(MediaExtractWorkspaceOptions{})
	if err != nil {
		t.Fatal(err)
	}
	if got, want := w.RootDir(), os.TempDir(); got != want {
		t.Fatalf("root=%q, want %q", got, want)
	}
}

func TestMediaExtractWorkspaceDefaultConcurrencyAllowsFourTasks(t *testing.T) {
	w, err := NewMediaExtractWorkspace(MediaExtractWorkspaceOptions{RootDir: t.TempDir()})
	if err != nil {
		t.Fatal(err)
	}
	var tasks []*MediaExtractTaskWorkspace
	defer func() {
		for _, task := range tasks {
			_ = task.Close()
		}
	}()
	for i := 0; i < 4; i++ {
		ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
		task, taskErr := w.NewTask(ctx, "default")
		cancel()
		if taskErr != nil {
			t.Fatalf("task %d: %v", i+1, taskErr)
		}
		tasks = append(tasks, task)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
	defer cancel()
	if _, err := w.NewTask(ctx, "fifth"); !errors.Is(err, context.DeadlineExceeded) {
		t.Fatalf("fifth task error=%v, want context deadline while four slots are held", err)
	}
}

func TestMediaExtractWorkspaceBoundsConcurrentTasks(t *testing.T) {
	w, err := NewMediaExtractWorkspace(MediaExtractWorkspaceOptions{RootDir: t.TempDir(), MaxConcurrentTasks: 1})
	if err != nil {
		t.Fatal(err)
	}
	first, err := w.NewTask(context.Background(), "first")
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
	defer cancel()
	if _, err := w.NewTask(ctx, "second"); !errors.Is(err, context.DeadlineExceeded) {
		t.Fatalf("second task error=%v, want context deadline while slot is held", err)
	}
	if err := first.Close(); err != nil {
		t.Fatal(err)
	}
	second, err := w.NewTask(context.Background(), "second")
	if err != nil {
		t.Fatal(err)
	}
	_ = second.Close()
}

func TestMediaExtractWorkspaceStagesAndCleansTaskFiles(t *testing.T) {
	root := t.TempDir()
	w, err := NewMediaExtractWorkspace(MediaExtractWorkspaceOptions{RootDir: root})
	if err != nil {
		t.Fatal(err)
	}
	task, err := w.NewTask(context.Background(), "task/with-slash")
	if err != nil {
		t.Fatal(err)
	}
	path, n, err := task.StageReader(context.Background(), "source.bin", strings.NewReader("hello"), 16)
	if err != nil {
		t.Fatal(err)
	}
	if n != 5 {
		t.Fatalf("bytes=%d, want 5", n)
	}
	if filepath.Dir(path) != task.Dir() {
		t.Fatalf("path=%q is not under task dir %q", path, task.Dir())
	}
	if err := task.Close(); err != nil {
		t.Fatal(err)
	}
	if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) {
		t.Fatalf("staged file still exists, stat err=%v", err)
	}
}

func TestMediaExtractWorkspaceRejectsSourceOverLimit(t *testing.T) {
	w, err := NewMediaExtractWorkspace(MediaExtractWorkspaceOptions{RootDir: t.TempDir()})
	if err != nil {
		t.Fatal(err)
	}
	task, err := w.NewTask(context.Background(), "task")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = task.Close() }()
	_, _, err = task.StageReader(context.Background(), "source.bin", strings.NewReader("0123456789"), 5)
	if !errors.Is(err, ErrMediaExtractSourceTooLarge) {
		t.Fatalf("err=%v, want ErrMediaExtractSourceTooLarge", err)
	}
}

func TestMediaExtractWorkspaceHonorsCanceledContext(t *testing.T) {
	w, err := NewMediaExtractWorkspace(MediaExtractWorkspaceOptions{RootDir: t.TempDir()})
	if err != nil {
		t.Fatal(err)
	}
	task, err := w.NewTask(context.Background(), "task")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = task.Close() }()
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	_, _, err = task.StageReader(ctx, "source.bin", strings.NewReader("hello"), 16)
	if !errors.Is(err, context.Canceled) {
		t.Fatalf("err=%v, want context.Canceled", err)
	}
	if _, err := io.ReadAll(strings.NewReader("still-compiles")); err != nil {
		t.Fatal(err)
	}
}
