package main

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

// fakeServer writes an executable standing in for llama-server: it ignores
// SIGTERM the way the real one effectively does — by dying messily rather than
// cleanly — and reports a non-zero status.
func fakeServer(t *testing.T, body string) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "fake-llama-server")
	if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body+"\n"), 0o755); err != nil {
		t.Fatal(err)
	}
	return path
}

// A deliberate stop is a yield, and the log has to say so.
//
// llama-server aborts inside its own static teardown on SIGTERM, so the exit
// status of a routine yield is identical to that of a real crash. Reading the
// mavgpud log, the two were indistinguishable (Vikunja #491).
func TestStopMarksTheExitAsAYield(t *testing.T) {
	r := newRunner(fakeServer(t, "while : ; do sleep 1 ; done"), nil, "")
	if err := r.start(); err != nil {
		t.Fatalf("start: %v", err)
	}
	r.mu.Lock()
	if r.yielding {
		t.Error("a freshly started server is already marked as yielding")
	}
	r.mu.Unlock()

	r.stop(2 * time.Second)
	deadline := time.Now().Add(2 * time.Second)
	for time.Now().Before(deadline) {
		if !r.running() {
			return
		}
		time.Sleep(10 * time.Millisecond)
	}
	t.Fatal("the child outlived stop")
}

// Stopping when nothing is running must not arm the flag for the next child.
// The next exit after that would be a real crash logged as a yield.
func TestStopWithNoChildDoesNotArmTheFlag(t *testing.T) {
	r := newRunner("/nonexistent", nil, "")
	r.stop(10 * time.Millisecond)
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.yielding {
		t.Error("stop armed the yield flag with no child running")
	}
}
