package message

import (
	"strings"
	"testing"
	"time"

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

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

	plan := newMessagePlannerForTest().Plan(messagePlanRequestForTest(MessagePlanRequest{}))

	require.Equal(t, "msg-planner", plan.MessageWrite.ID)
	require.Equal(t, "scope-thread", plan.MessageWrite.ScopeID)
	require.Equal(t, "owner", plan.MessageWrite.AuthorMemberID)
	require.Empty(t, plan.SourceWrites)
	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Empty(t, plan.TaskContextIngressRefs)
	require.Empty(t, plan.Explain)
	require.Equal(t, MessagePlanSideEffectProfileScopeFanout, plan.SideEffectProfile)
	require.Len(t, plan.OutboxEvents, 1)
	require.Equal(t, ScopeOutboxFanoutScope, plan.OutboxEvents[0].FanoutType)
	require.Equal(t, "message", plan.OutboxEvents[0].EventRefType)
}

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

	missing := messagePlanRequestForTest(MessagePlanRequest{
		LiveMentions: []MentionTarget{{MemberID: "public-agent", MemberType: ScopeActorPublicAgent}},
	})
	denied := newMessagePlannerForTest().Plan(missing)
	require.Empty(t, denied.MembershipMutations)
	require.Empty(t, denied.NotificationMutations)
	require.Len(t, denied.OutboxEvents, 1)
	require.Len(t, denied.Explain, 1)
	require.False(t, denied.Explain[0].Allowed)
	require.Equal(t, "target_not_in_scope", denied.Explain[0].DenyReason)

	active := missing
	active.LiveMentions[0].TargetMembership = publicMembershipForPlannerTest("public-agent", "scope-thread")
	plan := newMessagePlannerForTest().Plan(active)
	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Len(t, plan.OutboxEvents, 1)
	require.Len(t, plan.Explain, 1)
	require.True(t, plan.Explain[0].Allowed)
	require.Equal(t, ScopeMessageReasonDirectMention, plan.Explain[0].Reason)
}

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

	request := messagePlanRequestForTest(MessagePlanRequest{
		LiveMentions: []MentionTarget{privateMentionTargetForPlannerTest("private-agent", "owner")},
	})
	plan := newMessagePlannerForTest().Plan(request)

	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Len(t, plan.OutboxEvents, 1)
	require.Len(t, plan.Explain, 1)
	require.True(t, plan.Explain[0].Allowed)
	require.Equal(t, ScopeAuthorizationSourceOwner, plan.Explain[0].AuthorizationSource)

	missing := request
	missing.LiveMentions[0].TargetMembership = nil
	denied := newMessagePlannerForTest().Plan(missing)
	require.Len(t, denied.OutboxEvents, 1)
	require.Len(t, denied.Explain, 1)
	require.False(t, denied.Explain[0].Allowed)
	require.Equal(t, "target_not_in_scope", denied.Explain[0].DenyReason)
}

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

	target := privateMentionTargetForPlannerTest("private-agent", "owner")
	target.ParentMembership = privateMembershipForTest("private-agent", "other-owner", "scope-parent")
	request := messagePlanRequestForTest(MessagePlanRequest{LiveMentions: []MentionTarget{target}})
	plan := newMessagePlannerForTest().Plan(request)

	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Len(t, plan.OutboxEvents, 1)
	require.Len(t, plan.Explain, 1)
	require.False(t, plan.Explain[0].Allowed)
	require.Equal(t, ScopeDenyReasonPrivateParentScopeMissing, plan.Explain[0].DenyReason)
}

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

	singleForward := messagePlanRequestForTest(MessagePlanRequest{})
	singleForward.Intent.SourceRefs = []SourceRef{{SourceScopeID: "source-scope", SourceSeq: 10, SourceMessageID: "source-message", SourceKind: "forward"}}
	singleForward.LiveMentions = []MentionTarget{{MemberID: "public-agent", MemberType: ScopeActorPublicAgent, TargetMembership: publicMembershipForPlannerTest("public-agent", "scope-thread")}}
	singlePlan := newMessagePlannerForTest().Plan(singleForward)
	require.Len(t, singlePlan.SourceWrites, 1)
	require.Len(t, singlePlan.Explain, 1)
	require.Equal(t, ScopeMessageReasonDirectMention, singlePlan.Explain[0].Reason)

	multiForward := messagePlanRequestForTest(MessagePlanRequest{})
	multiForward.Intent.SourceRefs = []SourceRef{
		{SourceScopeID: "source-scope", SourceSeq: 10, SourceMessageID: "source-message-1", SourceKind: "forward"},
		{SourceScopeID: "source-scope", SourceSeq: 11, SourceMessageID: "source-message-2", SourceKind: "forward"},
	}
	multiForward.LiveMentions = singleForward.LiveMentions
	multiPlan := newMessagePlannerForTest().Plan(multiForward)
	require.Len(t, multiPlan.SourceWrites, 2)
	require.Empty(t, multiPlan.MembershipMutations)
	require.Empty(t, multiPlan.NotificationMutations)
	require.Len(t, multiPlan.OutboxEvents, 1)
	require.Empty(t, multiPlan.Explain)
}

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

	request := messagePlanRequestForTest(MessagePlanRequest{})
	request.Intent.ExplicitContext = true
	request.LiveMentions = []MentionTarget{{MemberID: "live-public-agent", MemberType: ScopeActorPublicAgent, TargetMembership: publicMembershipForPlannerTest("live-public-agent", "scope-thread")}}
	request.ExplicitRecipients = []MentionTarget{privateMentionTargetForPlannerTest("private-agent", "owner")}
	plan := newMessagePlannerForTest().Plan(request)

	require.Len(t, plan.OutboxEvents, 1)
	require.Len(t, plan.Explain, 1)
	require.Equal(t, "private-agent", plan.Explain[0].MemberID)
	require.Equal(t, ScopeMessageReasonExplicitRecipient, plan.Explain[0].Reason)
	require.Equal(t, ScopeAuthorizationSourceExplicitRecipient, plan.Explain[0].AuthorizationSource)
}

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

	request := messagePlanRequestForTest(MessagePlanRequest{
		ControlledBroadcastRecipients: []MentionTarget{{MemberID: "public-agent", MemberType: ScopeActorPublicAgent, TargetMembership: publicMembershipForPlannerTest("public-agent", "scope-thread")}},
	})
	plan := newMessagePlannerForTest().Plan(request)

	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Len(t, plan.OutboxEvents, 1)
	require.Len(t, plan.Explain, 1)
	require.Equal(t, ScopeMessageReasonControlledBroadcast, plan.Explain[0].Reason)
}

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

	request := messagePlanRequestForTest(MessagePlanRequest{
		Actor:        ActorContext{MemberID: "service-member", ActorType: ScopeActorService},
		LiveMentions: []MentionTarget{privateMentionTargetForPlannerTest("private-agent", "owner")},
	})
	plan := newMessagePlannerForTest().Plan(request)

	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Len(t, plan.OutboxEvents, 1)
	require.Len(t, plan.Explain, 1)
	require.Equal(t, ScopeDenyReasonServiceNoPrivateAuthority, plan.Explain[0].DenyReason)
}

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

	request := messagePlanRequestForTest(MessagePlanRequest{
		Actor:        ActorContext{MemberID: "private-peer", ActorType: ScopeActorPrivateAgent, OwnerMemberID: "owner", Membership: privateMembershipForTest("private-peer", "owner", "scope-thread")},
		LiveMentions: []MentionTarget{privateMentionTargetForPlannerTest("private-agent", "owner")},
	})
	plan := newMessagePlannerForTest().Plan(request)
	require.Len(t, plan.Explain, 1)
	require.Equal(t, ScopeAuthorizationSourceSameOwnerPrivate, plan.Explain[0].AuthorizationSource)

	request.Actor.Membership = privateMembershipForTest("private-peer", "other-owner", "scope-thread")
	denied := newMessagePlannerForTest().Plan(request)
	require.Len(t, denied.OutboxEvents, 1)
	require.Len(t, denied.Explain, 1)
	require.Equal(t, ScopeDenyReasonPrivateScopeMismatch, denied.Explain[0].DenyReason)
}

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

	request := messagePlanRequestForTest(MessagePlanRequest{TaskContextIngressRefs: []ScopeTaskContextIngressRef{{TaskID: "task-1", ExecutionID: "exec-1", RecipientID: "agent-1"}}})
	plan := newMessagePlannerForTest().Plan(request)

	require.Len(t, plan.TaskContextIngressRefs, 1)
	require.Equal(t, "scope-thread", plan.TaskContextIngressRefs[0].ScopeID)
	require.Equal(t, "task-1", plan.TaskContextIngressRefs[0].TaskID)
	require.Equal(t, scopePlannerNowForTest(), plan.TaskContextIngressRefs[0].CreatedAt)
}

