package message

import (
	"strings"
	"time"
)

const (
	ScopeActorHuman        = "human"
	ScopeActorAgent        = "agent"
	ScopeActorPublicAgent  = "public_agent"
	ScopeActorPrivateAgent = "private_agent"
	ScopeActorApp          = "app"
	ScopeActorService      = "service"
	ScopeActorSystem       = "system"
)

const (
	ScopeAuthorizationSourceOwner             = "owner"
	ScopeAuthorizationSourceScopeGrant        = "scope_grant"
	ScopeAuthorizationSourceParentScopeGrant  = "parent_scope_grant"
	ScopeAuthorizationSourceSameOwnerPrivate  = "same_owner_private"
	ScopeAuthorizationSourceExplicitRecipient = "explicit_recipient"
)

const (
	ScopeDenyReasonMissingActor              = "missing_actor"
	ScopeDenyReasonMissingTargetPrivate      = "missing_target_private"
	ScopeDenyReasonServiceNoPrivateAuthority = "service_no_private_authority"
	ScopeDenyReasonPublicAgentNeedsGrant     = "public_agent_needs_grant"
	ScopeDenyReasonGrantInactive             = "grant_inactive"
	ScopeDenyReasonGrantScopeMismatch        = "grant_scope_mismatch"
	ScopeDenyReasonGrantOwnerMismatch        = "grant_owner_mismatch"
	ScopeDenyReasonGrantGranteeMismatch      = "grant_grantee_mismatch"
	ScopeDenyReasonPrivateScopeMismatch      = "private_scope_mismatch"
	ScopeDenyReasonPrivateParentScopeMissing = "private_parent_scope_missing"
	ScopeDenyReasonNoPrivateAuthority        = "no_private_authority"
)

const (
	ScopeRoutingMentionModeNormal          = "normal"
	ScopeRoutingMentionModeSingleForward   = "single_forward"
	ScopeRoutingMentionModeMultiForward    = "multi_forward"
	ScopeRoutingMentionModeExplicitContext = "explicit_context"
)

type PolicyEngine struct {
	now func() time.Time
}

func NewPolicyEngine() PolicyEngine {
	return PolicyEngine{now: time.Now}
}

func NewPolicyEngineWithClock(now func() time.Time) PolicyEngine {
	if now == nil {
		now = time.Now
	}
	return PolicyEngine{now: now}
}

type PrivateAuthorityRequest struct {
	ActorMemberID      string
	ActorType          string
	ActorOwnerMemberID string

	TargetPrivateAgentMemberID string
	TargetPrivateOwnerMemberID string
	TargetScopeID              string
	ParentScopeID              string

	ExplicitRecipient bool
	Grant             *ScopePrivateGrantRecord
	ActorMembership   *ScopeMembershipRecord
	TargetMembership  *ScopeMembershipRecord
}

type PrivateAuthorityDecision struct {
	Allowed              bool
	AuthorizationSource  string
	AuthorizedByMemberID string
	AuthorizationGrantID string
	AuthorizationScopeID string
	DenyReason           string
}

type MentionPolicyRequest struct {
	ActorMemberID      string
	ActorType          string
	ActorOwnerMemberID string

	TargetMemberID         string
	TargetType             string
	TargetScopeID          string
	ParentScopeID          string
	TargetPrivateOwnerID   string
	TargetParentMembership *ScopeMembershipRecord

	ExplicitRecipient bool
	Grant             *ScopePrivateGrantRecord
	ActorMembership   *ScopeMembershipRecord
	TargetMembership  *ScopeMembershipRecord
}

type MentionPolicyDecision struct {
	Allowed       bool
	LiveMention   bool
	MessageReason string
	Authority     PrivateAuthorityDecision
	DenyReason    string
}

type RoutingMentionModeRequest struct {
	ExplicitContext bool
	SourceCount     int
}

