package asynctask_test

import (
	"testing"
	"time"

	"github.com/break/junhong_cmp_fiber/pkg/asynctask"
)

func TestCompletedTaskUsesCountsForPartialAndAllItemFailures(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
	cases := []struct {
		name    string
		success int
		failed  int
	}{
		{name: "部分成功", success: 7, failed: 3},
		{name: "业务项全部失败", success: 0, failed: 10},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			projection, err := asynctask.NewTerminalProjection(asynctask.TerminalResult{
				TaskID: "task-1", Status: asynctask.StatusCompleted,
				TotalCount: 10, SuccessCount: tc.success, FailedCount: tc.failed,
				StartedAt: &now, CompletedAt: &now, UpdatedAt: now,
			})
			if err != nil {
				t.Fatalf("构造任务投影失败：%v", err)
			}
			if projection.Status != asynctask.StatusCompleted || projection.StatusName != "已完成" {
				t.Fatalf("业务项处理到终点必须为已完成：%+v", projection)
			}
			if projection.Progress != 100 {
				t.Fatalf("终态进度必须为 100，得到：%d", projection.Progress)
			}
		})
	}
}

func TestTerminalProjectionRejectsBrokenCountInvariant(t *testing.T) {
	t.Parallel()

	_, err := asynctask.NewTerminalProjection(asynctask.TerminalResult{
		TaskID: "task-2", Status: asynctask.StatusCompleted,
		TotalCount: 10, SuccessCount: 5, FailedCount: 4,
		UpdatedAt: time.Now(),
	})
	if err == nil {
		t.Fatal("终态计数不守恒时应拒绝构造公开投影")
	}
}

func TestStructuredPayloadRejectsBytes(t *testing.T) {
	t.Parallel()

	if err := asynctask.ValidatePayload([]byte(`{"task_id":1}`)); err == nil {
		t.Fatal("预序列化 []byte 载荷必须被拒绝")
	}
	if err := asynctask.ValidatePayload(struct {
		TaskID uint `json:"task_id"`
	}{TaskID: 1}); err != nil {
		t.Fatalf("结构化载荷应通过校验：%v", err)
	}
}

func TestClaimRecoversOnlyExpiredProcessingTask(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
	activeExpiry := now.Add(time.Minute)
	active, err := asynctask.Claim(asynctask.LeaseState{
		Status: asynctask.StatusProcessing, LeaseOwner: "worker-a", LeaseExpiresAt: &activeExpiry,
	}, "worker-b", now, time.Minute)
	if err != nil {
		t.Fatalf("检查有效租约失败：%v", err)
	}
	if active.Claimed {
		t.Fatal("其他 Worker 不得抢占有效租约")
	}

	expiredAt := now.Add(-time.Second)
	recovered, err := asynctask.Claim(asynctask.LeaseState{
		Status: asynctask.StatusProcessing, LeaseOwner: "worker-a", LeaseExpiresAt: &expiredAt,
	}, "worker-b", now, time.Minute)
	if err != nil {
		t.Fatalf("恢复过期任务失败：%v", err)
	}
	if !recovered.Claimed || recovered.State.LeaseOwner != "worker-b" {
		t.Fatalf("过期任务应由新 Worker 恢复：%+v", recovered)
	}
}

func TestTerminalTaskCannotBeClaimedOrCancelledAgain(t *testing.T) {
	t.Parallel()

	result, err := asynctask.Claim(asynctask.LeaseState{Status: asynctask.StatusCompleted}, "worker-a", time.Now(), time.Minute)
	if err != nil {
		t.Fatalf("检查终态任务失败：%v", err)
	}
	if result.Claimed || asynctask.CanCancel(asynctask.StatusCompleted) {
		t.Fatal("终态任务不得被重复消费或取消")
	}
}
