package message

import (
	"strings"
	"testing"
	"time"

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

func TestBuildReviewFeedbackPayloadDeterministicWithClientMessageID(t *testing.T) {
	t.Parallel()

	request := ReviewFeedbackBuildRequest{
		Context: ReviewFeedbackContextSnapshot{
			ReviewContextID: "rctx_1",
			SubjectType:     "thread",
			SubjectID:       "thr_1",
			DisplayName:     "Review target",
		},
		ClientMessageID:     "client-1",
		Locale:              "zh-CN",
		Summary:             "looks good except one issue",
		SubmittedByMemberID: "mem_1",
		SubmittedAt:         time.Date(2026, 7, 1, 1, 2, 3, 0, time.UTC),
		Items: []ReviewFeedbackItemInput{{
			Type:              "line",
			Path:              "internal/api/foo.go",
			OldPath:           "internal/api/old_foo.go",
			Side:              "new",
			NewLine:           int64Ptr(42),
			OldLine:           int64Ptr(40),
			StartLine:         int64Ptr(42),
			EndLine:           int64Ptr(44),
			HunkHeader:        "@@ -40,3 +42,3 @@",
			AnchorFingerprint: "anchor-fp",
			CodePreview:       "\tif err != nil {\n\t\treturn err\n\t}",
			Body:              "please tighten this validation",
		}},
	}

	first, err := BuildReviewFeedbackPayload(request)
	require.NoError(t, err)
	second, err := BuildReviewFeedbackPayload(request)
	require.NoError(t, err)
	require.Equal(t, first.Items[0].ID, second.Items[0].ID)
	require.Equal(t, ReviewFeedbackStatusOpen, first.Items[0].Status)
	require.Equal(t, "line", first.Items[0].Type)
	require.Equal(t, "inline", first.Items[0].Kind)
	require.Equal(t, "internal/api/old_foo.go", first.Items[0].OldPath)
	require.Equal(t, "new", first.Items[0].Side)
	require.Equal(t, int64(42), *first.Items[0].NewLine)
	require.Equal(t, int64(40), *first.Items[0].OldLine)
	require.Equal(t, int64(42), *first.Items[0].StartLine)
	require.Equal(t, int64(44), *first.Items[0].EndLine)
	require.Equal(t, "@@ -40,3 +42,3 @@", first.Items[0].HunkHeader)
	require.Equal(t, "anchor-fp", first.Items[0].AnchorFingerprint)
	require.Equal(t, "if err != nil {\nreturn err\n}", first.Items[0].CodePreview)
	require.Equal(t, "zh-CN", first.Locale)

	metadata, err := BuildReviewFeedbackMetadata(first)
	require.NoError(t, err)
	decoded, ok, err := ReviewFeedbackPayloadFromMetadata(metadata)
	require.NoError(t, err)
	require.True(t, ok)
	require.Equal(t, "rctx_1", decoded.ReviewContextID)
	formatted := FormatReviewFeedbackMessage(decoded)
	require.Contains(t, formatted, "> internal/api/foo.go:42-44")
	require.Contains(t, formatted, "> if err != nil {\n> return err\n> }")
	require.Contains(t, formatted, "please tighten this validation")
	require.NotContains(t, formatted, "> please tighten this validation")
	require.NotContains(t, formatted, "**")
	require.NotContains(t, formatted, "`internal/api/foo.go:42-44`")
	require.NotContains(t, formatted, "1. ")
	require.Equal(t, "Review target", ReviewFeedbackContributionSourceLabel(decoded))
	require.NotContains(t, formatted, "来自评审评论")
	require.NotContains(t, formatted, "via review comment")
	require.NotContains(t, formatted, "Review feedback")
	require.NotContains(t, formatted, "Context:")
	require.NotContains(t, formatted, "Review target")
	require.NotContains(t, formatted, "looks good except one issue")
	require.NotContains(t, formatted, "open")
	require.NotContains(t, formatted, "[")
}

func TestUpdateReviewFeedbackItemStatusPreservesOtherMetadata(t *testing.T) {
	t.Parallel()

	payload, err := BuildReviewFeedbackPayload(ReviewFeedbackBuildRequest{
		Context:             ReviewFeedbackContextSnapshot{ReviewContextID: "rctx_1"},
		ClientMessageID:     "client-1",
		SubmittedByMemberID: "mem_author",
		SubmittedAt:         time.Date(2026, 7, 1, 1, 2, 3, 0, time.UTC),
		Items:               []ReviewFeedbackItemInput{{ID: "item_1", Body: "fix this"}},
	})
	require.NoError(t, err)
	metadata, err := BuildReviewFeedbackMetadata(payload)
	require.NoError(t, err)
	metadata[MentionMetadataKey] = []any{map[string]any{"token": "agent"}}

	updated, updatedPayload, err := UpdateReviewFeedbackItemStatus(metadata, "item_1", ReviewFeedbackStatusResolved, "mem_reviewer", time.Date(2026, 7, 1, 2, 0, 0, 0, time.UTC))
	require.NoError(t, err)
	require.Equal(t, []any{map[string]any{"token": "agent"}}, updated[MentionMetadataKey])
	require.Equal(t, ReviewFeedbackStatusResolved, updatedPayload.Items[0].Status)
	require.Equal(t, "mem_reviewer", updatedPayload.Items[0].ResolvedByMemberID)

	updated, updatedPayload, err = UpdateReviewFeedbackItemStatus(updated, "item_1", ReviewFeedbackStatusOpen, "mem_reviewer", time.Date(2026, 7, 1, 3, 0, 0, 0, time.UTC))
	require.NoError(t, err)
	require.Equal(t, ReviewFeedbackStatusOpen, updatedPayload.Items[0].Status)
	require.Empty(t, updatedPayload.Items[0].ResolvedByMemberID)
	require.Empty(t, updatedPayload.Items[0].ResolvedAt)
}

func TestFormatReviewFeedbackMessagePreservesMarkdownQuoteAndLocalizesHeader(t *testing.T) {
	t.Parallel()

	payload, err := BuildReviewFeedbackPayload(ReviewFeedbackBuildRequest{
		Context: ReviewFeedbackContextSnapshot{
			ReviewContextID: "rctx_1",
			DisplayName:     "Agent review",
			PRURL:           "https://github.example/pull/1",
		},
		Locale:          "ja-JP,ja;q=0.9",
		ClientMessageID: "client-quote",
		Items: []ReviewFeedbackItemInput{{
			Type: "general",
			Body: "> previous comment\n\nnew reply",
		}},
	})
	require.NoError(t, err)

	formatted := FormatReviewFeedbackMessage(payload)
	require.Contains(t, formatted, "> previous comment\n\nnew reply")
	require.Equal(t, "Agent review", ReviewFeedbackContributionSourceLabel(payload))
	require.NotContains(t, formatted, "レビューコメント経由")
	require.NotContains(t, formatted, "全体コメント")
	require.NotContains(t, formatted, "Agent review")
	require.NotContains(t, formatted, "https://github.example/pull/1")
	require.NotContains(t, formatted, "open")
}

func TestFormatReviewFeedbackApprovalDecisionMessage(t *testing.T) {
	t.Parallel()

	payload, err := BuildReviewFeedbackPayload(ReviewFeedbackBuildRequest{
		Context: ReviewFeedbackContextSnapshot{
			ReviewContextID: "rctx_approved",
			DisplayName:     "Review target",
		},
		Locale:          "zh-CN",
		Decision:        "approve",
		ClientMessageID: "client-approved",
	})
	require.NoError(t, err)
	require.Equal(t, ReviewFeedbackDecisionApproved, payload.Decision)
	require.Empty(t, payload.Items)
	require.Equal(t, "变更已批准", FormatReviewFeedbackMessage(payload))

	metadata, err := BuildReviewFeedbackMetadata(payload)
	require.NoError(t, err)
	decoded, ok, err := ReviewFeedbackPayloadFromMetadata(metadata)
	require.NoError(t, err)
	require.True(t, ok)
	require.Equal(t, ReviewFeedbackDecisionApproved, decoded.Decision)

	payload.Locale = "ja"
	require.Equal(t, "変更を承認しました", FormatReviewFeedbackMessage(payload))
	payload.Locale = "en"
	require.Equal(t, "Changes approved", FormatReviewFeedbackMessage(payload))

	payloadWithComment, err := BuildReviewFeedbackPayload(ReviewFeedbackBuildRequest{
		Context: ReviewFeedbackContextSnapshot{
			ReviewContextID: "rctx_approved_with_comment",
			DisplayName:     "Review target",
		},
		Locale:          "zh-CN",
		Decision:        "approved",
		ClientMessageID: "client-approved-with-comment",
		Items: []ReviewFeedbackItemInput{
			{Type: "line", Path: "internal/review.go", NewLine: int64Ptr(28), CodePreview: "return nil", Body: "inline feedback"},
			{Type: "general", Body: "approval note"},
		},
	})
	require.NoError(t, err)
	formattedWithComment := FormatReviewFeedbackMessage(payloadWithComment)
	require.Equal(t, "变更已批准", strings.SplitN(formattedWithComment, "\n", 2)[0])
	require.Less(t, strings.Index(formattedWithComment, "变更已批准"), strings.Index(formattedWithComment, "approval note"))
	require.Less(t, strings.Index(formattedWithComment, "approval note"), strings.Index(formattedWithComment, "> internal/review.go:28"))
	require.Contains(t, formattedWithComment, "> return nil")
}

func TestReviewFeedbackContributionSourceLabelUsesContextForMultipleItems(t *testing.T) {
	t.Parallel()

	payload, err := BuildReviewFeedbackPayload(ReviewFeedbackBuildRequest{
		Context:         ReviewFeedbackContextSnapshot{ReviewContextID: "rctx_1", DisplayName: "Backend diff"},
		Locale:          "zh-CN",
		ClientMessageID: "client-multiple",
		Items: []ReviewFeedbackItemInput{
			{Type: "line", Path: "a.go", NewLine: int64Ptr(1), Body: "first"},
			{Type: "general", Body: "second"},
		},
	})
	require.NoError(t, err)

	formatted := FormatReviewFeedbackMessage(payload)
	require.Contains(t, formatted, "> a.go:1")
	require.NotContains(t, formatted, "普通评论")
	require.NotContains(t, formatted, "**")
	require.Contains(t, formatted, "\n\n---\n\n")
	require.Less(t, strings.Index(formatted, "second"), strings.Index(formatted, "> a.go:1"))
	require.NotContains(t, formatted, "1. ")
	require.NotContains(t, formatted, "2. ")
	require.Equal(t, "Backend diff", ReviewFeedbackContributionSourceLabel(payload))
}

func TestBuildReviewFeedbackMetadataCompactsLargeStructuredPayload(t *testing.T) {
	t.Parallel()

	quotedHistory := strings.Repeat("previous comment line\n", 120)
	body := "> " + strings.ReplaceAll(strings.TrimSpace(quotedHistory), "\n", "\n> ") + "\n\nactual reply body"
	largeValue := strings.Repeat("metadata-bloat-", 2000)
	payload, err := BuildReviewFeedbackPayload(ReviewFeedbackBuildRequest{
		Context: ReviewFeedbackContextSnapshot{
			ReviewContextID: "rctx_1",
			SubjectType:     "thread",
			SubjectID:       "thr_1",
			DisplayName:     largeValue,
			RootPath:        largeValue,
			PRURL:           largeValue,
			HeadRef:         strings.Repeat("h", 300),
			BaseRef:         strings.Repeat("b", 300),
		},
		ClientMessageID: "client-large",
		Summary:         largeValue,
		Refs: map[string]any{
			"diff": largeValue,
		},
		Items: []ReviewFeedbackItemInput{{
			Type:              "line",
			Path:              strings.Repeat("internal/api/really-long-path/", 30) + "foo.go",
			OldPath:           strings.Repeat("internal/api/really-long-old-path/", 30) + "foo.go",
			Side:              "new",
			NewLine:           int64Ptr(42),
			HunkHeader:        strings.Repeat("@@ -1,3 +1,3 @@ long context ", 80),
			AnchorFingerprint: strings.Repeat("anchor-fingerprint-", 40),
			StartLine:         int64Ptr(42),
			EndLine:           int64Ptr(45),
			CodePreview:       "first line\nsecond line\n" + strings.Repeat("code-preview-", 80),
			Body:              body,
			Anchor: map[string]any{
				"line": 42,
				"diff": largeValue,
			},
			Metadata: map[string]any{
				"history": largeValue,
			},
		}},
	})
	require.NoError(t, err)

	formatted := FormatReviewFeedbackMessage(payload)
	require.Contains(t, formatted, "> previous comment line")
	require.Contains(t, formatted, "> …")
	require.Contains(t, formatted, "\n\nactual reply body")
	require.NotContains(t, formatted, "> actual reply body")

	metadata, err := BuildReviewFeedbackMetadata(payload)
	require.NoError(t, err)
	encoded, _, err := EncodeMessageMetadata(metadata)
	require.NoError(t, err)
	require.LessOrEqual(t, len(encoded), maxMessageMetadataBytes)

	decoded, ok, err := ReviewFeedbackPayloadFromMetadata(metadata)
	require.NoError(t, err)
	require.True(t, ok)
	require.Empty(t, decoded.Summary)
	require.Empty(t, decoded.Refs)
	require.LessOrEqual(t, len([]rune(decoded.Context.DisplayName)), maxReviewFeedbackMetadataDisplayNameRunes)
	require.Empty(t, decoded.Context.RootPath)
	require.Empty(t, decoded.Context.PRURL)
	require.LessOrEqual(t, len([]rune(decoded.Context.HeadRef)), maxReviewFeedbackMetadataRefRunes)
	require.LessOrEqual(t, len([]rune(decoded.Context.BaseRef)), maxReviewFeedbackMetadataRefRunes)
	require.Equal(t, "actual reply body", decoded.Items[0].Body)
	require.Empty(t, decoded.Items[0].Anchor)
	require.Empty(t, decoded.Items[0].Metadata)
	require.LessOrEqual(t, len([]rune(decoded.Items[0].Path)), maxReviewFeedbackMetadataPathRunes)
	require.LessOrEqual(t, len([]rune(decoded.Items[0].OldPath)), maxReviewFeedbackMetadataPathRunes)
	require.LessOrEqual(t, len([]rune(decoded.Items[0].HunkHeader)), maxReviewFeedbackMetadataHunkHeaderRunes)
	require.LessOrEqual(t, len([]rune(decoded.Items[0].AnchorFingerprint)), maxReviewFeedbackMetadataFingerprintRunes)
	require.LessOrEqual(t, len([]rune(decoded.Items[0].CodePreview)), maxReviewFeedbackMetadataCodePreviewRunes)
	require.Equal(t, int64(42), *decoded.Items[0].StartLine)
	require.Equal(t, int64(45), *decoded.Items[0].EndLine)
	require.Contains(t, decoded.Items[0].CodePreview, "\n")
}

func int64Ptr(value int64) *int64 {
	return &value
}
