package runartifact

import (
	"encoding/base64"
	"encoding/json"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestDraftIsDeterministicAndReferenceBound(t *testing.T) {
	content := []byte(`{"branches":[{"id":1},{"id":2}]}`)
	left, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_join", content)
	require.NoError(t, err)
	right, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_join", content)
	require.NoError(t, err)
	require.Equal(t, left.Ref, right.Ref)
	require.Equal(t, content, left.Content)
	require.NoError(t, left.Ref.Validate())
	other, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_other", content)
	require.NoError(t, err)
	require.NotEqual(t, left.Ref.ArtifactID, other.Ref.ArtifactID)
}

func TestArtifactKindsDiscriminateAndIDsAreVerified(t *testing.T) {
	content := []byte(`{"same":true}`)
	join, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_1", content)
	require.NoError(t, err)
	effect, err := NewDraftForKind(KindEffectText, "wsp_1", "wri_1", "join", "act_1", content)
	require.NoError(t, err)
	reducer, err := NewDraftForKind(KindReducerValue, "wsp_1", "wri_1", "join", "act_1", content)
	require.NoError(t, err)
	require.NotEqual(t, join.Ref.ArtifactID, effect.Ref.ArtifactID)
	require.NotEqual(t, effect.Ref.ArtifactID, reducer.Ref.ArtifactID)
	for _, ref := range []Ref{join.Ref, effect.Ref, reducer.Ref} {
		require.NoError(t, ref.Validate())
	}
	forged := join.Ref
	forged.RunID = "wri_other"
	require.ErrorIs(t, forged.Validate(), ErrInvalidReference)
}

func TestExpandJSONRequiresTrustedResolverAndPreservesContent(t *testing.T) {
	draft, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_join", []byte(`{"a":[1,2],"text":"exact"}`))
	require.NoError(t, err)
	payload, err := json.Marshal(map[string]any{"result": Marker(draft.Ref)})
	require.NoError(t, err)
	got, err := ExpandJSON(payload, func(ref Ref) ([]byte, error) {
		require.Equal(t, draft.Ref, ref)
		return draft.Content, nil
	})
	require.NoError(t, err)
	require.JSONEq(t, `{"result":{"a":[1,2],"text":"exact"}}`, string(got))

	forged := draft.Ref
	forged.RunID = "wri_other"
	forgedPayload, err := json.Marshal(map[string]any{"result": Marker(forged)})
	require.NoError(t, err)
	_, err = ExpandJSON(forgedPayload, func(ref Ref) ([]byte, error) {
		if ref.RunID != draft.Ref.RunID {
			return nil, ErrUnauthorizedReference
		}
		return draft.Content, nil
	})
	require.ErrorIs(t, err, ErrInvalidReference)
}

func TestCursorBindsAllReadCoordinates(t *testing.T) {
	draft, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_join", []byte(`{"a":"0123456789"}`))
	require.NoError(t, err)
	secret := []byte("test-secret")
	cursor, err := EncodeCursor(draft.Ref, "root", []string{"a"}, 3, 4, secret)
	require.NoError(t, err)
	decodedCursor, err := base64.RawURLEncoding.DecodeString(cursor)
	require.NoError(t, err)
	var cursorFields map[string]any
	require.NoError(t, json.Unmarshal(decodedCursor, &cursorFields))
	require.Len(t, cursorFields, 2)
	require.Equal(t, float64(3), cursorFields["offset"])
	require.NotEmpty(t, cursorFields["mac"])
	decoded, err := DecodeCursor(cursor, draft.Ref, "root", []string{"a"}, 4, secret)
	require.NoError(t, err)
	require.Equal(t, 3, decoded.Offset)
	_, err = DecodeCursor(cursor, draft.Ref, "root", []string{"different"}, 4, secret)
	require.ErrorIs(t, err, ErrInvalidCursor)
	_, err = DecodeCursor(cursor, draft.Ref, "root", []string{"a"}, 5, secret)
	require.ErrorIs(t, err, ErrInvalidCursor)
	altered := draft.Ref
	altered.Digest = strings.Repeat("sha256:", 0) + "sha256:" + strings.Repeat("0", 64)
	_, err = DecodeCursor(cursor, altered, "root", []string{"a"}, 4, secret)
	require.Error(t, err)
	_, err = DecodeCursor(cursor, draft.Ref, "root", []string{"a"}, 4, []byte("wrong-secret"))
	require.ErrorIs(t, err, ErrInvalidCursor)
	mutated := cursor[:len(cursor)-1] + "A"
	_, err = DecodeCursor(mutated, draft.Ref, "root", []string{"a"}, 4, secret)
	require.ErrorIs(t, err, ErrInvalidCursor)
}

func TestFindMarkerAtNestedPathReturnsArtifactSuffix(t *testing.T) {
	draft, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_join", []byte(`{"items":[1,2,3]}`))
	require.NoError(t, err)
	raw, err := json.Marshal(map[string]any{
		"dependencies": map[string]any{"join": map[string]any{"value": Marker(draft.Ref)}},
	})
	require.NoError(t, err)
	ref, suffix, marker, err := FindMarkerAtPath(raw, []string{"dependencies", "join", "value", "items"})
	require.NoError(t, err)
	require.True(t, marker)
	require.Equal(t, draft.Ref, ref)
	require.Equal(t, []string{"items"}, suffix)
	_, _, marker, err = FindMarkerAtPath(raw, []string{"dependencies", "other"})
	require.NoError(t, err)
	require.False(t, marker)
	_, err = EncodeCursor(draft.Ref, "output", nil, 0, 64, []byte("secret"))
	require.NoError(t, err)
}

func TestSelectPathReturnsExactRawBytes(t *testing.T) {
	raw := []byte(`{"z":0,"nested":{"value":[{"text":"x"},2]}}`)
	selected, err := SelectPath(raw, []string{"nested", "value", "0"})
	require.NoError(t, err)
	require.Equal(t, `{"text":"x"}`, string(selected))
	_, err = SelectPath(raw, []string{"nested", "missing"})
	require.ErrorIs(t, err, ErrInvalidRead)
}

func TestArtifactBudgetIsTyped(t *testing.T) {
	_, err := NewDraftForKind(KindJoinAggregate, "wsp_1", "wri_1", "join", "act_join", []byte(strings.Repeat("x", MaxArtifactBytes+1)))
	require.ErrorIs(t, err, ErrArtifactBudgetExceeded)
}
