package runeffect

import (
	"testing"
	"time"
)

func TestStatusLifecycleHelpers(t *testing.T) {
	cases := []struct {
		value    string
		status   Status
		terminal bool
		active   bool
	}{
		{" running ", StatusRunning, false, true},
		{"retry_waiting", StatusRetryWaiting, false, true},
		{"succeeded", StatusSucceeded, true, false},
		{"failed", StatusFailed, true, false},
		{"canceled", StatusCanceled, true, false},
		{"skipped", StatusSkipped, true, false},
		{"duplicate", StatusDuplicate, true, false},
	}
	for _, tc := range cases {
		status, err := NormalizeStatus(tc.value)
		if err != nil {
			t.Fatalf("NormalizeStatus(%q) returned error: %v", tc.value, err)
		}
		if status != tc.status {
			t.Fatalf("NormalizeStatus(%q)=%q, want %q", tc.value, status, tc.status)
		}
		if status.IsTerminal() != tc.terminal {
			t.Fatalf("%q terminal=%v, want %v", status, status.IsTerminal(), tc.terminal)
		}
		if status.IsActive() != tc.active {
			t.Fatalf("%q active=%v, want %v", status, status.IsActive(), tc.active)
		}
	}
	if _, err := NormalizeStatus("unknown"); err == nil {
		t.Fatal("NormalizeStatus accepted an unknown value")
	}
}

func TestEffectNormalizationAndValidation(t *testing.T) {
	message, err := (Effect{Kind: EffectKindMessage, MessageID: " msg_1 "}).Normalize()
	if err != nil {
		t.Fatalf("normalize message effect: %v", err)
	}
	if message.Ref != "msg_1" || message.MessageID != "msg_1" {
		t.Fatalf("message effect was not normalized: %+v", message)
	}
	if err := message.Validate(); err != nil {
		t.Fatalf("message effect should validate: %v", err)
	}

	discussion := TaskDiscussionEffect(" entry_1 ")
	if discussion.Ref != "entry_1" || discussion.MessageID != "" {
		t.Fatalf("task discussion effect was not normalized by constructor: %+v", discussion)
	}
	if err := discussion.Validate(); err != nil {
		t.Fatalf("task discussion effect should validate: %v", err)
	}

	if err := (Effect{Kind: EffectKindMessage, Ref: "msg_1", MessageID: "msg_2"}).Validate(); err == nil {
		t.Fatal("message effect accepted divergent ref/message id")
	}
	if err := (Effect{Kind: EffectKindTaskDiscussion, Ref: "entry_1", MessageID: "msg_1"}).Validate(); err == nil {
		t.Fatal("task discussion effect accepted message id")
	}

	for _, effect := range []Effect{
		MessageUpdateEffect(" btn_1 ", " msg_1 "),
		NotificationResolvedEffect("btn_1", "msg_1"),
		MessageWithdrawalEffect("btn_1", "msg_1"),
	} {
		if err := effect.Validate(); err != nil {
			t.Fatalf("%s effect should validate: %v", effect.Kind, err)
		}
		if effect.Ref != "btn_1" || effect.MessageID != "msg_1" {
			t.Fatalf("%s effect was not normalized by constructor: %+v", effect.Kind, effect)
		}
	}
	if err := (Effect{Kind: EffectKindMessageUpdate, Ref: "btn_1"}).Validate(); err == nil {
		t.Fatal("message update effect accepted an empty message id")
	}
	if err := (Effect{Kind: EffectKindNotificationResolved, MessageID: "msg_1"}).Validate(); err == nil {
		t.Fatal("notification resolved effect accepted an empty ref")
	}
	if err := (Effect{Kind: EffectKindMessageWithdrawal, Ref: "btn_1", MessageID: "msg_1"}).Validate(); err != nil {
		t.Fatalf("message withdrawal effect should allow distinct ref and message id: %v", err)
	}
}

func TestContractValidation(t *testing.T) {
	now := time.Date(2026, 5, 30, 0, 0, 0, 0, time.UTC)
	completed := now.Add(time.Second)
	valid := []Contract{
		{Status: StatusRunning, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, Effect: NoEffect()},
		{Status: StatusRetryWaiting, IdempotencyKey: "run:key", AttemptCount: 2, AttemptedAt: &now, Effect: NoEffect(), Error: Error{Code: "transient"}},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: MessageEffect("msg_1")},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: TaskDiscussionEffect("entry_1")},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: NotificationResolvedEffect("btn_1", "msg_1")},
		{Status: StatusFailed, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: NoEffect(), Error: Error{Code: "target_not_found", Detail: "gone"}},
		{Status: StatusCanceled, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: NoEffect(), Error: Error{Code: "operator_canceled"}},
		{Status: StatusSkipped, IdempotencyKey: "run:key", AttemptCount: 0, Effect: NoEffect()},
		{Status: StatusDuplicate, IdempotencyKey: "run:key", AttemptCount: 0, Effect: NoEffect()},
	}
	for _, contract := range valid {
		if err := contract.Validate(); err != nil {
			t.Fatalf("contract should validate: %+v: %v", contract, err)
		}
	}

	invalid := []Contract{
		{Status: StatusRunning, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: NoEffect()},
		{Status: StatusRunning, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, Effect: MessageEffect("msg_1")},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, Effect: MessageEffect("msg_1")},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: NoEffect()},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: MessageEffect("msg_1"), Error: Error{Code: "bad"}},
		{Status: StatusFailed, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: NoEffect()},
		{Status: StatusFailed, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: MessageEffect("msg_1"), Error: Error{Code: "bad"}},
		{Status: StatusCanceled, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, Effect: NoEffect()},
		{Status: StatusCanceled, IdempotencyKey: "run:key", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: MessageEffect("msg_1")},
		{Status: StatusSucceeded, IdempotencyKey: "", AttemptCount: 1, AttemptedAt: &now, CompletedAt: &completed, Effect: MessageEffect("msg_1")},
		{Status: StatusSucceeded, IdempotencyKey: "run:key", AttemptCount: 1, CompletedAt: &completed, Effect: MessageEffect("msg_1")},
	}
	for _, contract := range invalid {
		if err := contract.Validate(); err == nil {
			t.Fatalf("contract should not validate: %+v", contract)
		}
	}
}
