package executor

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"log/slog"
	"testing"

	"tangled.org/core/api/tangled"
	millv1 "tangled.org/core/spindle/mill/proto/gen"
	"tangled.org/core/spindle/models"
)

type placementEngine struct {
	*fakeEngine
	capabilities  []string
	validationErr error
	validated     bool
}

func (e *placementEngine) Capabilities() []string {
	return e.capabilities
}

func (e *placementEngine) ValidateWorkflowPlacement(*models.Workflow) error {
	e.validated = true
	return e.validationErr
}

func TestPushSnapshotAdvertisesCanonicalPerEngineCapabilities(t *testing.T) {
	enc := newCaptureEncoder()
	eng := &placementEngine{
		fakeEngine:   &fakeEngine{},
		capabilities: []string{"runner/qemu", "image/nixos", "runner/qemu"},
	}
	e := &Executor{
		l:       slog.New(slog.NewTextHandler(io.Discard, nil)),
		enc:     enc,
		seats:   2,
		engines: map[string]models.Engine{"microvm": eng},
		active:  make(map[string]*reservation),
	}

	e.pushSnapshot()
	availability := (<-enc.messages).GetNodeSnapshot().GetEngines()["microvm"]
	if availability == nil {
		t.Fatal("snapshot omitted microvm availability")
	}
	got := availability.GetCapabilities()
	want := []string{"image/nixos", "runner/qemu"}
	if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
		t.Fatalf("snapshot capabilities = %v, want %v", got, want)
	}
}

func TestHandleReserveValidatesPlacementBeforeAcquiringSlot(t *testing.T) {
	enc := newCaptureEncoder()
	validationErr := errors.New("image architecture is not native")
	eng := &placementEngine{fakeEngine: &fakeEngine{}, validationErr: validationErr}
	e := &Executor{
		l:       slog.New(slog.NewTextHandler(io.Discard, nil)),
		enc:     enc,
		seats:   1,
		engines: map[string]models.Engine{"microvm": eng},
		active:  make(map[string]*reservation),
	}
	twf, err := json.Marshal(tangled.Pipeline_Workflow{Name: "build"})
	if err != nil {
		t.Fatal(err)
	}
	tpl, err := json.Marshal(tangled.Pipeline{})
	if err != nil {
		t.Fatal(err)
	}

	e.handleReserve(context.Background(), &millv1.ReserveSeat{
		LeaseId:         "lease-1",
		TargetEngine:    "microvm",
		RawWorkflowJson: string(twf),
		RawPipelineJson: string(tpl),
		Knot:            "k",
		Rkey:            "r",
	})

	result := (<-enc.messages).GetReserveResult()
	if result == nil {
		t.Fatal("handleReserve() did not send ReserveResult")
	}
	if result.GetAccepted() {
		t.Fatal("handleReserve() accepted placement validation failure")
	}
	if result.GetRejectClass() != millv1.RejectClass_REJECT_CLASS_INCOMPATIBLE {
		t.Fatalf("reject class = %v, want incompatible", result.GetRejectClass())
	}
	if !eng.validated {
		t.Fatal("placement validator was not called")
	}
	if eng.acquireCalled {
		t.Fatal("slot acquisition ran after placement validation failed")
	}
	if len(e.active) != 0 {
		t.Fatalf("active reservations = %d, want 0", len(e.active))
	}
}
