package run

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

func selectOneWorkItem(key string, responder any) map[string]any {
	return map[string]any{
		plan.WorkItemFieldKey:           key,
		plan.WorkItemFieldDependsOn:     []any{},
		plan.WorkItemFieldWorkScope:     map[string]any{"kind": "task", "scopeId": "tsk_" + key},
		plan.WorkItemFieldRelatedScopes: []any{map[string]any{"kind": "task", "scopeId": "tsk_parent"}},
		plan.WorkItemFieldResponder:     responder,
		plan.WorkItemFieldInstruction:   "implement " + key,
	}
}

func TestNormalizeWorkItemsSelectOneFixedAndPool(t *testing.T) {
	t.Parallel()
	raw := []any{
		selectOneWorkItem("fixed", "@dev1"),
		selectOneWorkItem("pool", map[string]any{"selectOne": []any{"@dev2", "@dev3", "@dev1"}}),
	}
	items, err := NormalizeWorkItems("callback.workItems", raw, []string{"@dev1", "@dev2", "@dev3"})
	require.NoError(t, err)
	require.Equal(t, "@dev1", items[0].Responder)
	require.Empty(t, items[0].Responders)
	require.Empty(t, items[1].Responder)
	require.Equal(t, []string{"@dev2", "@dev3", "@dev1"}, items[1].Responders)

	// Round-trip: normalized value re-normalizes losslessly.
	normalized := WorkItemsValue(items)
	renormalized, err := NormalizeWorkItems("callback.workItems", normalized, nil)
	require.NoError(t, err)
	require.Equal(t, items, renormalized)
}

func TestNormalizeWorkItemsSelectOneDedupPreservesFirstOccurrenceOrder(t *testing.T) {
	t.Parallel()
	raw := []any{
		selectOneWorkItem("pool", map[string]any{"selectOne": []any{"@dev3", "@dev1", "@dev3", "@dev2", "@dev1"}}),
	}
	items, err := NormalizeWorkItems("callback.workItems", raw, []string{"@dev1", "@dev2", "@dev3"})
	require.NoError(t, err)
	require.Equal(t, []string{"@dev3", "@dev1", "@dev2"}, items[0].Responders)
}

func TestNormalizeWorkItemsSelectOneEachCandidateMustBeAllowed(t *testing.T) {
	t.Parallel()
	// A pool with a candidate outside allowedResponders is rejected atomically.
	raw := []any{
		selectOneWorkItem("pool", map[string]any{"selectOne": []any{"@dev1", "@intruder"}}),
	}
	_, err := NormalizeWorkItems("callback.workItems", raw, []string{"@dev1", "@dev2"})
	require.ErrorContains(t, err, "is not an allowed responder")
}

func TestNormalizeWorkItemsSelectOneEmptyPoolRejected(t *testing.T) {
	t.Parallel()
	raw := []any{
		selectOneWorkItem("pool", map[string]any{"selectOne": []any{}}),
	}
	_, err := NormalizeWorkItems("callback.workItems", raw, []string{"@dev1", "@dev2"})
	require.ErrorContains(t, err, "must not be empty")
}

func TestNormalizeWorkItemsSelectOneUpperBoundRejected(t *testing.T) {
	t.Parallel()
	candidates := make([]any, 0, plan.MaxAllowedResponders+1)
	for index := 0; index <= plan.MaxAllowedResponders; index++ {
		candidates = append(candidates, "@dev"+string(rune('a'+index%26))+string(rune('0'+index%10)))
	}
	raw := []any{
		selectOneWorkItem("pool", map[string]any{"selectOne": candidates}),
	}
	_, err := NormalizeWorkItems("callback.workItems", raw, []string{})
	// The normalize fails first on the empty allowedResponders allowlist; use a
	// permissive allowlist of the actual candidate set to reach the upper bound.
	allowed := make([]string, len(candidates))
	for index, candidate := range candidates {
		allowed[index] = candidate.(string)
	}
	_, err = NormalizeWorkItems("callback.workItems", raw, allowed)
	require.ErrorContains(t, err, "more than")
}

func TestNormalizeSelectOneCandidatesDynamicEmptyFails(t *testing.T) {
	t.Parallel()
	_, err := normalizeSelectOneCandidates([]any{})
	require.ErrorContains(t, err, "must not be empty")
	_, err = normalizeSelectOneCandidates("not-an-array")
	require.ErrorContains(t, err, "want agent array")
}

func TestSelectOneCandidatesFromObjectRejectsUnknownFields(t *testing.T) {
	t.Parallel()
	_, _, err := selectOneCandidatesFromObject(map[string]any{"selectOne": []any{"@dev1"}, "forged": true})
	require.ErrorContains(t, err, "unsupported field")
}