func (engine PolicyEngine) EvaluatePrivateAuthority(request PrivateAuthorityRequest) PrivateAuthorityDecision {
	actorMemberID := strings.TrimSpace(request.ActorMemberID)
	actorType := strings.TrimSpace(request.ActorType)
	targetPrivateAgentMemberID := strings.TrimSpace(request.TargetPrivateAgentMemberID)
	targetPrivateOwnerMemberID := strings.TrimSpace(request.TargetPrivateOwnerMemberID)
	targetScopeID := strings.TrimSpace(request.TargetScopeID)
	parentScopeID := strings.TrimSpace(request.ParentScopeID)
	if actorMemberID == "" || actorType == "" {
		return denyPrivateAuthority(ScopeDenyReasonMissingActor)
	}
	if targetPrivateAgentMemberID == "" || targetPrivateOwnerMemberID == "" || targetScopeID == "" {
		return denyPrivateAuthority(ScopeDenyReasonMissingTargetPrivate)
	}
	if actorType == ScopeActorApp || actorType == ScopeActorService || actorType == ScopeActorSystem {
		return denyPrivateAuthority(ScopeDenyReasonServiceNoPrivateAuthority)
	}

	if actorType == ScopeActorHuman && actorMemberID == targetPrivateOwnerMemberID {
		source := ScopeAuthorizationSourceOwner
		if request.ExplicitRecipient {
			source = ScopeAuthorizationSourceExplicitRecipient
		}
		return allowPrivateAuthority(source, actorMemberID, "", targetScopeID)
	}

	if request.Grant != nil {
		decision := evaluatePrivateAuthorityGrant(*request.Grant, actorMemberID, targetPrivateOwnerMemberID, targetScopeID, parentScopeID, engine.now())
		if decision.DenyReason == "" {
			if request.ExplicitRecipient {
				decision.AuthorizationSource = ScopeAuthorizationSourceExplicitRecipient
			}
			return decision
		}
		return decision
	}

	if actorType == ScopeActorPrivateAgent && strings.TrimSpace(request.ActorOwnerMemberID) == targetPrivateOwnerMemberID {
		if request.ActorMembership == nil || request.TargetMembership == nil {
			return denyPrivateAuthority(ScopeDenyReasonPrivateScopeMismatch)
		}
		if !privateMembershipMatches(*request.ActorMembership, actorMemberID, targetScopeID, targetPrivateOwnerMemberID) ||
			!privateMembershipMatches(*request.TargetMembership, targetPrivateAgentMemberID, targetScopeID, targetPrivateOwnerMemberID) {
			return denyPrivateAuthority(ScopeDenyReasonPrivateScopeMismatch)
		}
		return allowPrivateAuthority(ScopeAuthorizationSourceSameOwnerPrivate, targetPrivateOwnerMemberID, "", targetScopeID)
	}

	if actorType == ScopeActorPublicAgent && strings.TrimSpace(request.ActorOwnerMemberID) == targetPrivateOwnerMemberID {
		return denyPrivateAuthority(ScopeDenyReasonPublicAgentNeedsGrant)
	}
	return denyPrivateAuthority(ScopeDenyReasonNoPrivateAuthority)
}

func (engine PolicyEngine) EvaluateMentionPolicy(request MentionPolicyRequest) MentionPolicyDecision {
	targetMemberID := strings.TrimSpace(request.TargetMemberID)
	targetType := strings.TrimSpace(request.TargetType)
	if targetMemberID == "" || targetType == "" {
		return denyMentionPolicy(ScopeDenyReasonMissingTargetPrivate)
	}
	if targetType != ScopeActorPrivateAgent {
		return MentionPolicyDecision{
			Allowed:       true,
			LiveMention:   true,
			MessageReason: ScopeMessageReasonDirectMention,
		}
	}

	targetScopeID := strings.TrimSpace(request.TargetScopeID)
	parentScopeID := strings.TrimSpace(request.ParentScopeID)
	privateMembershipScopeID := parentScopeID
	if privateMembershipScopeID == "" {
		privateMembershipScopeID = targetScopeID
	}
	if request.TargetParentMembership == nil ||
		!privateMembershipMatches(*request.TargetParentMembership, targetMemberID, privateMembershipScopeID, strings.TrimSpace(request.TargetPrivateOwnerID)) {
		return denyMentionPolicy(ScopeDenyReasonPrivateParentScopeMissing)
	}

	authority := engine.EvaluatePrivateAuthority(PrivateAuthorityRequest{
		ActorMemberID:              request.ActorMemberID,
		ActorType:                  request.ActorType,
		ActorOwnerMemberID:         request.ActorOwnerMemberID,
		TargetPrivateAgentMemberID: targetMemberID,
		TargetPrivateOwnerMemberID: request.TargetPrivateOwnerID,
		TargetScopeID:              targetScopeID,
		ParentScopeID:              parentScopeID,
		ExplicitRecipient:          request.ExplicitRecipient,
		Grant:                      request.Grant,
		ActorMembership:            request.ActorMembership,
		TargetMembership:           request.TargetMembership,
	})
	if !authority.Allowed {
		return MentionPolicyDecision{Authority: authority, DenyReason: authority.DenyReason}
	}
	return MentionPolicyDecision{
		Allowed:       true,
		LiveMention:   true,
		MessageReason: ScopeMessageReasonDirectMention,
		Authority:     authority,
	}
}

