package engine

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"github.com/google/uuid"

	"git.cloudinit.dev/coreci/orca/internal/model"
	"git.cloudinit.dev/coreci/orca/internal/store"
)

func newTestExecutor(t *testing.T) (*Executor, func()) {
	t.Helper()
	path := filepath.Join(t.TempDir(), "test.db")
	db, err := store.Open(path)
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	ex := NewExecutor(store.NewJobRepo(db), store.NewTaskRepo(db), nil)
	return ex, func() { _ = db.Close() }
}

func TestExecutor_Submit_Success(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	ctx := context.Background()
	spec := []byte(`{"command":"/bin/echo","args":["hello"]}`)
	jobID, err := ex.Submit(ctx, spec)
	if err != nil {
		t.Fatalf("Submit: %v", err)
	}
	if jobID == "" {
		t.Fatal("Submit: empty jobID")
	}
	status, err := ex.Status(ctx, jobID)
	if err != nil {
		t.Fatalf("Status: %v", err)
	}
	if status != string(model.JobStatusComplete) {
		t.Errorf("Status: got %q, want %q", status, model.JobStatusComplete)
	}
}

func TestExecutor_Submit_MissingCommand(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	_, err := ex.Submit(context.Background(), []byte(`{"name":"x"}`))
	if err == nil {
		t.Fatal("Submit: expected error for missing command, got nil")
	}
}

func TestExecutor_Submit_MalformedJSON(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	_, err := ex.Submit(context.Background(), []byte(`{bad json`))
	if err == nil {
		t.Fatal("Submit: expected error for malformed JSON, got nil")
	}
}

func TestExecutor_Submit_FailingCommand(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	ctx := context.Background()
	jobID, err := ex.Submit(ctx, []byte(`{"command":"/bin/false"}`))
	if err == nil {
		t.Fatal("Submit failing command: expected error, got nil")
	}
	if jobID == "" {
		t.Fatal("Submit failing command: empty jobID")
	}
	status, err := ex.Status(ctx, jobID)
	if err != nil {
		t.Fatalf("Status: %v", err)
	}
	if status != string(model.JobStatusFailed) {
		t.Errorf("Status: got %q, want %q", status, model.JobStatusFailed)
	}
}

func TestExecutor_Status_NotFound(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	_, err := ex.Status(context.Background(), "nonexistent-job-id")
	if err == nil {
		t.Fatal("Status: expected error for missing job, got nil")
	}
}

func TestExecutor_Run_Success(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	ctx := context.Background()
	job := &model.Job{
		ID:     uuid.NewString(),
		Name:   "run-success",
		Spec:   "{}",
		Status: model.JobStatusPending,
	}
	specs := []TaskSpec{{Name: "echo", Command: "/bin/echo", Args: []string{"hi"}}}
	if err := ex.Run(ctx, job, specs); err != nil {
		t.Fatalf("Run: %v", err)
	}
	got, err := ex.Status(ctx, job.ID)
	if err != nil {
		t.Fatalf("Status: %v", err)
	}
	if got != string(model.JobStatusComplete) {
		t.Errorf("Status: got %q, want %q", got, model.JobStatusComplete)
	}
}

func TestExecutor_Run_ContextCancel(t *testing.T) {
	ex, cleanup := newTestExecutor(t)
	defer cleanup()
	ctx, cancel := context.WithCancel(context.Background())
	job := &model.Job{
		ID:     uuid.NewString(),
		Name:   "run-cancel",
		Spec:   "{}",
		Status: model.JobStatusPending,
	}
	specs := []TaskSpec{{Name: "sleep", Command: "/bin/sleep", Args: []string{"10"}}}

	go func() {
		time.Sleep(100 * time.Millisecond)
		cancel()
	}()

	err := ex.Run(ctx, job, specs)
	if err == nil {
		t.Fatal("Run: expected error after context cancel, got nil")
	}
	status, sErr := ex.Status(context.Background(), job.ID)
	if sErr != nil {
		t.Fatalf("Status after cancel: %v", sErr)
	}
	if status == string(model.JobStatusComplete) {
		t.Errorf("Status: got %q, want not complete (task should have been killed)", status)
	}
}
