package payload

import (
	"strings"
	"testing"

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

func TestPageBytesRoundTripsUTF8AndBindsCursorToResource(t *testing.T) {
	raw := []byte(`{"message":"` + strings.Repeat("界", 1200) + `"}`)
	identity := Identity{Kind: "run", WorkspaceID: "wsp_1", PlanID: "wpl_1", RunID: "wri_1"}
	first, err := PageBytes(identity, "input", "", "json", raw, "", 17)
	require.NoError(t, err)
	require.NotEmpty(t, first.NextCursor)

	var cursor = first.NextCursor
	var recovered strings.Builder
	recovered.WriteString(first.Value)
	for {
		page, err := PageBytes(identity, "input", "", "json", raw, cursor, 17)
		require.NoError(t, err)
		recovered.WriteString(page.Value)
		if page.Complete {
			break
		}
		cursor = page.NextCursor
		require.NotEmpty(t, cursor)
	}
	require.Equal(t, string(raw), recovered.String())

	for _, read := range []struct {
		name, kind, workspace, plan, version, run, step, section, path string
	}{
		{name: "workspace", kind: "run", workspace: "wsp_other", plan: "wpl_1", run: "wri_1", section: "input"},
		{name: "plan", kind: "run", workspace: "wsp_1", plan: "wpl_other", run: "wri_1", section: "input"},
		{name: "run", kind: "run", workspace: "wsp_1", plan: "wpl_1", run: "wri_other", section: "input"},
		{name: "step", kind: "run", workspace: "wsp_1", plan: "wpl_1", run: "wri_1", step: "wst_other", section: "input"},
		{name: "section", kind: "run", workspace: "wsp_1", plan: "wpl_1", run: "wri_1", section: "output"},
		{name: "path", kind: "run", workspace: "wsp_1", plan: "wpl_1", run: "wri_1", section: "input", path: "/message"},
	} {
		t.Run(read.name, func(t *testing.T) {
			_, err := PageBytes(Identity{Kind: read.kind, WorkspaceID: read.workspace, PlanID: read.plan, VersionID: read.version, RunID: read.run, StepID: read.step}, read.section, read.path, "json", raw, first.NextCursor, 17)
			require.Error(t, err)
		})
	}

	changed := []byte(`{"message":"` + strings.Repeat("改", 1200) + `"}`)
	_, err = PageBytes(identity, "input", "", "json", changed, first.NextCursor, 17)
	require.ErrorIs(t, err, ErrContentChanged)
}

func TestPageBytesWithSnapshotRejectsRevisionChangesAndAllowsUnchangedPages(t *testing.T) {
	raw := []byte(`{"payload":"` + strings.Repeat("x", 5000) + `"}`)
	identity := Identity{Kind: "plan", WorkspaceID: "wsp_1", PlanID: "wpl_1"}
	snapshot := Snapshot{Version: "draft-7", Digest: ContentDigest(raw)}
	first, err := PageBytesWithSnapshot(identity, "source", "", "text", raw, snapshot, "", 64)
	require.NoError(t, err)
	require.NotEmpty(t, first.NextCursor)

	unchanged, err := PageBytesWithSnapshot(identity, "source", "", "text", raw, snapshot, first.NextCursor, 64)
	require.NoError(t, err)
	require.NotEmpty(t, unchanged.Value)

	_, err = PageBytesWithSnapshot(identity, "source", "", "text", raw, Snapshot{Version: "draft-8", Digest: ContentDigest(raw)}, first.NextCursor, 64)
	require.ErrorIs(t, err, ErrContentChanged)
	changed := []byte(`{"payload":"` + strings.Repeat("y", 5000) + `"}`)
	_, err = PageBytesWithSnapshot(identity, "source", "", "text", changed, snapshot, first.NextCursor, 64)
	require.ErrorIs(t, err, ErrContentChanged)
	_, err = PageBytesWithSnapshot(identity, "source", "", "text", changed, Snapshot{Version: "draft-8", Digest: ContentDigest(changed)}, first.NextCursor, 64)
	require.ErrorIs(t, err, ErrContentChanged)
}

func TestNarrowJSONUsesBoundedPointerBeforePaging(t *testing.T) {
	raw := []byte(`{"outer":{"keep":"value","large":"` + strings.Repeat("x", 5000) + `"}}`)
	page, err := PageBytes(Identity{Kind: "run", WorkspaceID: "wsp_1", PlanID: "wpl_1", RunID: "wri_1"}, "output", "$.outer.keep", "json", mustNarrow(t, raw, "/outer/keep"), "", 64)
	require.NoError(t, err)
	require.Equal(t, "/outer/keep", page.Path)
	require.Equal(t, `"value"`, page.Value)
	canonical, err := CanonicalPath("/outer/keep")
	require.NoError(t, err)
	require.Equal(t, page.Path, canonical)

	require.Error(t, ValidateReadSelector("input", strings.Repeat("x", MaxPathBytes+1), ""))
	require.Error(t, ValidateReadSelector("input", "", strings.Repeat("x", MaxCursorBytes+1)))
}

func mustNarrow(t *testing.T, raw []byte, path string) []byte {
	t.Helper()
	value, err := NarrowJSON(raw, path)
	require.NoError(t, err)
	return value
}
