package run

import (
	"context"
	"runtime"
	"sync"
	"testing"
	"time"

	"github.com/google/uuid"
)

// fakeRepo 仅实现 supervisor 实际用到的 UpdateRunProfileExit；其余满足接口即可。
type fakeRepo struct {
	mu       sync.Mutex
	exitID   uuid.UUID
	exitCode *int32
	lastErr  string
	called   bool
}

func (f *fakeRepo) CreateRunProfile(context.Context, *RunProfile) (*RunProfile, error) {
	return nil, nil
}
func (f *fakeRepo) GetRunProfileByID(context.Context, uuid.UUID) (*RunProfile, error) {
	return nil, nil
}
func (f *fakeRepo) ListRunProfilesByWorkspace(context.Context, uuid.UUID) ([]*RunProfile, error) {
	return nil, nil
}
func (f *fakeRepo) UpdateRunProfile(context.Context, *RunProfile) (*RunProfile, error) {
	return nil, nil
}
func (f *fakeRepo) DeleteRunProfile(context.Context, uuid.UUID) error { return nil }
func (f *fakeRepo) MarkRunProfileStarted(context.Context, uuid.UUID) (*RunProfile, error) {
	return nil, nil
}
func (f *fakeRepo) UpdateRunProfileExit(_ context.Context, id uuid.UUID, exitCode *int32, lastErr string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.called = true
	f.exitID = id
	f.exitCode = exitCode
	f.lastErr = lastErr
	return nil
}

func testCfg() SupervisorConfig {
	return SupervisorConfig{
		KillGrace:      2 * time.Second,
		OutBufferLines: 100,
		OutTailBytes:   2000,
		WSSendBuffer:   16,
		ShutdownGrace:  5 * time.Second,
	}
}

func quickExit() SpawnParams {
	p := SpawnParams{ProfileID: uuid.New()}
	if runtime.GOOS == "windows" {
		p.Command, p.Args = "cmd", []string{"/c", "exit", "0"}
	} else {
		p.Command, p.Args = "sh", []string{"-c", "exit 0"}
	}
	return p
}

func sleeper() SpawnParams {
	p := SpawnParams{ProfileID: uuid.New()}
	if runtime.GOOS == "windows" {
		p.Command, p.Args = "cmd", []string{"/c", "timeout", "/t", "30", "/nobreak"}
	} else {
		p.Command, p.Args = "sh", []string{"-c", "sleep 30"}
	}
	return p
}

// TestSpawn_SelfExit_RecordsCrash 验证：未被我方终止而自行退出 → crashed，
// 且 onExit 把退出码落库（monitor 在 close(done) 前已完成 onExit）。
func TestSpawn_SelfExit_RecordsCrash(t *testing.T) {
	repo := &fakeRepo{}
	sup := NewSupervisor(repo, testCfg(), nil)
	params := quickExit()

	proc, err := sup.Spawn(params)
	if err != nil {
		t.Fatalf("spawn: %v", err)
	}
	select {
	case <-proc.done:
	case <-time.After(10 * time.Second):
		t.Fatal("process did not exit")
	}

	if got := sup.StatusOf(params.ProfileID); got != StatusCrashed {
		t.Fatalf("status = %q, want crashed (self-exit not initiated by us)", got)
	}
	repo.mu.Lock()
	defer repo.mu.Unlock()
	if !repo.called {
		t.Fatal("UpdateRunProfileExit not called on exit")
	}
	if repo.exitID != params.ProfileID {
		t.Fatalf("exit recorded for wrong profile: %v", repo.exitID)
	}
	if repo.exitCode == nil || *repo.exitCode != 0 {
		t.Fatalf("exit code = %v, want 0", repo.exitCode)
	}
}

// TestStop_TerminatesAndMarksStopped 验证 Stop 整树终止并判定为 stopped。
func TestStop_TerminatesAndMarksStopped(t *testing.T) {
	repo := &fakeRepo{}
	sup := NewSupervisor(repo, testCfg(), nil)
	params := sleeper()

	proc, err := sup.Spawn(params)
	if err != nil {
		t.Fatalf("spawn: %v", err)
	}
	if got := sup.StatusOf(params.ProfileID); got != StatusRunning {
		t.Fatalf("status = %q, want running", got)
	}

	if err := sup.Stop(context.Background(), params.ProfileID, testCfg().KillGrace); err != nil {
		t.Fatalf("stop: %v", err)
	}
	// Stop 已等 done；onExit 在 close(done) 前完成。
	select {
	case <-proc.done:
	default:
		t.Fatal("done not closed after Stop")
	}
	if got := sup.StatusOf(params.ProfileID); got != StatusStopped {
		t.Fatalf("status = %q, want stopped", got)
	}
	if _, ok := sup.Get(params.ProfileID); ok {
		t.Fatal("proc still registered after stop")
	}
}

// TestSpawn_AlreadyRunning 验证同一 profile 不能并发拉起两个进程。
func TestSpawn_AlreadyRunning(t *testing.T) {
	sup := NewSupervisor(&fakeRepo{}, testCfg(), nil)
	params := sleeper()

	if _, err := sup.Spawn(params); err != nil {
		t.Fatalf("first spawn: %v", err)
	}
	defer func() { _ = sup.Stop(context.Background(), params.ProfileID, testCfg().KillGrace) }()

	if _, err := sup.Spawn(params); err != ErrAlreadyRunning {
		t.Fatalf("second spawn err = %v, want ErrAlreadyRunning", err)
	}
}

// TestStop_NotRunning 验证停止未运行的 profile 返回 ErrNotRunning。
func TestStop_NotRunning(t *testing.T) {
	sup := NewSupervisor(&fakeRepo{}, testCfg(), nil)
	if err := sup.Stop(context.Background(), uuid.New(), testCfg().KillGrace); err != ErrNotRunning {
		t.Fatalf("err = %v, want ErrNotRunning", err)
	}
}
