package app

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"

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

type fakeTeamMentionExpander struct {
	edges      []team.TeamExpansionEdge
	lastParams *team.ExpandTeamMentionsParams
}

func (fake fakeTeamMentionExpander) ExpandTeamMentionsForScope(_ context.Context, params team.ExpandTeamMentionsParams) ([]team.TeamExpansionEdge, error) {
	if fake.lastParams != nil {
		*fake.lastParams = params
	}
	return fake.edges, nil
}

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

	tokens := directMentionTokensForTeamFallback(
		[]string{"core", "platform"},
		[]string{"core"},
	)
	require.Equal(t, []string{"platform"}, tokens)
}

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

	tokens := directMentionTokensForTeamFallback(
		[]string{"core"},
		[]string{"core"},
	)
	require.Empty(t, tokens)
	require.NotNil(t, tokens)
}

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

	tokens := directMentionTokensForTeamFallback(
		[]string{"core"},
		nil,
	)
	require.Equal(t, []string{"core"}, tokens)
}

type emptyMentionHintQueryer struct{}

func (emptyMentionHintQueryer) QueryRow(context.Context, string, ...any) coredb.Row {
	return emptyMentionHintRow{}
}

func (emptyMentionHintQueryer) Query(context.Context, string, ...any) (coredb.Rows, error) {
	return emptyMentionHintRows{}, nil
}

type emptyMentionHintRow struct{}

func (emptyMentionHintRow) Scan(...any) error {
	return nil
}

type emptyMentionHintRows struct{}

func (emptyMentionHintRows) Close() {}

func (emptyMentionHintRows) Err() error {
	return nil
}

func (emptyMentionHintRows) Next() bool {
	return false
}

