package usecase

import (
	"context"
	"testing"

	"haixun-backend/internal/model/job/domain/entity"
	"haixun-backend/internal/model/job/domain/enum"
	domusecase "haixun-backend/internal/model/job/domain/usecase"
	jobrepo "haixun-backend/internal/model/job/repository"

	goredis "github.com/redis/go-redis/v9"
)

func TestRequestCancel_RunningSetsRedisCancelSignal(t *testing.T) {
	ctx := context.Background()
	client := goredis.NewClient(&goredis.Options{Addr: "127.0.0.1:6379"})
	if err := client.Ping(ctx).Err(); err != nil {
		t.Skip("redis not available:", err)
	}
	defer client.Close()

	jobID := "507f1f77bcf86cd799439011"
	template := demoTemplate()
	run := &entity.Run{
		TemplateType: template.Type,
		Status:       enum.RunStatusRunning,
		WorkerType:   string(enum.WorkerTypeGo),
	}

	uc := NewUseCase(
		&memoryTemplateRepo{template: template},
		newMemoryRunRepo(run),
		&memoryScheduleRepo{},
		&memoryEventRepo{},
		jobrepo.NewRedisQueueRepository(client),
	)

	updated, err := uc.RequestCancel(ctx, domusecase.CancelRunRequest{
		JobID:  jobID,
		Reason: "test cancel",
	})
	if err != nil {
		t.Fatalf("RequestCancel() error = %v", err)
	}
	if updated.Status != enum.RunStatusCancelRequested {
		t.Fatalf("status = %s, want cancel_requested", updated.Status)
	}

	value, err := client.Get(ctx, "jobs:cancel:"+jobID).Result()
	if err != nil {
		t.Fatalf("redis cancel key missing: %v", err)
	}
	if value != "test cancel" {
		t.Fatalf("cancel value = %q, want test cancel", value)
	}
	_ = client.Del(ctx, "jobs:cancel:"+jobID)
}

// A pending/queued run has no worker executing it yet, so cancelling it is
// always safe. This must succeed even for templates like publish-analytics
// that opt out of *cooperative* cancel (CancelPolicy.Supported = false),
// otherwise a not-yet-started job can never be removed from the queue.
func TestRequestCancel_PendingAllowedEvenWhenTemplateDoesNotSupportCancel(t *testing.T) {
	ctx := context.Background()
	template := publishAnalyticsTemplate()
	if template.CancelPolicy.Supported {
		t.Fatal("test setup assumes publish-analytics does not support cooperative cancel")
	}
	runs := newMemoryRunRepo(&entity.Run{
		TemplateType: template.Type,
		Status:       enum.RunStatusPending,
		WorkerType:   template.Steps[0].WorkerType,
	})
	uc := testUseCaseFull(template, runs, nil, newMemoryQueueRepo())

	updated, err := uc.RequestCancel(ctx, domusecase.CancelRunRequest{
		JobID:  "507f1f77bcf86cd799439011",
		Reason: "user requested cancel",
	})
	if err != nil {
		t.Fatalf("RequestCancel() error = %v", err)
	}
	if updated.Status != enum.RunStatusCancelled {
		t.Fatalf("status = %s, want cancelled", updated.Status)
	}
}

// Cooperative cancel (interrupting an already-running worker) must still
// respect the template's CancelPolicy.
func TestRequestCancel_RunningRejectedWhenTemplateDoesNotSupportCancel(t *testing.T) {
	ctx := context.Background()
	template := publishAnalyticsTemplate()
	runs := newMemoryRunRepo(&entity.Run{
		TemplateType: template.Type,
		Status:       enum.RunStatusRunning,
		WorkerType:   template.Steps[0].WorkerType,
	})
	uc := testUseCaseFull(template, runs, nil, newMemoryQueueRepo())

	_, err := uc.RequestCancel(ctx, domusecase.CancelRunRequest{
		JobID:  "507f1f77bcf86cd799439011",
		Reason: "user requested cancel",
	})
	if err == nil {
		t.Fatal("expected error cancelling a running job whose template does not support cooperative cancel")
	}
}
