package workflow

import (
	"encoding/json"
	"testing"
)

func TestNormalizeExternalRefDefaultsMetadata(t *testing.T) {
	ref, err := NormalizeExternalRef(&ExternalRefInput{
		Provider: " plane ",
		ID:       " work-item-1 ",
		URL:      " https://plane.example/work-item-1 ",
	})
	if err != nil {
		t.Fatalf("NormalizeExternalRef returned error: %v", err)
	}
	if ref.Provider == nil || *ref.Provider != "plane" {
		t.Fatalf("unexpected provider: %#v", ref.Provider)
	}
	if ref.ID == nil || *ref.ID != "work-item-1" {
		t.Fatalf("unexpected id: %#v", ref.ID)
	}
	if ref.URL == nil || *ref.URL != "https://plane.example/work-item-1" {
		t.Fatalf("unexpected url: %#v", ref.URL)
	}
	if string(ref.Metadata) != "{}" {
		t.Fatalf("unexpected metadata: %s", ref.Metadata)
	}
}

func TestNormalizeExternalRefRejectsInvalidMetadata(t *testing.T) {
	_, err := NormalizeExternalRef(&ExternalRefInput{
		Provider: "plane",
		Metadata: json.RawMessage(`{"bad"`),
	})
	if err == nil {
		t.Fatal("expected invalid metadata error")
	}
}

func TestNormalizeExternalRefRequiresProvider(t *testing.T) {
	_, err := NormalizeExternalRef(&ExternalRefInput{
		ID: "work-item-1",
	})
	if err == nil {
		t.Fatal("expected provider error")
	}
}

func TestNormalizeTaskMetadataDefaultsAndKeepsValidJSON(t *testing.T) {
	metadata, err := NormalizeTaskMetadata(nil)
	if err != nil {
		t.Fatalf("NormalizeTaskMetadata returned error: %v", err)
	}
	if string(metadata) != "{}" {
		t.Fatalf("unexpected default metadata: %s", metadata)
	}

	metadata, err = NormalizeTaskMetadata(json.RawMessage(`{"agent_phase":0}`))
	if err != nil {
		t.Fatalf("NormalizeTaskMetadata returned error: %v", err)
	}
	if string(metadata) != `{"agent_phase":0}` {
		t.Fatalf("unexpected metadata: %s", metadata)
	}
}

func TestNormalizeTaskMetadataRejectsInvalidJSON(t *testing.T) {
	_, err := NormalizeTaskMetadata(json.RawMessage(`{"agent_phase"`))
	if err == nil {
		t.Fatal("expected invalid metadata error")
	}
}

func TestLifecycleStartTaskAllowsQueuedAndMarksRunning(t *testing.T) {
	tests := []struct {
		from   TaskStatus
		to     TaskStatus
		wanted bool
	}{
		{StatusPending, StatusRunning, true},
		{StatusQueued, StatusRunning, true},
	}

	for _, tt := range tests {
		got := canTransition(tt.from, tt.to)
		if got != tt.wanted {
			t.Errorf("canTransition(%s, %s) = %v; want %v", tt.from, tt.to, got, tt.wanted)
		}
	}
}

func TestCanTransitionRejectsInvalidLifecycleMove(t *testing.T) {
	tests := []struct {
		from   TaskStatus
		to     TaskStatus
		wanted bool
	}{
		{StatusCompleted, StatusRunning, false},
		{StatusPending, StatusCompleted, false},
		{StatusCanceled, StatusRunning, false},
		{StatusRunning, StatusQueued, false},
	}

	for _, tt := range tests {
		got := canTransition(tt.from, tt.to)
		if got != tt.wanted {
			t.Errorf("canTransition(%s, %s) = %v; want %v", tt.from, tt.to, got, tt.wanted)
		}
	}
}

func TestMergeTaskMetadataPreservesExistingKeys(t *testing.T) {
	current := json.RawMessage(`{"plan_ref":"plan-123","attempt":2}`)
	updates := map[string]any{
		"attempt":         3,
		"agent_run_state": "running",
	}
	merged, err := mergeTaskMetadata(current, updates)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var res map[string]any
	if err := json.Unmarshal(merged, &res); err != nil {
		t.Fatalf("failed to unmarshal: %v", err)
	}

	if res["plan_ref"] != "plan-123" {
		t.Errorf("plan_ref was modified: %v", res["plan_ref"])
	}
	if res["attempt"] != float64(3) && res["attempt"] != 3 {
		t.Errorf("attempt was not updated: %v", res["attempt"])
	}
	if res["agent_run_state"] != "running" {
		t.Errorf("agent_run_state was not set: %v", res["agent_run_state"])
	}
}

func TestLifecycleMetadataStartIncrementsAttempt(t *testing.T) {
	current1 := json.RawMessage(`{"plan_ref":"plan-123"}`)
	attempt1, err := nextTaskAttempt(current1)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if attempt1 != 1 {
		t.Errorf("expected attempt 1, got: %d", attempt1)
	}

	current2 := json.RawMessage(`{"attempt":2}`)
	attempt2, err := nextTaskAttempt(current2)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if attempt2 != 3 {
		t.Errorf("expected attempt 3, got: %d", attempt2)
	}
}

func TestLifecycleMetadataRejectsInvalidExistingMetadata(t *testing.T) {
	current := json.RawMessage(`{invalid_json}`)
	_, err := nextTaskAttempt(current)
	if err == nil {
		t.Fatal("expected error on invalid metadata, got nil")
	}
	if err != ErrInvalidTaskInput {
		t.Errorf("expected ErrInvalidTaskInput, got: %v", err)
	}
}

func TestCanTransitionAllowsRetryFromFailedToRunning(t *testing.T) {
	if !canTransition(StatusFailed, StatusRunning) {
		t.Error("expected canTransition(StatusFailed, StatusRunning) to be true, got false")
	}
}