func newMessagePlannerForTest() MessagePlanner {
	now := scopePlannerNowForTest()
	policy := NewPolicyEngineWithClock(func() time.Time { return now })
	return NewMessagePlannerWithClock(policy, func() time.Time { return now })
}

func messagePlanRequestForTest(overrides MessagePlanRequest) MessagePlanRequest {
	request := MessagePlanRequest{
		Intent: MessageIntent{
			WorkspaceID:              "workspace-planner",
			TargetScopeID:            "scope-thread",
			ParentScopeID:            "scope-parent",
			MessageID:                "msg-planner",
			ClientMessageID:          "client-planner",
			IdempotencyPayloadDigest: strings.Repeat("a", 64),
			Seq:                      7,
			WorkspaceGlobalSeq:       70,
			MessageType:              "text",
			Content:                  "hello",
			CreatedAt:                scopePlannerNowForTest(),
		},
		Actor: ActorContext{
			MemberID:  "owner",
			ActorType: ScopeActorHuman,
		},
		Scope: ScopeContext{
			TargetScopeID: "scope-thread",
			ParentScopeID: "scope-parent",
			RootMessageID: "root-message",
		},
	}
	if overrides.Intent.WorkspaceID != "" {
		request.Intent = overrides.Intent
	} else {
		if overrides.Intent.TargetScopeID != "" {
			request.Intent.TargetScopeID = overrides.Intent.TargetScopeID
		}
		if overrides.Intent.ParentScopeID != "" {
			request.Intent.ParentScopeID = overrides.Intent.ParentScopeID
		}
		if overrides.Intent.MessageID != "" {
			request.Intent.MessageID = overrides.Intent.MessageID
		}
		if overrides.Intent.ClientMessageID != "" {
			request.Intent.ClientMessageID = overrides.Intent.ClientMessageID
		}
		if overrides.Intent.Seq != 0 {
			request.Intent.Seq = overrides.Intent.Seq
		}
		if overrides.Intent.WorkspaceGlobalSeq != 0 {
			request.Intent.WorkspaceGlobalSeq = overrides.Intent.WorkspaceGlobalSeq
		}
		if overrides.Intent.MessageType != "" {
			request.Intent.MessageType = overrides.Intent.MessageType
		}
		if overrides.Intent.Content != "" {
			request.Intent.Content = overrides.Intent.Content
		}
		if len(overrides.Intent.Metadata) > 0 {
			request.Intent.Metadata = overrides.Intent.Metadata
		}
		request.Intent.SourceRefs = overrides.Intent.SourceRefs
		request.Intent.ExplicitContext = overrides.Intent.ExplicitContext
		if !overrides.Intent.CreatedAt.IsZero() {
			request.Intent.CreatedAt = overrides.Intent.CreatedAt
		}
	}
	if overrides.Actor.MemberID != "" || overrides.Actor.ActorType != "" || overrides.Actor.OwnerMemberID != "" || overrides.Actor.Membership != nil {
		request.Actor = overrides.Actor
	}
	if overrides.Scope.TargetScopeID != "" || overrides.Scope.ParentScopeID != "" || overrides.Scope.RootMessageID != "" {
		request.Scope = overrides.Scope
	}
	request.LiveMentions = overrides.LiveMentions
	request.ControlledBroadcastRecipients = overrides.ControlledBroadcastRecipients
	request.ExplicitRecipients = overrides.ExplicitRecipients
	request.TaskContextIngressRefs = overrides.TaskContextIngressRefs
	return request
}

func publicMembershipForPlannerTest(memberID string, scopeID string) *ScopeMembershipRecord {
	return &ScopeMembershipRecord{ScopeID: scopeID, MemberID: memberID, Visibility: "public", EffectiveFromSeq: 0}
}

func privateMentionTargetForPlannerTest(memberID string, ownerMemberID string) MentionTarget {
	return MentionTarget{
		MemberID:         memberID,
		MemberType:       ScopeActorPrivateAgent,
		OwnerMemberID:    ownerMemberID,
		ParentMembership: privateMembershipForTest(memberID, ownerMemberID, "scope-parent"),
		TargetMembership: privateMembershipForTest(memberID, ownerMemberID, "scope-thread"),
	}
}

func scopePlannerNowForTest() time.Time {
	return time.Date(2026, 5, 13, 3, 30, 0, 0, time.UTC)
}
