package message

import (
	"testing"
	"time"

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

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

	now := time.Date(2026, 5, 13, 3, 0, 0, 0, time.UTC)
	engine := NewPolicyEngineWithClock(func() time.Time { return now })
	grant := ScopePrivateGrantRecord{
		ID:                "grant_scope_policy",
		ScopeID:           "scope-policy-thread",
		OwnerMemberID:     "owner",
		GranteeMemberID:   "grantee",
		GrantedByMemberID: "owner",
		CreatedAt:         now.Add(-time.Hour),
	}
	parentGrant := grant
	parentGrant.ID = "grant_scope_policy_parent"
	parentGrant.ScopeID = "scope-policy-parent"

	tests := []struct {
		name                 string
		request              PrivateAuthorityRequest
		allowed              bool
		authorizationSource  string
		authorizationScopeID string
		denyReason           string
	}{
		{
			name: "owner human allowed",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "owner",
				ActorType:     ScopeActorHuman,
			}),
			allowed:              true,
			authorizationSource:  ScopeAuthorizationSourceOwner,
			authorizationScopeID: "scope-policy-thread",
		},
		{
			name: "explicit owner allowed",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID:     "owner",
				ActorType:         ScopeActorHuman,
				ExplicitRecipient: true,
			}),
			allowed:              true,
			authorizationSource:  ScopeAuthorizationSourceExplicitRecipient,
			authorizationScopeID: "scope-policy-thread",
		},
		{
			name: "scope grant allowed",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant:         &grant,
			}),
			allowed:              true,
			authorizationSource:  ScopeAuthorizationSourceScopeGrant,
			authorizationScopeID: "scope-policy-thread",
		},
		{
			name: "parent scope grant allowed",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant:         &parentGrant,
			}),
			allowed:              true,
			authorizationSource:  ScopeAuthorizationSourceParentScopeGrant,
			authorizationScopeID: "scope-policy-parent",
		},
		{
			name: "same owner private allowed when both memberships are private in scope",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID:      "private-peer",
				ActorType:          ScopeActorPrivateAgent,
				ActorOwnerMemberID: "owner",
				ActorMembership:    privateMembershipForTest("private-peer", "owner", "scope-policy-thread"),
				TargetMembership:   privateMembershipForTest("target-private", "owner", "scope-policy-thread"),
			}),
			allowed:              true,
			authorizationSource:  ScopeAuthorizationSourceSameOwnerPrivate,
			authorizationScopeID: "scope-policy-thread",
		},
		{
			name: "public agent needs explicit grant even with same owner",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID:      "public-agent",
				ActorType:          ScopeActorPublicAgent,
				ActorOwnerMemberID: "owner",
			}),
			denyReason: ScopeDenyReasonPublicAgentNeedsGrant,
		},
		{
			name: "app denied private authority",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "app",
				ActorType:     ScopeActorApp,
			}),
			denyReason: ScopeDenyReasonServiceNoPrivateAuthority,
		},
		{
			name: "service denied private authority",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "service",
				ActorType:     ScopeActorService,
			}),
			denyReason: ScopeDenyReasonServiceNoPrivateAuthority,
		},
		{
			name: "cross owner human denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "other-human",
				ActorType:     ScopeActorHuman,
			}),
			denyReason: ScopeDenyReasonNoPrivateAuthority,
		},
		{
			name: "revoked grant denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant: func() *ScopePrivateGrantRecord {
					revoked := grant
					revoked.RevokedAt = &now
					return &revoked
				}(),
			}),
			denyReason: ScopeDenyReasonGrantInactive,
		},
		{
			name: "expired grant denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant: func() *ScopePrivateGrantRecord {
					expired := grant
					expiresAt := now.Add(-time.Second)
					expired.ExpiresAt = &expiresAt
					return &expired
				}(),
			}),
			denyReason: ScopeDenyReasonGrantInactive,
		},
		{
			name: "grant owner mismatch denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant: func() *ScopePrivateGrantRecord {
					mismatch := grant
					mismatch.OwnerMemberID = "other-owner"
					return &mismatch
				}(),
			}),
			denyReason: ScopeDenyReasonGrantOwnerMismatch,
		},
		{
			name: "grant grantee mismatch denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "other-grantee",
				ActorType:     ScopeActorHuman,
				Grant:         &grant,
			}),
			denyReason: ScopeDenyReasonGrantGranteeMismatch,
		},
		{
			name: "grant scope mismatch denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant: func() *ScopePrivateGrantRecord {
					mismatch := grant
					mismatch.ScopeID = "unrelated-scope"
					return &mismatch
				}(),
			}),
			denyReason: ScopeDenyReasonGrantScopeMismatch,
		},
		{
			name: "same owner private missing membership denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID:      "private-peer",
				ActorType:          ScopeActorPrivateAgent,
				ActorOwnerMemberID: "owner",
				TargetMembership:   privateMembershipForTest("target-private", "owner", "scope-policy-thread"),
			}),
			denyReason: ScopeDenyReasonPrivateScopeMismatch,
		},
		{
			name: "same owner private membership owner mismatch denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID:      "private-peer",
				ActorType:          ScopeActorPrivateAgent,
				ActorOwnerMemberID: "owner",
				ActorMembership:    privateMembershipForTest("private-peer", "other-owner", "scope-policy-thread"),
				TargetMembership:   privateMembershipForTest("target-private", "owner", "scope-policy-thread"),
			}),
			denyReason: ScopeDenyReasonPrivateScopeMismatch,
		},
		{
			name: "empty grant scope denied",
			request: privateAuthorityRequestForTest(PrivateAuthorityRequest{
				ActorMemberID: "grantee",
				ActorType:     ScopeActorHuman,
				Grant: func() *ScopePrivateGrantRecord {
					emptyScope := grant
					emptyScope.ScopeID = ""
					return &emptyScope
				}(),
			}),
			denyReason: ScopeDenyReasonGrantScopeMismatch,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			decision := engine.EvaluatePrivateAuthority(tt.request)
			require.Equal(t, tt.allowed, decision.Allowed)
			require.Equal(t, tt.authorizationSource, decision.AuthorizationSource)
			require.Equal(t, tt.authorizationScopeID, decision.AuthorizationScopeID)
			require.Equal(t, tt.denyReason, decision.DenyReason)
		})
	}
}

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

	now := time.Date(2026, 5, 13, 3, 0, 0, 0, time.UTC)
	engine := NewPolicyEngineWithClock(func() time.Time { return now })
	grant := ScopePrivateGrantRecord{
		ID:                "grant_scope_policy_mention",
		ScopeID:           "scope-policy-thread",
		OwnerMemberID:     "owner",
		GranteeMemberID:   "grantee",
		GrantedByMemberID: "owner",
		CreatedAt:         now.Add(-time.Hour),
	}

	publicDecision := engine.EvaluateMentionPolicy(MentionPolicyRequest{
		ActorMemberID:  "owner",
		ActorType:      ScopeActorHuman,
		TargetMemberID: "public-agent",
		TargetType:     ScopeActorPublicAgent,
		TargetScopeID:  "scope-policy-thread",
	})
	require.True(t, publicDecision.Allowed)
	require.True(t, publicDecision.LiveMention)
	require.Equal(t, "direct_mention", publicDecision.MessageReason)

	privateDecision := engine.EvaluateMentionPolicy(MentionPolicyRequest{
		ActorMemberID:          "grantee",
		ActorType:              ScopeActorHuman,
		TargetMemberID:         "target-private",
		TargetType:             ScopeActorPrivateAgent,
		TargetScopeID:          "scope-policy-thread",
		ParentScopeID:          "scope-policy-parent",
		TargetPrivateOwnerID:   "owner",
		TargetParentMembership: privateMembershipForTest("target-private", "owner", "scope-policy-parent"),
		TargetMembership:       privateMembershipForTest("target-private", "owner", "scope-policy-thread"),
		Grant:                  &grant,
	})
	require.True(t, privateDecision.Allowed)
	require.True(t, privateDecision.LiveMention)
	require.Equal(t, ScopeAuthorizationSourceScopeGrant, privateDecision.Authority.AuthorizationSource)

	missingParent := engine.EvaluateMentionPolicy(MentionPolicyRequest{
		ActorMemberID:        "owner",
		ActorType:            ScopeActorHuman,
		TargetMemberID:       "target-private",
		TargetType:           ScopeActorPrivateAgent,
		TargetScopeID:        "scope-policy-thread",
		ParentScopeID:        "scope-policy-parent",
		TargetPrivateOwnerID: "owner",
	})
	require.False(t, missingParent.Allowed)
	require.Equal(t, ScopeDenyReasonPrivateParentScopeMissing, missingParent.DenyReason)

	parentOwnerMismatch := engine.EvaluateMentionPolicy(MentionPolicyRequest{
		ActorMemberID:          "owner",
		ActorType:              ScopeActorHuman,
		TargetMemberID:         "target-private",
		TargetType:             ScopeActorPrivateAgent,
		TargetScopeID:          "scope-policy-thread",
		ParentScopeID:          "scope-policy-parent",
		TargetPrivateOwnerID:   "owner",
		TargetParentMembership: privateMembershipForTest("target-private", "other-owner", "scope-policy-parent"),
	})
	require.False(t, parentOwnerMismatch.Allowed)
	require.False(t, parentOwnerMismatch.LiveMention)
	require.Equal(t, ScopeDenyReasonPrivateParentScopeMissing, parentOwnerMismatch.DenyReason)

	noAuthority := engine.EvaluateMentionPolicy(MentionPolicyRequest{
		ActorMemberID:          "outsider",
		ActorType:              ScopeActorHuman,
		TargetMemberID:         "target-private",
		TargetType:             ScopeActorPrivateAgent,
		TargetScopeID:          "scope-policy-thread",
		ParentScopeID:          "scope-policy-parent",
		TargetPrivateOwnerID:   "owner",
		TargetParentMembership: privateMembershipForTest("target-private", "owner", "scope-policy-parent"),
		TargetMembership:       privateMembershipForTest("target-private", "owner", "scope-policy-thread"),
	})
	require.False(t, noAuthority.Allowed)
	require.Equal(t, ScopeDenyReasonNoPrivateAuthority, noAuthority.DenyReason)
}

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

	require.Equal(t, ScopeRoutingMentionModeNormal, ResolveRoutingMentionMode(RoutingMentionModeRequest{}))
	require.Equal(t, ScopeRoutingMentionModeSingleForward, ResolveRoutingMentionMode(RoutingMentionModeRequest{SourceCount: 1}))
	require.Equal(t, ScopeRoutingMentionModeMultiForward, ResolveRoutingMentionMode(RoutingMentionModeRequest{SourceCount: 2}))
	require.Equal(t, ScopeRoutingMentionModeExplicitContext, ResolveRoutingMentionMode(RoutingMentionModeRequest{ExplicitContext: true, SourceCount: 2}))
}

