package asynctask_test

import (
	"context"
	"testing"
	"time"

	"gorm.io/gorm"

	storepkg "github.com/break/junhong_cmp_fiber/internal/infrastructure/asynctask"
	"github.com/break/junhong_cmp_fiber/internal/testutil"
	contract "github.com/break/junhong_cmp_fiber/pkg/asynctask"
)

func TestPostgresTaskTransitionsAreConditionalAndRecoverExpiredLease(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	createTaskContractTable(t, db)
	store, err := storepkg.NewStore(db, taskDefinition())
	if err != nil {
		t.Fatalf("创建任务契约 Store 失败：%v", err)
	}
	now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
	if err := db.Exec("INSERT INTO test_async_contract_task (id, status, updated_at) VALUES (1, 1, ?)", now).Error; err != nil {
		t.Fatalf("准备待处理任务失败：%v", err)
	}
	claimed, err := store.Claim(context.Background(), 1, "worker-a", now, time.Minute)
	if err != nil || !claimed {
		t.Fatalf("领取待处理任务失败：%v，领取：%v", err, claimed)
	}
	claimed, err = store.Claim(context.Background(), 1, "worker-b", now.Add(30*time.Second), time.Minute)
	if err != nil || claimed {
		t.Fatalf("不得抢占有效租约：%v，领取：%v", err, claimed)
	}
	claimed, err = store.Claim(context.Background(), 1, "worker-b", now.Add(2*time.Minute), time.Minute)
	if err != nil || !claimed {
		t.Fatalf("过期任务应由新 Worker 恢复：%v，领取：%v", err, claimed)
	}
	renewed, err := store.Renew(context.Background(), 1, "worker-a", now.Add(150*time.Second), 2*time.Minute)
	if err != nil || renewed {
		t.Fatalf("旧租约所有者不得续租：%v，续租：%v", err, renewed)
	}
	renewed, err = store.Renew(context.Background(), 1, "worker-b", now.Add(150*time.Second), 2*time.Minute)
	if err != nil || !renewed {
		t.Fatalf("有效租约所有者续租失败：%v，续租：%v", err, renewed)
	}
	claimed, err = store.Claim(context.Background(), 1, "worker-c", now.Add(3*time.Minute), time.Minute)
	if err != nil || claimed {
		t.Fatalf("续租后不得被其他 Worker 领取：%v，领取：%v", err, claimed)
	}
	finished, err := store.Finish(context.Background(), 1, "worker-a", contract.TerminalResult{
		TaskID: "1", Status: contract.StatusCompleted, TotalCount: 10, SuccessCount: 7, FailedCount: 3, UpdatedAt: now,
	}, now.Add(3*time.Minute))
	if err != nil || finished {
		t.Fatalf("旧租约所有者不得完成任务：%v，完成：%v", err, finished)
	}
	finished, err = store.Finish(context.Background(), 1, "worker-b", contract.TerminalResult{
		TaskID: "1", Status: contract.StatusCompleted, TotalCount: 10, SuccessCount: 7, FailedCount: 3, UpdatedAt: now,
	}, now.Add(3*time.Minute))
	if err != nil || !finished {
		t.Fatalf("当前租约所有者完成任务失败：%v，完成：%v", err, finished)
	}
	claimed, err = store.Claim(context.Background(), 1, "worker-c", now.Add(4*time.Minute), time.Minute)
	if err != nil || claimed {
		t.Fatalf("终态重复消费必须无副作用：%v，领取：%v", err, claimed)
	}
	cancelled, err := store.Cancel(context.Background(), 1, now.Add(4*time.Minute))
	if err != nil || cancelled {
		t.Fatalf("终态任务不得再次取消：%v，取消：%v", err, cancelled)
	}

	var row struct {
		Status  int
		Total   int
		Success int
		Failed  int
	}
	if err := db.Table("test_async_contract_task").Select("status, total_count AS total, success_count AS success, failed_count AS failed").Where("id = 1").Scan(&row).Error; err != nil {
		t.Fatalf("读取任务终态失败：%v", err)
	}
	if row.Status != contract.StatusCompleted || row.Total != 10 || row.Success != 7 || row.Failed != 3 {
		t.Fatalf("任务终态计数错误：%+v", row)
	}
}

func TestPostgresTaskContractSupportsWholeFailureAndCancellation(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	createTaskContractTable(t, db)
	store, _ := storepkg.NewStore(db, taskDefinition())
	now := time.Now().UTC()
	if err := db.Exec("INSERT INTO test_async_contract_task (id, status, updated_at) VALUES (2, 1, ?), (3, 1, ?)", now, now).Error; err != nil {
		t.Fatalf("准备任务失败：%v", err)
	}
	claimed, _ := store.Claim(context.Background(), 2, "worker", now, time.Minute)
	if !claimed {
		t.Fatal("整体失败任务领取失败")
	}
	finished, err := store.Finish(context.Background(), 2, "worker", contract.TerminalResult{
		TaskID: "2", Status: contract.StatusFailed, ErrorCode: "FILE_PARSE_FAILED", ErrorSummary: "文件无法解析", UpdatedAt: now,
	}, now)
	if err != nil || !finished {
		t.Fatalf("整体失败终态更新失败：%v", err)
	}
	cancelled, err := store.Cancel(context.Background(), 3, now)
	if err != nil || !cancelled {
		t.Fatalf("待处理任务取消失败：%v", err)
	}
}

func taskDefinition() storepkg.Definition {
	return storepkg.Definition{
		Table: "test_async_contract_task", IDColumn: "id", StatusColumn: "status",
		LeaseOwnerColumn: "lease_owner", LeaseExpiresColumn: "lease_expires_at",
		TotalColumn: "total_count", SuccessColumn: "success_count", FailedColumn: "failed_count",
		ProgressColumn: "progress", ErrorCodeColumn: "error_code", ErrorSummaryColumn: "error_summary",
		StartedAtColumn: "started_at", CompletedAtColumn: "completed_at", UpdatedAtColumn: "updated_at",
	}
}

func createTaskContractTable(t *testing.T, db *gorm.DB) {
	t.Helper()
	if err := db.Exec(`CREATE TEMP TABLE test_async_contract_task (
		id bigint PRIMARY KEY, status integer NOT NULL, total_count integer NOT NULL DEFAULT 0,
		success_count integer NOT NULL DEFAULT 0, failed_count integer NOT NULL DEFAULT 0,
		progress integer NOT NULL DEFAULT 0, error_code varchar(100) NOT NULL DEFAULT '',
		error_summary varchar(500) NOT NULL DEFAULT '', lease_owner varchar(100), lease_expires_at timestamptz,
		started_at timestamptz, completed_at timestamptz, updated_at timestamptz NOT NULL
	) ON COMMIT DROP`).Error; err != nil {
		t.Fatalf("创建任务契约测试表失败：%v", err)
	}
}
