package tasktimeline

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

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

	now := time.Date(2026, 7, 29, 1, 20, 0, 0, time.UTC)
	activities := []TypedActivityProjection{
		{TaskID: "task_1", EventID: "event_updated", EventType: "updated", Seq: 10, CreatedAt: now},
		{TaskID: "task_1", EventID: "event_assigned", EventType: "assigned", Seq: 11, CreatedAt: now},
		{TaskID: "task_1", EventID: "event_checklist", EventType: "checklist_updated", Seq: 12, CreatedAt: now},
		{TaskID: "task_2", EventID: "event_status", EventType: "status_changed", Seq: 3, CreatedAt: now},
	}

	got := coalesceTaskAttentionActivities(activities)

	require.Len(t, got, 2)
	require.Equal(t, "event_assigned", got[0].EventID)
	require.Equal(t, "event_status", got[1].EventID)
}

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

	queryer := &recordingTaskAttentionQueryer{}
	humans, agents, err := loadTaskAttentionRecipients(context.Background(), queryer, "task_1", "actor_1")

	require.NoError(t, err)
	require.Empty(t, humans)
	require.Empty(t, agents)
	require.Contains(t, queryer.query, "sns.task_watch_state = 'active'")
	require.Contains(t, queryer.query, "sns.task_watch_state = 'auto-wake'")
	require.Contains(t, queryer.query, "OR task.assignee_member_id = m.id")
	require.Contains(t, queryer.query, "m.workspace_id = task.workspace_id")
	require.Contains(t, queryer.query, "JOIN scope_memberships sm")
	require.NotContains(t, queryer.query, "sns.state =")
}

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

	now := time.Date(2026, 7, 29, 1, 22, 0, 0, time.UTC)
	got := coalesceTaskAttentionActivities([]TypedActivityProjection{
		{TaskID: "task_1", EventID: "event_status", EventType: "status_changed", Seq: 10, CreatedAt: now},
		{TaskID: "task_1", EventID: "event_review", EventType: "review_requested", Seq: 11, CreatedAt: now},
		{TaskID: "task_1", EventID: "event_assigned", EventType: "assigned", Seq: 12, CreatedAt: now},
	})

	require.Len(t, got, 1)
	require.Equal(t, "event_review", got[0].EventID)
}

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

	got := coalesceTaskAttentionActivities([]TypedActivityProjection{
		{TaskID: "task_1", EventID: "discussion", EventType: taskDiscussionAddedEventType, SourceMessageID: "msg_1"},
		{TaskID: "task_2", EventID: "suppressed", EventType: "updated", SuppressAttention: true},
	})

	require.Empty(t, got)
}

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

	tests := []struct {
		eventType string
		payload   string
		want      string
	}{
		{eventType: "assigned", payload: "mem_internal_assignee", want: "Assigned task"},
		{eventType: "unassigned", payload: "mem_internal_assignee", want: "Unassigned task"},
		{eventType: "reviewer_assigned", payload: "review_internal_id", want: "Assigned reviewer"},
		{eventType: "review_approved", payload: "review_internal_id", want: "Approved review"},
		{eventType: "review_changes_requested", payload: "review_internal_id", want: "Requested review changes"},
		{eventType: "review_cancelled", payload: "review_internal_id", want: "Cancelled review"},
	}
	for _, test := range tests {
		test := test
		t.Run(test.eventType, func(t *testing.T) {
			t.Parallel()
			got := taskActivityPreview(TypedActivityProjection{
				EventType:    test.eventType,
				EventPayload: test.payload,
			})
			require.Equal(t, test.want, got)
			require.NotContains(t, got, test.payload)
		})
	}
}

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

	msg := taskActivityReceiveMessage(
		TypedActivityProjection{
			EventID:      "tev_checkpoint",
			EventType:    "checkpoint_added",
			ActivityType: "task.checkpoint_added",
			Seq:          99,
			CreatedAt:    time.Date(2026, 8, 7, 4, 0, 0, 0, time.UTC),
		},
		TaskAttentionSnapshot{TaskScopeID: "tsk_scope"},
	)

	require.Equal(t, "tev_checkpoint", msg.MessageID)
	require.Equal(t, "tsk_scope", msg.TargetKey)
	require.Zero(t, msg.TargetSeq)
	require.False(t, msg.CarriesScopeTargetSeq())
}

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

	now := time.Date(2026, 7, 29, 1, 25, 0, 0, time.UTC)
	projection := taskConversationProjection(
		taskAttentionProjection{
			TaskID:                "task_7",
			TaskScopeID:           "tsk_scope_7",
			ChannelID:             "channel / seven",
			TaskNumber:            7,
			Title:                 "Review attention",
			Status:                "in_progress",
			ParentTaskRef:         "#t2",
			SourceThreadChannelID: "thr_7",
			AssigneeMemberID:      "mem_agent",
		},
		message.AuthorProfile{
			ActorType:   message.AuthorActorTypeAgent,
			PrincipalID: "agt_7",
			MemberID:    "mem_agent",
			DisplayName: "Reviewer",
		},
		true,
		message.AuthorProfile{
			ActorType:   message.AuthorActorTypeHuman,
			MemberID:    "mem_actor",
			DisplayName: "Alice",
		},
		"task.assigned",
		"Assigned task",
		now,
	)

	require.Equal(t, "task:tsk_scope_7", projection.ConversationID)
	require.Equal(t, "task", projection.Kind)
	require.Equal(t, "/channels/channel%20%2F%20seven/tasks/7", projection.Projection["route"])
	require.Equal(t, "#t7 Review attention", projection.Projection["title"])
	require.Equal(t, "Assigned task", projection.Projection["preview"])
	require.Equal(t, "Alice", projection.Projection["lastAuthorName"])
	task, ok := projection.Projection["task"].(map[string]any)
	require.True(t, ok)
	require.Equal(t, int64(7), task["number"])
	require.Equal(t, "in_progress", task["status"])
	require.Equal(t, "/channels/channel%20%2F%20seven/tasks/7", task["route"])
	require.NotNil(t, task["assigneeProfile"])
}

type recordingTaskAttentionQueryer struct {
	query string
}

func (*recordingTaskAttentionQueryer) QueryRow(context.Context, string, ...any) coredb.Row {
	return recordingTaskTimelineUnexpectedRow{}
}

func (queryer *recordingTaskAttentionQueryer) Query(_ context.Context, query string, _ ...any) (coredb.Rows, error) {
	queryer.query = strings.TrimSpace(query)
	return emptyTaskAttentionRows{}, nil
}

type emptyTaskAttentionRows struct{}

func (emptyTaskAttentionRows) Close()            {}
func (emptyTaskAttentionRows) Err() error        { return nil }
func (emptyTaskAttentionRows) Next() bool        { return false }
func (emptyTaskAttentionRows) Scan(...any) error { return nil }