func ResolveRoutingMentionMode(request RoutingMentionModeRequest) string {
	if request.ExplicitContext {
		return ScopeRoutingMentionModeExplicitContext
	}
	if request.SourceCount == 1 {
		return ScopeRoutingMentionModeSingleForward
	}
	if request.SourceCount > 1 {
		return ScopeRoutingMentionModeMultiForward
	}
	return ScopeRoutingMentionModeNormal
}

func evaluatePrivateAuthorityGrant(grant ScopePrivateGrantRecord, actorMemberID string, targetPrivateOwnerMemberID string, targetScopeID string, parentScopeID string, now time.Time) PrivateAuthorityDecision {
	grantID := strings.TrimSpace(grant.ID)
	if grantID == "" {
		return denyPrivateAuthority(ScopeDenyReasonGrantInactive)
	}
	if strings.TrimSpace(grant.OwnerMemberID) != targetPrivateOwnerMemberID {
		return denyPrivateAuthority(ScopeDenyReasonGrantOwnerMismatch)
	}
	if strings.TrimSpace(grant.GranteeMemberID) != actorMemberID {
		return denyPrivateAuthority(ScopeDenyReasonGrantGranteeMismatch)
	}
	if grant.RevokedAt != nil || (grant.ExpiresAt != nil && !grant.ExpiresAt.After(now)) {
		return denyPrivateAuthority(ScopeDenyReasonGrantInactive)
	}
	grantScopeID := strings.TrimSpace(grant.ScopeID)
	if grantScopeID == "" {
		return denyPrivateAuthority(ScopeDenyReasonGrantScopeMismatch)
	}
	switch grantScopeID {
	case targetScopeID:
		return allowPrivateAuthority(ScopeAuthorizationSourceScopeGrant, strings.TrimSpace(grant.GrantedByMemberID), grantID, targetScopeID)
	case parentScopeID:
		return allowPrivateAuthority(ScopeAuthorizationSourceParentScopeGrant, strings.TrimSpace(grant.GrantedByMemberID), grantID, parentScopeID)
	default:
		return denyPrivateAuthority(ScopeDenyReasonGrantScopeMismatch)
	}
}

func privateMembershipMatches(record ScopeMembershipRecord, memberID string, scopeID string, ownerMemberID string) bool {
	return strings.TrimSpace(record.MemberID) == strings.TrimSpace(memberID) &&
		strings.TrimSpace(record.ScopeID) == strings.TrimSpace(scopeID) &&
		strings.TrimSpace(record.Visibility) == "private" &&
		record.RemovedAt == nil &&
		strings.TrimSpace(record.OwnerMemberID) == strings.TrimSpace(ownerMemberID) &&
		strings.TrimSpace(ownerMemberID) != ""
}

func allowPrivateAuthority(source string, authorizedByMemberID string, grantID string, scopeID string) PrivateAuthorityDecision {
	return PrivateAuthorityDecision{
		Allowed:              true,
		AuthorizationSource:  source,
		AuthorizedByMemberID: strings.TrimSpace(authorizedByMemberID),
		AuthorizationGrantID: strings.TrimSpace(grantID),
		AuthorizationScopeID: strings.TrimSpace(scopeID),
	}
}

func denyPrivateAuthority(reason string) PrivateAuthorityDecision {
	return PrivateAuthorityDecision{DenyReason: reason}
}

func denyMentionPolicy(reason string) MentionPolicyDecision {
	return MentionPolicyDecision{DenyReason: reason}
}
