package message

import (
	"testing"

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

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

	tests := []struct {
		name       string
		appMessage AppMessage
		wantKind   string
	}{
		{name: "human user", appMessage: AppMessage{ID: "msg_human", AuthorProfile: AuthorProfile{ActorType: AuthorActorTypeHuman, DisplayName: "Alice"}}, wantKind: ContributionSourceKindHumanCompose},
		{name: "agent", appMessage: AppMessage{ID: "msg_agent", AuthorProfile: AuthorProfile{ActorType: AuthorActorTypeAgent, DisplayName: "AgentTeam"}}, wantKind: ContributionSourceKindAgentRuntime},
		{name: "app service without an explicit delivery source", appMessage: AppMessage{ID: "msg_service", AuthorProfile: AuthorProfile{ActorType: AuthorActorTypeService, DisplayName: "Gitea"}}, wantKind: ""},
		{name: "service account", appMessage: AppMessage{ID: "msg_service_account", AuthorProfile: AuthorProfile{ActorType: AuthorActorTypeService, DisplayName: "News", ServiceKind: AuthorServiceKindServiceAccount}}, wantKind: ContributionSourceKindServiceDirectPost},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			source := DefaultContributionSourceForAppMessage(tt.appMessage)
			if tt.wantKind == "" {
				require.True(t, source.IsZero())
				return
			}

			require.Equal(t, tt.wantKind, source.Kind)
			require.Equal(t, ContributionSourceRefTypeMessage, source.RefType)
			require.Equal(t, tt.appMessage.ID, source.RefID)
			require.Equal(t, tt.appMessage.AuthorProfile.DisplayName, source.Label)
		})
	}
}

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

	appMessage := AppMessage{
		ID: "msg_profile_source",
		AuthorProfile: AuthorProfile{
			ActorType:   AuthorActorTypeService,
			DisplayName: "Gitea",
			MemberID:    "mem_gitea",
			ServiceKind: AuthorServiceKindApp,
		},
	}

	source := DefaultContributionSourceForAppMessage(appMessage)

	require.True(t, source.IsZero())
}

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

	source := ContributionSourceForServiceEventDelivery(ServiceEventDeliveryContributionSourceParams{
		DeliveryID:  " sed_1 ",
		Label:       "Gitea PR watch",
		EventID:     "evt_1",
		EventType:   "gitea.pull_request",
		EventAction: "gitea_pull_request.synchronized",
	})

	require.Equal(t, ContributionSourceKindProviderEvent, source.Kind)
	require.Equal(t, ContributionSourceRefTypeServiceEventDelivery, source.RefType)
	require.Equal(t, "sed_1", source.RefID)
	require.Equal(t, "sed_1", source.RunID)
	require.Equal(t, "evt_1", source.EventID)
	require.Equal(t, "gitea_pull_request.synchronized", source.EventType)
	require.Equal(t, "Gitea PR watch", source.Label)
	require.NoError(t, source.ValidateForWrite())
}

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

	source := ContributionSourceForAutomationRun(AutomationRunContributionSourceParams{
		RunID:               " arun_1 ",
		AutomationName:      "Daily standup",
		TimeMode:            "cron",
		SourceOccurrenceRef: "2026-05-30T09:00:00Z",
	})

	require.Equal(t, ContributionSourceKindScheduleRun, source.Kind)
	require.Equal(t, ContributionSourceRefTypeAutomationRun, source.RefType)
	require.Equal(t, "arun_1", source.RefID)
	require.Equal(t, "arun_1", source.RunID)
	require.Equal(t, "2026-05-30T09:00:00Z", source.EventID)
	require.Equal(t, "cron", source.EventType)
	require.Equal(t, "Daily standup", source.Label)
	require.NoError(t, source.ValidateForWrite())
}

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

	source := ContributionSourceForWorkflowRun(WorkflowRunContributionSourceParams{
		RunID: " wri_1 ", EffectID: " wfx_1 ", PlanID: "pln_1",
		WorkflowName: "成语接龙", SourceKind: "manual",
	})

	require.Equal(t, ContributionSourceKindWorkflowRun, source.Kind)
	require.Equal(t, ContributionSourceRefTypeWorkflowRun, source.RefType)
	require.Equal(t, "wri_1", source.RefID)
	require.Equal(t, "wri_1", source.RunID)
	require.Equal(t, "wfx_1", source.EventID)
	require.Equal(t, "manual", source.EventType)
	require.Equal(t, "成语接龙", source.Label)
	require.NoError(t, source.ValidateForWrite())
}

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

	tests := []struct {
		name    string
		source  ContributionSource
		wantErr string
	}{
		{name: "empty allowed"},
		{name: "message source", source: ContributionSource{Kind: ContributionSourceKindHumanCompose, RefType: ContributionSourceRefTypeMessage, RefID: "msg_1"}},
		{name: "service direct post message self ref", source: ContributionSource{Kind: ContributionSourceKindServiceDirectPost, RefType: ContributionSourceRefTypeMessage, RefID: "msg_bot_notification"}},
		{name: "service direct post requires message ref type", source: ContributionSource{Kind: ContributionSourceKindServiceDirectPost, RefType: ContributionSourceRefTypeServiceEventDelivery, RefID: "sed_1"}, wantErr: "invalid contribution source ref type"},
		{name: "service direct post requires concrete message ref", source: ContributionSource{Kind: ContributionSourceKindServiceDirectPost, RefType: ContributionSourceRefTypeMessage}, wantErr: "contribution source ref id is required"},
		{name: "review comment source binds later", source: ContributionSource{Kind: ContributionSourceKindReviewComment, RefType: ContributionSourceRefTypeMessage}},
		{name: "review comment source with message ref", source: ContributionSource{Kind: ContributionSourceKindReviewComment, RefType: ContributionSourceRefTypeMessage, RefID: "msg_review"}},
		{name: "provider event service event delivery source", source: ContributionSource{Kind: ContributionSourceKindProviderEvent, RefType: ContributionSourceRefTypeServiceEventDelivery, RefID: "sed_1", RunID: "sed_1"}},
		{name: "provider event service event delivery source without run id", source: ContributionSource{Kind: ContributionSourceKindProviderEvent, RefType: ContributionSourceRefTypeServiceEventDelivery, RefID: "sed_1"}},
		{name: "schedule automation run source", source: ContributionSource{Kind: ContributionSourceKindScheduleRun, RefType: ContributionSourceRefTypeAutomationRun, RefID: "arun_1", RunID: "arun_1"}},
		{name: "workflow run source", source: ContributionSource{Kind: ContributionSourceKindWorkflowRun, RefType: ContributionSourceRefTypeWorkflowRun, RefID: "wri_1", RunID: "wri_1", EventID: "wfx_1"}},
		{name: "workflow run requires effect", source: ContributionSource{Kind: ContributionSourceKindWorkflowRun, RefType: ContributionSourceRefTypeWorkflowRun, RefID: "wri_1", RunID: "wri_1"}, wantErr: "effect id is required"},
		{name: "workflow run rejects automation ref", source: ContributionSource{Kind: ContributionSourceKindWorkflowRun, RefType: ContributionSourceRefTypeAutomationRun, RefID: "wri_1", RunID: "wri_1", EventID: "wfx_1"}, wantErr: "invalid contribution source ref type"},
		{name: "schedule rejects service event delivery", source: ContributionSource{Kind: ContributionSourceKindScheduleRun, RefType: ContributionSourceRefTypeServiceEventDelivery, RefID: "sed_1", RunID: "sed_1"}, wantErr: "invalid contribution source ref type"},
		{name: "provider event rejects automation run", source: ContributionSource{Kind: ContributionSourceKindProviderEvent, RefType: ContributionSourceRefTypeAutomationRun, RefID: "arun_1", RunID: "arun_1"}, wantErr: "invalid contribution source ref type"},
		{name: "mismatched ref type", source: ContributionSource{Kind: ContributionSourceKindScheduleRun, RefType: ContributionSourceRefTypeMessage, RefID: "trun_1", RunID: "trun_1"}, wantErr: "invalid contribution source ref type"},
		{name: "review comment rejects ledger ref type", source: ContributionSource{Kind: ContributionSourceKindReviewComment, RefType: ContributionSourceRefTypeAutomationRun, RefID: "arun_1"}, wantErr: "invalid contribution source ref type"},
		{name: "missing native ref id", source: ContributionSource{Kind: ContributionSourceKindProviderEvent, RefType: ContributionSourceRefTypeServiceEventDelivery}, wantErr: "contribution source ref id is required"},
		{name: "mismatched native run id", source: ContributionSource{Kind: ContributionSourceKindScheduleRun, RefType: ContributionSourceRefTypeAutomationRun, RefID: "arun_1", RunID: "arun_2"}, wantErr: "contribution source run ref mismatch"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			err := tt.source.ValidateForWrite()
			if tt.wantErr == "" {
				require.NoError(t, err)
			} else {
				require.ErrorContains(t, err, tt.wantErr)
			}
		})
	}
}

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

	source, err := (ContributionSource{
		Kind:    ContributionSourceKindProviderEvent,
		RefType: ContributionSourceRefTypeServiceEventDelivery,
		RefID:   "sed_1",
	}).NormalizeForWrite()

	require.NoError(t, err)
	require.Equal(t, "sed_1", source.RunID)
}