func (emptyMentionHintRows) Scan(...any) error {
	return nil
}

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

	routing, err := resolveMentionHintAgentRecipientsForUser(
		context.Background(),
		emptyMentionHintQueryer{},
		"wsp",
		"chn",
		"mem_author",
		[]message.MessageMentionHint{{Type: "agent", ID: "agt_old", Token: "helper"}},
		[]string{"helper"},
		nil,
	)
	require.NoError(t, err)
	require.Empty(t, routing.recipients)
	require.Equal(t, []string{"helper"}, routing.claimedTokens)

	liveTokens := directMentionTokensExcluding([]string{"helper"}, routing.claimedTokens)
	require.Empty(t, liveTokens)
	require.NotNil(t, liveTokens)

	fallbackTokens := directMentionTokensForTeamFallback([]string{"helper"}, routing.claimedTokens)
	require.Empty(t, fallbackTokens)
	require.NotNil(t, fallbackTokens)
}

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

	var params team.ExpandTeamMentionsParams
	_, err := resolveTeamMentionRecipientsForUser(
		context.Background(),
		fakeTeamMentionExpander{lastParams: &params},
		"wsp",
		"chn",
		"mem_author",
		[]message.MessageMentionHint{{Type: "team", ID: "team_core", Token: "core"}},
		[]string{"core"},
		[]string{},
	)
	require.NoError(t, err)
	require.Equal(t, []string{"core"}, params.Tokens)
	require.Empty(t, params.FallbackTokens)
	require.NotNil(t, params.FallbackTokens)
}

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

	routing, err := resolveTeamMentionRecipientsForUser(
		context.Background(),
		fakeTeamMentionExpander{edges: []team.TeamExpansionEdge{
			{
				TeamID:              "team_core",
				TeamHandle:          "core-team",
				TeamDisplayName:     "Core Team",
				MentionToken:        "Core-Team",
				ActorKind:           team.ActorKindAgent,
				ActorID:             "mem_agent",
				ActorDisplayName:    "Helper",
				ActorHandle:         "helper",
				TeamMemberID:        "wtm_agent",
				AgentID:             "agt_helper",
				MachineID:           "mac_helper",
				AutoWakeEnabled:     true,
				Role:                team.MemberRoleLeader,
				RoleLabel:           "Builder",
				RoleOverlaySnapshot: "Build it.",
				RoleOverlayVersion:  2,
				WakeOnTeamMention:   true,
			},
			{
				TeamID:              "team_core",
				TeamHandle:          "core-team",
				TeamDisplayName:     "Core Team",
				MentionToken:        "Core-Team",
				ActorKind:           team.ActorKindAgent,
				ActorID:             "mem_observer",
				ActorDisplayName:    "Observer",
				ActorHandle:         "observer",
				TeamMemberID:        "wtm_observer",
				AgentID:             "agt_observer",
				MachineID:           "mac_observer",
				Role:                team.MemberRoleMember,
				RoleLabel:           "Observer",
				RoleOverlaySnapshot: "Track the work.",
				WakeOnTeamMention:   false,
			},
		}},
		"wsp",
		"chn",
		"mem_author",
		[]message.MessageMentionHint{{Type: "team", ID: "team_core", Token: "Core-Team"}},
		[]string{"core-team"},
		nil,
	)
	require.NoError(t, err)

	require.Equal(t, []string{"core-team"}, routing.tokens)
	require.Equal(t, []message.PersistedMention{{Type: "team", ID: "team_core", Token: "core-team"}}, routing.mentions)
	require.Len(t, routing.recipients, 1)
	recipient := routing.recipients[0]
	require.Equal(t, message.AgentDeliveryReasonTeamMention, recipient.DeliveryReason)
	require.Equal(t, "agt_helper", recipient.AgentID)
	require.True(t, recipient.AutoWakeEnabled)
	require.Len(t, recipient.TeamContexts, 1)
	context := recipient.TeamContexts[0]
	require.Equal(t, "team_core", context.TeamID)
	require.Equal(t, "Builder", context.RoleLabel)
	require.Equal(t, "Build it.", context.RoleOverlayPromptSnapshot)
	require.Len(t, context.MemberRoster, 2)
	require.Equal(t, "wtm_agent", context.MemberRoster[0].TeamMemberID)
	require.True(t, context.MemberRoster[0].Self)
	require.Equal(t, "Build it.", context.MemberRoster[0].RoleOverlayPrompt)
	require.True(t, context.MemberRoster[0].WakeOnTeamMention)
	require.Equal(t, "wtm_observer", context.MemberRoster[1].TeamMemberID)
	require.False(t, context.MemberRoster[1].Self)
}

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

	routing, err := resolveTeamMentionRecipientsForUser(
		context.Background(),
		fakeTeamMentionExpander{edges: []team.TeamExpansionEdge{
			{
				TeamID:              "team_core",
				TeamHandle:          "core-team",
				TeamDisplayName:     "Core Team",
				MentionToken:        "Core-Team",
				ActorKind:           team.ActorKindAgent,
				ActorID:             "mem_lead",
				ActorDisplayName:    "Lead",
				ActorHandle:         "lead",
				TeamMemberID:        "wtm_lead",
				AgentID:             "agt_lead",
				MachineID:           "mac_lead",
				AutoWakeEnabled:     true,
				Role:                team.MemberRoleLeader,
				RoleLabel:           "Lead",
				RoleOverlaySnapshot: "Lead the work.",
				WakeOnTeamMention:   true,
			},
			{
				TeamID:              "team_core",
				TeamHandle:          "core-team",
				TeamDisplayName:     "Core Team",
				MentionToken:        "Core-Team",
				ActorKind:           team.ActorKindAgent,
				ActorID:             "mem_specialist",
				ActorDisplayName:    "Specialist",
				ActorHandle:         "specialist",
				TeamMemberID:        "wtm_specialist",
				AgentID:             "agt_specialist",
				MachineID:           "mac_specialist",
				AutoWakeEnabled:     true,
				Role:                team.MemberRoleMember,
				RoleLabel:           "Specialist",
				RoleOverlaySnapshot: "Investigate the work.",
				WakeOnTeamMention:   false,
			},
		}},
		"wsp",
		"chn",
		"mem_author",
		[]message.MessageMentionHint{{Type: "team", ID: "team_core", Token: "Core-Team"}},
		[]string{"core-team"},
		nil,
	)
	require.NoError(t, err)

	require.True(t, routing.suppressDefaultAutoWake)
	require.Len(t, routing.recipients, 1)
	recipient := routing.recipients[0]
	require.Equal(t, "agt_lead", recipient.AgentID)
	require.Len(t, recipient.TeamContexts, 1)
	require.Len(t, recipient.TeamContexts[0].MemberRoster, 2)
	require.True(t, recipient.TeamContexts[0].MemberRoster[0].Self)
	require.False(t, recipient.TeamContexts[0].MemberRoster[1].Self)
	require.Equal(t, "mem_specialist", recipient.TeamContexts[0].MemberRoster[1].ActorID)
	require.Equal(t, "Lead the work.", recipient.TeamContexts[0].MemberRoster[0].RoleOverlayPrompt)
	require.Equal(t, "Investigate the work.", recipient.TeamContexts[0].MemberRoster[1].RoleOverlayPrompt)
}

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

	recipients, err := teamInvokeContextRecipients([]team.TeamExpansionEdge{
		{
			TeamID:              "team_core",
			TeamHandle:          "core-team",
			TeamDisplayName:     "Core Team",
			MentionToken:        "core-team",
			ActorKind:           team.ActorKindAgent,
			ActorID:             "mem_target",
			ActorDisplayName:    "Target",
			ActorHandle:         "target",
			TeamMemberID:        "wtm_target",
			AgentID:             "agt_target",
			MachineID:           "mac_target",
			AutoWakeEnabled:     true,
			Role:                team.MemberRoleMember,
			RoleLabel:           "Builder",
			RoleOverlaySnapshot: "Build it.",
			RoleOverlayVersion:  2,
			WakeOnTeamMention:   false,
		},
		{
			TeamID:           "team_core",
			TeamHandle:       "core-team",
			TeamDisplayName:  "Core Team",
			MentionToken:     "core-team",
			ActorKind:        team.ActorKindAgent,
			ActorID:          "mem_observer",
			ActorDisplayName: "Observer",
			ActorHandle:      "observer",
			TeamMemberID:     "wtm_observer",
			AgentID:          "agt_observer",
			MachineID:        "mac_observer",
			Role:             team.MemberRoleMember,
		},
	}, []message.AgentRecipient{{
		AgentID:        "agt_target",
		MachineID:      "mac_target",
		AgentMemberID:  "mem_target",
		DeliveryReason: message.AgentDeliveryReasonDirectMention,
		MentionToken:   "target",
	}})
	require.NoError(t, err)
	require.Len(t, recipients, 1)

	recipient := recipients[0]
	require.Equal(t, message.AgentDeliveryReasonDirectMention, recipient.DeliveryReason)
	require.Equal(t, "target", recipient.MentionToken)
	require.Len(t, recipient.TeamContexts, 1)
	context := recipient.TeamContexts[0]
	require.Equal(t, "team_core", context.TeamID)
	require.Equal(t, "Builder", context.RoleLabel)
	require.Equal(t, "Build it.", context.RoleOverlayPromptSnapshot)
	require.Len(t, context.MemberRoster, 2)
	require.Equal(t, "wtm_target", context.MemberRoster[0].TeamMemberID)
	require.True(t, context.MemberRoster[0].Self)
	require.Equal(t, "Build it.", context.MemberRoster[0].RoleOverlayPrompt)
	require.False(t, context.MemberRoster[0].WakeOnTeamMention)
	require.Equal(t, "wtm_observer", context.MemberRoster[1].TeamMemberID)
	require.False(t, context.MemberRoster[1].Self)
}

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

	recipients, err := teamInvokeContextRecipients([]team.TeamExpansionEdge{
		{
			TeamID:           "team_core",
			TeamHandle:       "core-team",
			TeamDisplayName:  "Core Team",
			ActorKind:        team.ActorKindAgent,
			ActorID:          "mem_leader",
			ActorDisplayName: "Leader",
			TeamMemberID:     "wtm_leader",
			AgentID:          "agt_leader",
			MachineID:        "mac_leader",
			Role:             team.MemberRoleLeader,
		},
		{
			TeamID:           "team_core",
			TeamHandle:       "core-team",
			TeamDisplayName:  "Core Team",
			ActorKind:        team.ActorKindAgent,
			ActorID:          "mem_member",
			ActorDisplayName: "Member",
			TeamMemberID:     "wtm_member",
			AgentID:          "agt_member",
			MachineID:        "mac_member",
			Role:             team.MemberRoleMember,
		},
	}, []message.AgentRecipient{{
		AgentID:        "agt_member",
		MachineID:      "mac_member",
		AgentMemberID:  "mem_member",
		DeliveryReason: message.AgentDeliveryReasonDirectMention,
		MentionToken:   "member",
	}})
	require.NoError(t, err)
	require.Len(t, recipients, 1)
	require.Equal(t, "agt_member", recipients[0].AgentID)
	require.Len(t, recipients[0].TeamContexts, 1)
	require.Equal(t, team.MemberRoleMember, recipients[0].TeamContexts[0].Role)
}

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

	_, err := teamInvokeContextRecipients([]team.TeamExpansionEdge{{
		TeamID:           "team_core",
		TeamHandle:       "core-team",
		TeamDisplayName:  "Core Team",
		ActorKind:        team.ActorKindAgent,
		ActorID:          "mem_target",
		ActorDisplayName: "Target",
		ActorHandle:      "target",
		TeamMemberID:     "wtm_target",
		AgentID:          "agt_target",
		MachineID:        "mac_target",
	}}, []message.AgentRecipient{{
		AgentID:        "agt_other",
		MachineID:      "mac_other",
		AgentMemberID:  "mem_other",
		DeliveryReason: message.AgentDeliveryReasonDirectMention,
		MentionToken:   "other",
	}})
	require.Error(t, err)
	require.Contains(t, err.Error(), "direct mentions must target agent members of the Team")
}
