package app

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/provider"
)

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

	tests := []struct {
		provider string
		input    string
		wantKey  string
	}{
		{
			provider: PullRequestSubscriptionProviderGitea,
			input:    "gitea:PingCAP/AgentTeam#42",
			wantKey:  "gitea:pingcap/agentteam#42",
		},
		{
			provider: PullRequestSubscriptionProviderGitHub,
			input:    "github:Acme/AgentTeam#7",
			wantKey:  "github:acme/agentteam#7",
		},
	}
	for _, test := range tests {
		t.Run(test.provider, func(t *testing.T) {
			t.Parallel()

			resource, err := NormalizePullRequestSubscriptionRef(test.provider, test.input)
			require.NoError(t, err)
			require.Equal(t, test.input, resource.ResourceRef)
			require.Equal(t, test.wantKey, resource.ResourceKey)
		})
	}
}

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

	_, err := NormalizePullRequestSubscriptionRef(
		PullRequestSubscriptionProviderGitea,
		"github:pingcap/agentteam#42",
	)

	require.Error(t, err)
	require.True(t, errors.Is(err, ErrBadRequest))
}

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

	baseBranchEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.base_branch_pushed"}
	reviewEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.review_submitted"}
	statusEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.ci_status"}
	mergedEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.closed", ActionKind: "merged"}

	require.False(t, serviceEventProviderEventFiltersMatch([]byte(`{}`), baseBranchEvent))
	require.True(t, serviceEventProviderEventFiltersMatch([]byte(`{}`), reviewEvent))
	require.True(t, serviceEventProviderEventFiltersMatch([]byte(`{}`), statusEvent))
	require.True(t, serviceEventProviderEventFiltersMatch([]byte(`{}`), mergedEvent))
}

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

	baseBranchEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.base_branch_pushed"}

	require.True(t, serviceEventProviderEventFiltersMatch([]byte(`{"events":["base_branch_push"]}`), baseBranchEvent))
	require.False(t, serviceEventProviderEventFiltersMatch([]byte(`{"events":["status"]}`), baseBranchEvent))
}

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

	mergedEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.closed", ActionKind: "merged"}
	closedEvent := provider.ProviderEvent{EventKind: "gitea_pull_request.closed", ActionKind: "closed"}

	require.True(t, serviceEventProviderEventFiltersMatch([]byte(`{"events":["merge"]}`), mergedEvent))
	require.False(t, serviceEventProviderEventFiltersMatch([]byte(`{"events":["close"]}`), mergedEvent))
	require.False(t, serviceEventProviderEventFiltersMatch([]byte(`{"events":["merge"]}`), closedEvent))
	require.True(t, serviceEventProviderEventFiltersMatch([]byte(`{"events":["close"]}`), closedEvent))
	require.True(t, serviceEventProviderEventCompletesSubscription(mergedEvent))
	require.True(t, serviceEventProviderEventCompletesSubscription(closedEvent))
}

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

	kinds := providerWatchEventKindsForFilters("gitea", []byte(`{}`))

	require.Contains(t, kinds, "gitea_pull_request.issue_comment_created")
	require.Contains(t, kinds, "gitea_pull_request.review_comment_created")
	require.Contains(t, kinds, "gitea_pull_request.review_submitted")
	require.Contains(t, kinds, "gitea_pull_request.synchronized")
	require.Contains(t, kinds, "gitea_pull_request.ci_status")
	require.Contains(t, kinds, "gitea_pull_request.workflow_run_status")
	require.Contains(t, kinds, "gitea_pull_request.workflow_job_status")
	require.Contains(t, kinds, "gitea_pull_request.closed")
	require.Contains(t, kinds, "gitea_pull_request.reopened")
	require.NotContains(t, kinds, "gitea_pull_request.base_branch_pushed")
	require.NotContains(t, kinds, "gitea_pull_request.*")
}

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

	filters, err := marshalProviderWatchEventFilters(map[string]any{"events": []string{
		"comments",
		"reviews",
		"status",
		"synchronize",
		"base_branch_push",
		"merge",
		"close",
		"reopen",
	}})
	require.NoError(t, err)

	kinds := providerWatchEventKindsForFilters("gitea", filters)

	require.Equal(t, []string{
		"gitea_pull_request.base_branch_pushed",
		"gitea_pull_request.ci_status",
		"gitea_pull_request.closed",
		"gitea_pull_request.issue_comment_created",
		"gitea_pull_request.reopened",
		"gitea_pull_request.review_comment_created",
		"gitea_pull_request.review_submitted",
		"gitea_pull_request.synchronized",
		"gitea_pull_request.workflow_job_status",
		"gitea_pull_request.workflow_run_status",
	}, kinds)
}

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

	longBranch := "release @agent " + strings.Repeat("x", serviceEventDisplayTextMaxBytes+512)
	content := formatServiceEventDeliveryMessage(provider.ProviderEvent{
		Provider:           "gitea",
		EventKind:          "gitea_pull_request.base_branch_pushed",
		ResourceIdentifier: "gitea:acme/agentteam#42 @all",
		ResourceURL:        "https://gitea.example.com/acme/agentteam/pulls/42 @online",
		ActorLogin:         "@provider-bot",
		Extra: map[string]string{
			"branch": longBranch,
		},
	})

	require.NotContains(t, content, "@agent")
	require.NotContains(t, content, "@all")
	require.NotContains(t, content, "@online")
	require.NotContains(t, content, "@provider-bot")
	require.Contains(t, content, "@\u200bagent")
	require.NotContains(t, content, strings.Repeat("x", serviceEventDisplayTextMaxBytes+128))
}

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

	content := serviceEventExternalBodyBlockquote("@agent " + strings.Repeat("a", 5000))

	require.NotContains(t, content, "@agent")
	require.Contains(t, content, "@\u200b")
	require.LessOrEqual(t, len(content), 4120)
}

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

	content := serviceEventExternalBodyBlockquote(strings.Repeat("@agent ", 1000))

	require.NotContains(t, content, "@agent")
	require.Contains(t, content, "@\u200bagent")
	require.LessOrEqual(t, len(content), serviceEventBodyExcerptMaxBytes+len("> "))
}

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

	longDescription := strings.Repeat("x", serviceEventBodyExcerptMaxBytes+512)
	event := serviceEventProviderWebhookEvent{
		Event: provider.ProviderEvent{
			Provider:     "gitea",
			ChangedPaths: []string{"docs/deploy.md", "README.md", "README.md"},
			Extra: map[string]string{
				"description": longDescription,
				"sha":         "head-sha",
			},
		},
	}

	payload, err := serviceEventDeliveryPayload(event)
	require.NoError(t, err)
	var decoded model.ProviderEventPayload
	require.NoError(t, json.Unmarshal(payload, &decoded))
	require.Equal(t, "head-sha", decoded.Extra["sha"])
	require.Equal(t, []string{"README.md", "docs/deploy.md"}, decoded.ChangedPaths)
	require.LessOrEqual(t, len(decoded.Extra["description"]), serviceEventBodyExcerptMaxBytes)
	require.NotEqual(t, longDescription, decoded.Extra["description"])

}