func privateAuthorityRequestForTest(overrides PrivateAuthorityRequest) PrivateAuthorityRequest {
	request := PrivateAuthorityRequest{
		ActorMemberID:              "actor",
		ActorType:                  ScopeActorHuman,
		TargetPrivateAgentMemberID: "target-private",
		TargetPrivateOwnerMemberID: "owner",
		TargetScopeID:              "scope-policy-thread",
		ParentScopeID:              "scope-policy-parent",
	}
	if overrides.ActorMemberID != "" {
		request.ActorMemberID = overrides.ActorMemberID
	}
	if overrides.ActorType != "" {
		request.ActorType = overrides.ActorType
	}
	if overrides.ActorOwnerMemberID != "" {
		request.ActorOwnerMemberID = overrides.ActorOwnerMemberID
	}
	if overrides.TargetPrivateAgentMemberID != "" {
		request.TargetPrivateAgentMemberID = overrides.TargetPrivateAgentMemberID
	}
	if overrides.TargetPrivateOwnerMemberID != "" {
		request.TargetPrivateOwnerMemberID = overrides.TargetPrivateOwnerMemberID
	}
	if overrides.TargetScopeID != "" {
		request.TargetScopeID = overrides.TargetScopeID
	}
	if overrides.ParentScopeID != "" {
		request.ParentScopeID = overrides.ParentScopeID
	}
	request.ExplicitRecipient = overrides.ExplicitRecipient
	request.Grant = overrides.Grant
	request.ActorMembership = overrides.ActorMembership
	request.TargetMembership = overrides.TargetMembership
	return request
}

func privateMembershipForTest(memberID string, ownerMemberID string, scopeID string) *ScopeMembershipRecord {
	return &ScopeMembershipRecord{
		ScopeID:       scopeID,
		MemberID:      memberID,
		Visibility:    "private",
		OwnerMemberID: ownerMemberID,
	}
}
