package daemon

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

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

// TestReviewWirePayloadPreservesControlCharTargetPathByteExact pins the
// transport-layer half of the newline-path contract reviewer3 required: when
// the workspace layer refuses to DiffTree a control-character path (covered
// by TestWrapReviewTreeError* in desktop/internal/workspace), the wire layer
// MUST still carry the path byte-exact so the frontend can surface a stable
// error state rather than panic / corrupt the path.
//
// The JSON encoder is what carries these payloads between Hub and desktop
// (and onward to the frontend). encoding/json does not transform `\n`, `\t`,
// `\r`, or trailing spaces inside JSON string values — they survive
// marshal/unmarshal unchanged — but this test pins the contract explicitly
// so a future protobuf/msgpack migration cannot silently start transforming
// control characters. Each of the three Review wire payloads that carry a
// TargetPath is exercised.
func TestReviewWirePayloadPreservesControlCharTargetPathByteExact(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name       string
		targetPath string
	}{
		{"plain", "/repo"},
		{"trailing space", "/repo "},
		{"trailing tab", "/repo\t"},
		{"newline in middle", "/var/repo\nname"},
		{"carriage return", "/repo\r"},
		{"NUL byte", "/repo\x00"},
		{"interior space", "/var/repos/agentteam workspace"},
		{"multiple whitespace", "/repo \t\n\r"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			t.Run("summary payload", func(t *testing.T) {
				t.Parallel()
				src := reviewSummaryRemoteRequestPayload{
					AgentID: "agt_1", TargetPath: tc.targetPath,
					BaseRef: "main", HeadRef: "feature", ComparisonMode: "direct",
				}
				raw, err := json.Marshal(src)
				require.NoError(t, err, "marshal must accept control-char path")
				var got reviewSummaryRemoteRequestPayload
				require.NoError(t, json.Unmarshal(raw, &got), "unmarshal must succeed")
				require.Equal(t, src, got, "JSON round-trip must preserve TargetPath byte-exact")
				require.Equal(t, tc.targetPath, got.TargetPath,
					"TargetPath byte-exact: source length=%d, got length=%d", len(tc.targetPath), len(got.TargetPath))
			})
			t.Run("files payload", func(t *testing.T) {
				t.Parallel()
				src := reviewFilesRemoteRequestPayload{
					AgentID: "agt_1", TargetPath: tc.targetPath, Scope: "branch",
					BaseRef: "main", HeadRef: "feature", ComparisonMode: "direct",
				}
				raw, err := json.Marshal(src)
				require.NoError(t, err)
				var got reviewFilesRemoteRequestPayload
				require.NoError(t, json.Unmarshal(raw, &got))
				require.Equal(t, src, got, "JSON round-trip must preserve TargetPath byte-exact")
				require.Equal(t, tc.targetPath, got.TargetPath)
			})
			t.Run("diff payload", func(t *testing.T) {
				t.Parallel()
				src := reviewDiffRemoteRequestPayload{
					AgentID: "agt_1", TargetPath: tc.targetPath, Scope: "branch", Path: "main.go",
					BaseRef: "main", HeadRef: "feature", ComparisonMode: "direct",
				}
				raw, err := json.Marshal(src)
				require.NoError(t, err)
				var got reviewDiffRemoteRequestPayload
				require.NoError(t, json.Unmarshal(raw, &got))
				require.Equal(t, src, got, "JSON round-trip must preserve TargetPath byte-exact")
				require.Equal(t, tc.targetPath, got.TargetPath)
			})
		})
	}
}

// TestReviewWirePayloadDoesNotTransformEscapeSequences verifies that the JSON
// wire layer does NOT decode `\n` / `\t` / `\r` into the literal control
// bytes BEFORE re-encoding them as something else on the way out. The concern
// is that some intermediate layer (log formatter, JSON re-serializer, custom
// MarshalJSON) might "helpfully" transform control characters and corrupt
// the path. This test asserts the marshal output of a payload with embedded
// `\n` contains the JSON escape sequence `\n` (backslash + n), not a literal
// newline byte, so the wire bytes themselves stay portable across loggers and
// proxies that might otherwise reinterpret raw newlines.
func TestReviewWirePayloadDoesNotTransformEscapeSequences(t *testing.T) {
	t.Parallel()

	payload := reviewSummaryRemoteRequestPayload{
		AgentID: "agt_1", TargetPath: "/var/repo\nname",
		BaseRef: "main", HeadRef: "feature",
	}
	raw, err := json.Marshal(payload)
	require.NoError(t, err)

	// The serialized JSON MUST contain `\n` (2 chars: backslash + n), not a
	// raw 0x0A byte. This is what lets the wire bytes survive any
	// newline-sensitive transport (HTTP/1.1 chunked, log shippers, etc.).
	require.Contains(t, string(raw), `"/var/repo\nname"`,
		"JSON must escape control-char paths as \\n, not emit raw newline bytes")

	// Symmetric: unmarshalling MUST decode `\n` back to a single 0x0A byte.
	var got reviewSummaryRemoteRequestPayload
	require.NoError(t, json.Unmarshal(raw, &got))
	require.Equal(t, "/var/repo\nname", got.TargetPath,
		"unmarshal must decode \\n back to a single newline byte — not strip or duplicate it")
	require.Len(t, got.TargetPath, len("/var/repo")+1+4, // '/var/repo' + 0x0A + 'name'
		"control-char path length must be exactly the source length")
}

// TestReviewWirePayloadPreservesTrailingSpace verifies trailing whitespace in
// TargetPath survives the wire layer byte-exact. This is the parallel of the
// store-layer trailing-whitespace isolation test, applied to the wire
// surface: `/repo ` MUST NOT be silently trimmed to `/repo` by any JSON
// encoder / decoder / logger in the chain. encoding/json does not strip
// whitespace from string values, but pinning the contract here prevents a
// future custom JSON normalizer from silently rewriting paths.
func TestReviewWirePayloadPreservesTrailingSpace(t *testing.T) {
	t.Parallel()

	payload := reviewSummaryRemoteRequestPayload{
		AgentID: "agt_1", TargetPath: "/repo ",
	}
	raw, err := json.Marshal(payload)
	require.NoError(t, err)
	require.Contains(t, string(raw), `"/repo "`,
		"trailing space in TargetPath must be preserved verbatim in JSON output")

	var got reviewSummaryRemoteRequestPayload
	require.NoError(t, json.Unmarshal(raw, &got))
	require.Equal(t, "/repo ", got.TargetPath,
		"trailing space must round-trip through JSON unmarshal unchanged")
	require.True(t, strings.HasSuffix(got.TargetPath, " "),
		"TargetPath MUST end with a space — stripping would silently remap '/repo ' to '/repo' on the desktop side")
}
