package app

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type ServiceEventTarget struct {
	TargetKind               string
	TargetRef                string
	DeliveryScopeID          string
	PermissionCheckScopeID   string
	AuthoritySubjectMemberID string
	PrincipalMemberID        string
	ResolvedScopeTarget      access.ResolvedScopeTarget
	ScopeSnapshot            map[string]any
}

func serviceEventAuthoritySubjectMemberID(actor ServiceEventActor, target ServiceEventTarget) string {
	return firstNonEmpty(target.PrincipalMemberID, serviceEventActorPrincipalMemberID(actor), target.AuthoritySubjectMemberID)
}

func serviceEventDeliveryContractJSON(actor ServiceEventActor, target ServiceEventTarget) []byte {
	contract := serviceEventFinalResolvedScopeTarget(actor, target)
	payload, err := model.MarshalJSONObject("service event delivery contract", map[string]any{
		"targetKind":               strings.TrimSpace(target.TargetKind),
		"targetRef":                strings.TrimSpace(target.TargetRef),
		"storageScopeId":           strings.TrimSpace(contract.StorageScope.ScopeID),
		"storageScopeType":         strings.TrimSpace(string(contract.StorageScope.ScopeType)),
		"deliveryScopeId":          strings.TrimSpace(target.DeliveryScopeID),
		"deliveryScopeType":        strings.TrimSpace(string(contract.DeliveryScope.ScopeType)),
		"permissionCheckScopeId":   strings.TrimSpace(target.PermissionCheckScopeID),
		"authoritySubjectMemberId": strings.TrimSpace(serviceEventAuthoritySubjectMemberID(actor, target)),
		"permissionBasis":          serviceEventPermissionBasisContract(contract.PermissionBasis),
		"authoritySubjects":        serviceEventAuthoritySubjectContract(contract.Subjects),
	})
	if err != nil {
		return nil
	}
	return payload
}

func serviceEventFinalResolvedScopeTarget(actor ServiceEventActor, target ServiceEventTarget) access.ResolvedScopeTarget {
	contract := target.ResolvedScopeTarget.Normalized()
	if contract.Operation == "" {
		contract = serviceEventFallbackResolvedScopeTarget(actor, target)
	}
	authorityMemberID := strings.TrimSpace(serviceEventAuthoritySubjectMemberID(actor, target))
	if authorityMemberID == "" {
		return contract
	}
	for i := range contract.Subjects {
		if contract.Subjects[i].Kind == access.ScopeAuthoritySubjectRoutingPrincipal {
			contract.Subjects[i].MemberID = authorityMemberID
			return contract.Normalized()
		}
	}
	contract.Subjects = append(contract.Subjects, access.ScopeAuthoritySubject{
		Kind:     access.ScopeAuthoritySubjectRoutingPrincipal,
		MemberID: authorityMemberID,
	})
	return contract.Normalized()
}

func serviceEventFallbackResolvedScopeTarget(actor ServiceEventActor, target ServiceEventTarget) access.ResolvedScopeTarget {
	scopeKind := serviceEventScopeTargetKind(target.TargetKind)
	scope := access.ScopeRef{ScopeID: strings.TrimSpace(target.DeliveryScopeID), ScopeType: scopeKind}
	permissionScope := access.ScopeRef{ScopeID: strings.TrimSpace(target.PermissionCheckScopeID), ScopeType: scopeKind}
	return access.ResolvedScopeTarget{
		Operation: access.ScopeTargetOperationServiceEventDelivery,
		Requested: access.ScopeTargetRef{
			Kind: serviceEventScopeTargetKind(target.TargetKind),
			Ref:  strings.TrimSpace(target.TargetRef),
		},
		StorageScope:  scope,
		DeliveryScope: scope,
		Subjects: []access.ScopeAuthoritySubject{{
			Kind:     access.ScopeAuthoritySubjectRoutingPrincipal,
			MemberID: strings.TrimSpace(serviceEventAuthoritySubjectMemberID(actor, target)),
		}},
		PermissionBasis: []access.ScopePermissionBasis{{
			Kind:           access.ScopePermissionBasisDirectMembership,
			Scope:          permissionScope,
			DecisionSource: access.DecisionSourceChannelMembership,
			MemberID:       strings.TrimSpace(serviceEventAuthoritySubjectMemberID(actor, target)),
		}},
	}.Normalized()
}

func serviceEventScopeTargetKind(kind string) access.ScopeTargetKind {
	switch strings.TrimSpace(kind) {
	case "channel":
		return access.ScopeTargetKindChannel
	case "dm":
		return access.ScopeTargetKindDM
	case "thread":
		return access.ScopeTargetKindThread
	case "task":
		return access.ScopeTargetKindTask
	default:
		return access.ScopeTargetKind(strings.TrimSpace(kind))
	}
}

type serviceEventResolvedScopeTargetOptions struct {
	ParentScope     access.ScopeRef
	RootMessageID   string
	PermissionBasis []access.ScopePermissionBasis
}

func serviceEventResolvedScopeTarget(workspaceID string, targetKind string, targetRef string, deliveryScopeID string, actorMemberID string, options serviceEventResolvedScopeTargetOptions) access.ResolvedScopeTarget {
	scopeKind := serviceEventScopeTargetKind(targetKind)
	deliveryScope := access.ScopeRef{ScopeID: strings.TrimSpace(deliveryScopeID), ScopeType: scopeKind}
	permissionBasis := options.PermissionBasis
	if len(permissionBasis) == 0 {
		permissionBasis = []access.ScopePermissionBasis{{
			Kind:           access.ScopePermissionBasisDirectMembership,
			Scope:          deliveryScope,
			DecisionSource: access.DecisionSourceChannelMembership,
			MemberID:       strings.TrimSpace(actorMemberID),
		}}
	}
	return access.ResolvedScopeTarget{
		WorkspaceID: strings.TrimSpace(workspaceID),
		Operation:   access.ScopeTargetOperationServiceEventDelivery,
		Requested: access.ScopeTargetRef{
			Kind: scopeKind,
			Ref:  strings.TrimSpace(targetRef),
		},
		Subjects: []access.ScopeAuthoritySubject{{
			Kind:     access.ScopeAuthoritySubjectRoutingPrincipal,
			MemberID: strings.TrimSpace(actorMemberID),
		}},
		StorageScope:    deliveryScope,
		DeliveryScope:   deliveryScope,
		ParentScope:     options.ParentScope,
		RootMessageID:   strings.TrimSpace(options.RootMessageID),
		PermissionBasis: permissionBasis,
	}.Normalized()
}

func serviceEventPermissionBasisContract(items []access.ScopePermissionBasis) []map[string]any {
	contract := make([]map[string]any, 0, len(items))
	for _, item := range items {
		item = access.ScopePermissionBasis{
			Kind:           item.Kind,
			Scope:          item.Scope,
			DecisionSource: item.DecisionSource,
			MemberID:       strings.TrimSpace(item.MemberID),
			OwnerMemberID:  strings.TrimSpace(item.OwnerMemberID),
			GrantID:        strings.TrimSpace(item.GrantID),
			Reason:         strings.TrimSpace(item.Reason),
		}
		contract = append(contract, map[string]any{
			"kind":           strings.TrimSpace(string(item.Kind)),
			"scopeId":        strings.TrimSpace(item.Scope.ScopeID),
			"scopeType":      strings.TrimSpace(string(item.Scope.ScopeType)),
			"decisionSource": strings.TrimSpace(string(item.DecisionSource)),
			"memberId":       item.MemberID,
			"ownerMemberId":  item.OwnerMemberID,
			"grantId":        item.GrantID,
			"reason":         item.Reason,
		})
	}
	return contract
}

func serviceEventAuthoritySubjectContract(items []access.ScopeAuthoritySubject) []map[string]any {
	contract := make([]map[string]any, 0, len(items))
	for _, item := range items {
		contract = append(contract, map[string]any{
			"kind":           strings.TrimSpace(string(item.Kind)),
			"principalType":  strings.TrimSpace(string(item.PrincipalType)),
			"principalId":    strings.TrimSpace(item.PrincipalID),
			"memberId":       strings.TrimSpace(item.MemberID),
			"installationId": strings.TrimSpace(item.InstallationID),
		})
	}
	return contract
}

func (store *Store) ResolveServiceEventTargetForAgentTarget(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent, target string) (ServiceEventTarget, error) {
	// Keep the pre-unification task: forms bounded to this compatibility entry.
	// All modern author-facing targets are classified by the shared parser below.
	if taskSpec, ok := parseServiceEventTaskTarget(target); ok {
		if channelTarget, taskNumber, ok := parseServiceEventTaskChannelNumberTarget(taskSpec); ok {
			var resolved ServiceEventTarget
			if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
				channelID, err := resolveServiceEventAgentChannelTarget(ctx, tx, authenticatedAgent, channelTarget)
				if err != nil {
					return err
				}
				resolved, err = resolveServiceEventTaskTargetByChannelNumber(ctx, tx, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, channelID, taskNumber)
				return err
			}); err != nil {
				return ServiceEventTarget{}, err
			}
			return resolved, nil
		}
		var resolved ServiceEventTarget
		if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
			var err error
			resolved, err = resolveServiceEventTaskTargetByID(ctx, tx, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, taskSpec)
			return err
		}); err != nil {
			return ServiceEventTarget{}, err
		}
		return resolved, nil
	}

	parsed, err := domain.ParseMessageTargetReference(target)
	if err != nil {
		return ServiceEventTarget{}, mapMessagingError(err)
	}
	switch parsed.Kind {
	case domain.MessageTargetKindTask:
		var resolved ServiceEventTarget
		if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
			var err error
			resolved, err = resolveServiceEventTaskTargetByScopeID(ctx, tx, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, parsed.TaskScopeID)
			return err
		}); err != nil {
			return ServiceEventTarget{}, err
		}
		return resolved, nil
	case domain.MessageTargetKindTeam:
		// Team ids are not channel slugs. Resolve the author target through the
		// shared authorized send path so Team replies have the same materialization
		// semantics as ordinary channel replies, then convert the canonical scope
		// into the service-event delivery contract.
		authorized, err := store.ResolveWritableAgentMessageTarget(ctx, authenticatedAgent, target)
		if err != nil {
			return ServiceEventTarget{}, err
		}
		return ResolveServiceEventTargetForScope(ctx, store.db, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, authorized.StorageScope.ScopeID)
	case domain.MessageTargetKindWorkflow:
		return ServiceEventTarget{}, badRequest("Workflow Builder edit scopes do not support service-event subscriptions")
	}

	var resolved ServiceEventTarget
	if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		channelID, err := resolveServiceEventAgentChannelTarget(ctx, tx, authenticatedAgent, target)
		if err != nil {
			return err
		}
		resolved, err = ResolveServiceEventTargetForScope(ctx, tx, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, channelID)
		return err
	}); err != nil {
		return ServiceEventTarget{}, err
	}
	return resolved, nil
}

// ResolveServiceEventSubscriptionTargetForAgentTarget is the app-scoped MCP
// entry point for resolving delivery targets for pull-request subscriptions.
func (store *Store) ResolveServiceEventSubscriptionTargetForAgentTarget(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent, target string) (ServiceEventTarget, error) {
	return store.ResolveServiceEventTargetForAgentTarget(ctx, authenticatedAgent, target)
}

func resolveServiceEventAgentChannelTarget(ctx context.Context, queryer coredb.QueryExecer, authenticatedAgent agent.AuthenticatedAgent, target string) (string, error) {
	trimmed := strings.TrimSpace(target)
	channelSlug, ok := serviceEventDirectChannelSlug(trimmed)
	if ok {
		channelID, err := resolveServiceEventAgentEntryChannel(ctx, queryer, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, channelSlug)
		if err == nil {
			return channelID, nil
		}
		if !errors.Is(err, storeerr.ErrForbidden) {
			return "", err
		}
	}
	// Reply threads and DMs still need the message-layer resolver because their
	// canonical target is not the parent channel. If the message resolver rejects
	// a channel reply thread directly, resolve it through the same parent channel
	// roster basis used by agent channel/thread targets.
	channelID, err := message.ResolveChannelIDForAgentTarget(ctx, queryer, messagingActor(authenticatedAgent), trimmed)
	if err != nil {
		entryChannelID, entryErr := resolveServiceEventAgentReplyThreadTarget(ctx, queryer, authenticatedAgent, trimmed)
		if entryErr == nil {
			return entryChannelID, nil
		}
		if !errors.Is(entryErr, storeerr.ErrForbidden) && !errors.Is(entryErr, storeerr.ErrNotFound) {
			return "", entryErr
		}
		return "", mapMessagingError(err)
	}
	return channelID, nil
}

func resolveServiceEventAgentReplyThreadTarget(ctx context.Context, queryer coredb.Queryer, authenticatedAgent agent.AuthenticatedAgent, target string) (string, error) {
	parsed, err := domain.ParseMessageTargetReference(target)
	if err != nil {
		return "", err
	}
	reference := parsed.Thread
	if parsed.Kind != domain.MessageTargetKindReplyThread {
		return "", storeerr.ErrNotFound
	}
	if strings.TrimSpace(reference.ShortID) == "" || reference.Type != domain.ThreadTypeChannel {
		return "", storeerr.ErrNotFound
	}
	parentChannelID, err := resolveServiceEventAgentEntryChannel(ctx, queryer, authenticatedAgent.WorkspaceID, authenticatedAgent.MemberID, reference.ChannelSlug)
	if err != nil {
		return "", err
	}
	row := queryer.QueryRow(ctx, `
SELECT thread_scope.id
FROM scopes thread_scope
WHERE thread_scope.workspace_id = $1
  AND thread_scope.parent_scope_id = $2
  AND thread_scope.type = 'thread'
  AND thread_scope.status = 'active'
  AND thread_scope.root_message_short_id = $3
`, strings.TrimSpace(authenticatedAgent.WorkspaceID), parentChannelID, strings.TrimSpace(reference.ShortID))
	var threadChannelID string
	if err := row.Scan(&threadChannelID); err != nil {
		if coredb.IsNotFound(err) {
			return "", storeerr.ErrNotFound
		}
		return "", fmt.Errorf("resolve service event agent reply thread target: %w", err)
	}
	return threadChannelID, nil
}

func serviceEventChannelSlugByScope(ctx context.Context, queryer coredb.QueryRower, workspaceID string, channelID string) (string, error) {
	row := queryer.QueryRow(ctx, `
SELECT p.slug
FROM scopes s
JOIN scope_channel_profiles p
  ON p.scope_id = s.id
WHERE s.workspace_id = $1
  AND s.id = $2
  AND s.type = 'channel'
  AND s.status = 'active'
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(channelID))
	var slug string
	if err := row.Scan(&slug); err != nil {
		if coredb.IsNotFound(err) {
			return "", storeerr.ErrNotFound
		}
		return "", fmt.Errorf("load service event channel profile: %w", err)
	}
	return strings.TrimSpace(slug), nil
}

func serviceEventDirectChannelSlug(target string) (string, bool) {
	parsed, err := domain.ParseMessageTargetReference(target)
	if err != nil || parsed.Kind != domain.MessageTargetKindChannel {
		return "", false
	}
	return parsed.Thread.ChannelSlug, true
}

func resolveServiceEventAgentEntryChannel(ctx context.Context, queryer coredb.QueryRower, workspaceID string, agentMemberID string, channelSlug string) (string, error) {
	row := queryer.QueryRow(ctx, `
SELECT s.id
FROM scopes s
JOIN scope_channel_profiles p
  ON p.scope_id = s.id
JOIN scope_memberships sm
  ON sm.scope_id = s.id
 AND sm.member_id = $2
 AND sm.removed_at IS NULL
 AND sm.archived_at IS NULL
 AND sm.effective_to_seq IS NULL
WHERE s.workspace_id = $1
  AND s.type = 'channel'
  AND s.status = 'active'
  AND p.slug = $3
  AND sm.visibility IN ('public', 'private')
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(agentMemberID), strings.TrimSpace(channelSlug))
	var channelID string
	if err := row.Scan(&channelID); err != nil {
		if coredb.IsNotFound(err) {
			return "", storeerr.ErrForbidden
		}
		return "", fmt.Errorf("resolve service event agent entry channel: %w", err)
	}
	return channelID, nil
}

// ListServiceEventes returns non-removed watches in a bounded workspace scope.
// Agent actors must list through one app installation at a time so the shared
// service can re-check service_event.read authorization before exposing watch
// records.

func ResolveServiceEventTargetForScope(ctx context.Context, queryer coredb.Queryer, workspaceID string, actorMemberID string, targetScopeID string) (ServiceEventTarget, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedActorMemberID := strings.TrimSpace(actorMemberID)
	trimmedTargetScopeID := strings.TrimSpace(targetScopeID)
	if trimmedWorkspaceID == "" || trimmedActorMemberID == "" || trimmedTargetScopeID == "" {
		return ServiceEventTarget{}, badRequest("workspaceId, actor member, and target are required")
	}

	// Provider-watch records created before canonical scope targets still carry
	// task:<task-id>. Keep that compatibility syntax bounded to this resolver;
	// channel-number forms require the agent-aware entry above.
	if taskSpec, ok := parseServiceEventTaskTarget(trimmedTargetScopeID); ok {
		if _, _, isChannelNumber := parseServiceEventTaskChannelNumberTarget(taskSpec); isChannelNumber {
			return ServiceEventTarget{}, badRequest("service event task target must use a task id outside agent MCP current-target resolution")
		}
		return resolveServiceEventTaskTargetByID(ctx, queryer, trimmedWorkspaceID, trimmedActorMemberID, taskSpec)
	}

	if task, err := resolveServiceEventTaskTargetByScopeID(ctx, queryer, trimmedWorkspaceID, trimmedActorMemberID, trimmedTargetScopeID); err == nil {
		return task, nil
	} else if !errors.Is(err, storeerr.ErrNotFound) {
		return ServiceEventTarget{}, err
	}

	if channel, err := access.LookupChannel(ctx, queryer, trimmedWorkspaceID, trimmedTargetScopeID); err == nil {
		joined, err := access.IsPublicMemberInChannel(ctx, queryer, trimmedActorMemberID, channel.ChannelID)
		if err != nil {
			return ServiceEventTarget{}, err
		}
		if channel.Type == "channel" {
			hasAgentEntry := false
			if !joined {
				var agentEntryErr error
				hasAgentEntry, agentEntryErr = serviceEventAgentHasChannelEntry(ctx, queryer, trimmedWorkspaceID, trimmedActorMemberID, channel.ChannelID)
				if agentEntryErr != nil {
					return ServiceEventTarget{}, agentEntryErr
				}
			}
			if !joined && !hasAgentEntry {
				return ServiceEventTarget{}, storeerr.ErrForbidden
			}
			permissionBasisKind := access.ScopePermissionBasisDirectMembership
			if hasAgentEntry {
				permissionBasisKind = access.ScopePermissionBasisOwnerPrivateMembership
			}
			return ServiceEventTarget{
				TargetKind:               "channel",
				TargetRef:                channel.ChannelID,
				DeliveryScopeID:          channel.ChannelID,
				PermissionCheckScopeID:   channel.ChannelID,
				AuthoritySubjectMemberID: trimmedActorMemberID,
				ResolvedScopeTarget: serviceEventResolvedScopeTarget(trimmedWorkspaceID, "channel", channel.ChannelID, channel.ChannelID, trimmedActorMemberID, serviceEventResolvedScopeTargetOptions{
					PermissionBasis: []access.ScopePermissionBasis{{
						Kind:           permissionBasisKind,
						Scope:          access.ScopeRef{ScopeID: channel.ChannelID, ScopeType: access.ScopeTargetKindChannel},
						DecisionSource: access.DecisionSourceChannelMembership,
						MemberID:       trimmedActorMemberID,
					}},
				}),
				ScopeSnapshot: map[string]any{
					"target_kind":                 "channel",
					"target_ref":                  channel.ChannelID,
					"delivery_scope_id":           channel.ChannelID,
					"permission_check_scope_id":   channel.ChannelID,
					"authority_subject_member_id": trimmedActorMemberID,
				},
			}, nil
		}
		if channel.Type == "thread" {
			reply, err := resolveServiceEventReplyThread(ctx, queryer, trimmedWorkspaceID, trimmedActorMemberID, channel.ChannelID)
			if err != nil {
				return ServiceEventTarget{}, err
			}
			return reply, nil
		}
		return ServiceEventTarget{}, badRequest("target channel type is not supported")
	} else if err != nil && !errors.Is(err, access.ErrNotFound) {
		return ServiceEventTarget{}, err
	}

	if dm, err := resolveServiceEventDM(ctx, queryer, trimmedWorkspaceID, trimmedActorMemberID, trimmedTargetScopeID); err == nil {
		return dm, nil
	} else if err != nil && !errors.Is(err, storeerr.ErrNotFound) {
		return ServiceEventTarget{}, err
	}
	return ServiceEventTarget{}, storeerr.ErrNotFound
}

func parseServiceEventTaskTarget(target string) (string, bool) {
	trimmed := strings.TrimSpace(target)
	if !strings.HasPrefix(strings.ToLower(trimmed), "task:") {
		return "", false
	}
	taskSpec := strings.TrimSpace(trimmed[len("task:"):])
	return taskSpec, taskSpec != ""
}

func parseServiceEventTaskChannelNumberTarget(taskSpec string) (string, int64, bool) {
	trimmed := strings.TrimSpace(taskSpec)
	separator := strings.LastIndex(trimmed, ":")
	if separator <= 0 || separator == len(trimmed)-1 {
		return "", 0, false
	}
	channelTarget := strings.TrimSpace(trimmed[:separator])
	taskNumber, err := strconv.ParseInt(strings.TrimSpace(trimmed[separator+1:]), 10, 64)
	if err != nil || taskNumber <= 0 || !strings.HasPrefix(channelTarget, "#") {
		return "", 0, false
	}
	return channelTarget, taskNumber, true
}

func resolveServiceEventTaskTargetByID(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, taskID string) (ServiceEventTarget, error) {
	return resolveServiceEventTaskTarget(ctx, queryer, workspaceID, actorMemberID, strings.TrimSpace(taskID), "", 0)
}

func resolveServiceEventTaskTargetByScopeID(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, taskScopeID string) (ServiceEventTarget, error) {
	return resolveServiceEventTaskTargetQuery(ctx, queryer, workspaceID, actorMemberID, "", strings.TrimSpace(taskScopeID), "", 0)
}

func resolveServiceEventTaskTargetByChannelNumber(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, channelID string, taskNumber int64) (ServiceEventTarget, error) {
	return resolveServiceEventTaskTarget(ctx, queryer, workspaceID, actorMemberID, "", strings.TrimSpace(channelID), taskNumber)
}

func resolveServiceEventTaskTarget(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, taskID string, channelID string, taskNumber int64) (ServiceEventTarget, error) {
	return resolveServiceEventTaskTargetQuery(ctx, queryer, workspaceID, actorMemberID, taskID, "", channelID, taskNumber)
}

func resolveServiceEventTaskTargetQuery(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, taskID string, taskScopeID string, channelID string, taskNumber int64) (ServiceEventTarget, error) {
	query := `
SELECT
    t.id,
    t.task_scope_id,
    tb.channel_id,
    t.task_number,
    COALESCE(NULLIF(t.execution_owner_member_id, ''), NULLIF(creator_private_agent.owner_member_id, ''), NULLIF(t.created_by_ref, ''), t.created_by_member_id)
FROM tasks t
JOIN task_boards tb
  ON tb.id = t.board_id
 AND tb.workspace_id = $1
JOIN scopes task_scope
  ON task_scope.id = t.task_scope_id
 AND task_scope.workspace_id = $1
 AND task_scope.type = 'task'
 AND task_scope.status = 'active'
 AND task_scope.archived_at IS NULL
JOIN scopes task_channel
  ON task_channel.id = tb.channel_id
 AND task_channel.workspace_id = $1
 AND task_channel.type = 'channel'
 AND task_channel.status = 'active'
 AND task_channel.archived_at IS NULL
 AND t.channel_id = tb.channel_id
LEFT JOIN scope_memberships creator_private_agent
  ON creator_private_agent.scope_id = tb.channel_id
 AND creator_private_agent.member_id = COALESCE(NULLIF(t.created_by_ref, ''), t.created_by_member_id)
 AND creator_private_agent.visibility = 'private'
 AND creator_private_agent.owner_member_id IS NOT NULL
 AND creator_private_agent.effective_to_seq IS NULL
WHERE tb.workspace_id = $1
`
	args := []any{strings.TrimSpace(workspaceID)}
	if strings.TrimSpace(taskID) != "" {
		args = append(args, strings.TrimSpace(taskID))
		query += fmt.Sprintf("  AND t.id = $%d\n", len(args))
	} else if strings.TrimSpace(taskScopeID) != "" {
		args = append(args, strings.TrimSpace(taskScopeID))
		query += fmt.Sprintf("  AND t.task_scope_id = $%d\n", len(args))
	} else {
		if strings.TrimSpace(channelID) == "" || taskNumber <= 0 {
			return ServiceEventTarget{}, badRequest("service event task target is invalid")
		}
		args = append(args, strings.TrimSpace(channelID), taskNumber)
		query += fmt.Sprintf("  AND tb.channel_id = $%d\n  AND t.task_number = $%d\n", len(args)-1, len(args))
	}
	row := queryer.QueryRow(ctx, query, args...)
	var resolvedTaskID string
	var resolvedTaskScopeID string
	var resolvedChannelID string
	var resolvedTaskNumber int64
	var executionOwnerMemberID string
	if err := row.Scan(&resolvedTaskID, &resolvedTaskScopeID, &resolvedChannelID, &resolvedTaskNumber, &executionOwnerMemberID); err != nil {
		if coredb.IsNotFound(err) {
			return ServiceEventTarget{}, storeerr.ErrNotFound
		}
		return ServiceEventTarget{}, fmt.Errorf("resolve service event task target: %w", err)
	}
	trimmedActorMemberID := strings.TrimSpace(actorMemberID)
	joined, err := access.IsPublicMemberInChannel(ctx, queryer, trimmedActorMemberID, resolvedChannelID)
	if err != nil {
		return ServiceEventTarget{}, err
	}
	hasAgentEntry := false
	if !joined {
		var agentEntryErr error
		hasAgentEntry, agentEntryErr = serviceEventAgentHasChannelEntry(ctx, queryer, workspaceID, trimmedActorMemberID, resolvedChannelID)
		if agentEntryErr != nil {
			return ServiceEventTarget{}, agentEntryErr
		}
	}
	if !joined && !hasAgentEntry && trimmedActorMemberID != strings.TrimSpace(executionOwnerMemberID) {
		return ServiceEventTarget{}, storeerr.ErrForbidden
	}
	permissionBasisKind := access.ScopePermissionBasisTaskBoardChannel
	if hasAgentEntry {
		permissionBasisKind = access.ScopePermissionBasisOwnerPrivateMembership
	}
	if !joined && !hasAgentEntry {
		permissionBasisKind = access.ScopePermissionBasisTaskCollaboratorGrant
	}
	return ServiceEventTarget{
		TargetKind:               "task",
		TargetRef:                resolvedTaskID,
		DeliveryScopeID:          resolvedTaskScopeID,
		PermissionCheckScopeID:   resolvedChannelID,
		AuthoritySubjectMemberID: trimmedActorMemberID,
		ResolvedScopeTarget: serviceEventResolvedScopeTarget(workspaceID, "task", resolvedTaskID, resolvedTaskScopeID, trimmedActorMemberID, serviceEventResolvedScopeTargetOptions{
			ParentScope: access.ScopeRef{ScopeID: resolvedChannelID, ScopeType: access.ScopeTargetKindChannel},
			PermissionBasis: []access.ScopePermissionBasis{{
				Kind:           permissionBasisKind,
				Scope:          access.ScopeRef{ScopeID: resolvedChannelID, ScopeType: access.ScopeTargetKindChannel},
				DecisionSource: access.DecisionSourceChannelMembership,
				MemberID:       trimmedActorMemberID,
				OwnerMemberID:  strings.TrimSpace(executionOwnerMemberID),
			}},
		}),
		ScopeSnapshot: map[string]any{
			"target_kind":                 "task",
			"target_ref":                  resolvedTaskID,
			"delivery_scope_id":           resolvedTaskScopeID,
			"permission_check_scope_id":   resolvedChannelID,
			"authority_subject_member_id": trimmedActorMemberID,
			"task_scope_id":               resolvedTaskScopeID,
			"task_number":                 resolvedTaskNumber,
			"execution_owner_member_id":   executionOwnerMemberID,
		},
	}, nil
}

func serviceEventAgentHasChannelEntry(ctx context.Context, queryer coredb.QueryRower, workspaceID string, agentMemberID string, channelID string) (bool, error) {
	allowed, err := hasAgentChannelWriteMembership(ctx, queryer, workspaceID, agentMemberID, channelID)
	if err != nil {
		return false, fmt.Errorf("check service event agent channel entry: %w", err)
	}
	return allowed, nil
}

func resolveServiceEventReplyThread(ctx context.Context, queryer coredb.Queryer, workspaceID string, actorMemberID string, threadChannelID string) (ServiceEventTarget, error) {
	row := queryer.QueryRow(ctx, `
SELECT
    thread_scope.id,
    thread_scope.root_message_id,
    parent_scope.type,
    parent_scope.id
FROM scopes thread_scope
JOIN scopes parent_scope
  ON parent_scope.workspace_id = thread_scope.workspace_id
 AND parent_scope.id = thread_scope.parent_scope_id
 AND parent_scope.status = 'active'
WHERE thread_scope.workspace_id = $1
  AND thread_scope.id = $2
  AND thread_scope.type = 'thread'
  AND thread_scope.status = 'active'
`, workspaceID, threadChannelID)
	var threadScopeID string
	var parentMessageID string
	var parentThreadType string
	var parentScopeID string
	if err := row.Scan(&threadScopeID, &parentMessageID, &parentThreadType, &parentScopeID); err != nil {
		if coredb.IsNotFound(err) {
			return ServiceEventTarget{}, storeerr.ErrNotFound
		}
		return ServiceEventTarget{}, fmt.Errorf("resolve service event reply thread: %w", err)
	}
	permissionCheckScopeID := strings.TrimSpace(parentScopeID)
	permissionBasisKind := access.ScopePermissionBasisParentDMParticipant
	decisionSource := access.DecisionSourceDMParticipant
	if parentThreadType == "channel" {
		joined, err := access.IsPublicMemberInChannel(ctx, queryer, actorMemberID, parentScopeID)
		if err != nil {
			return ServiceEventTarget{}, err
		}
		hasAgentEntry := false
		if !joined {
			var agentEntryErr error
			hasAgentEntry, agentEntryErr = serviceEventAgentHasChannelEntry(ctx, queryer, workspaceID, actorMemberID, parentScopeID)
			if agentEntryErr != nil {
				return ServiceEventTarget{}, agentEntryErr
			}
		}
		if !joined && !hasAgentEntry {
			return ServiceEventTarget{}, storeerr.ErrForbidden
		}
		permissionBasisKind = access.ScopePermissionBasisParentMembership
		decisionSource = access.DecisionSourceChannelMembership
		if !joined && hasAgentEntry {
			permissionBasisKind = access.ScopePermissionBasisOwnerPrivateMembership
		}
	} else if parentThreadType == "dm" {
		if err := requireServiceEventDMParticipant(ctx, queryer, workspaceID, actorMemberID, parentScopeID); err != nil {
			return ServiceEventTarget{}, err
		}
	} else {
		return ServiceEventTarget{}, badRequest("reply thread parent type is not supported")
	}
	return ServiceEventTarget{
		TargetKind:               "thread",
		TargetRef:                strings.TrimSpace(threadScopeID),
		DeliveryScopeID:          strings.TrimSpace(threadScopeID),
		PermissionCheckScopeID:   permissionCheckScopeID,
		AuthoritySubjectMemberID: strings.TrimSpace(actorMemberID),
		ResolvedScopeTarget: serviceEventResolvedScopeTarget(workspaceID, "thread", threadScopeID, threadScopeID, actorMemberID, serviceEventResolvedScopeTargetOptions{
			ParentScope:   access.ScopeRef{ScopeID: parentScopeID, ScopeType: serviceEventScopeTargetKind(parentThreadType)},
			RootMessageID: parentMessageID,
			PermissionBasis: []access.ScopePermissionBasis{{
				Kind:           permissionBasisKind,
				Scope:          access.ScopeRef{ScopeID: parentScopeID, ScopeType: serviceEventScopeTargetKind(parentThreadType)},
				DecisionSource: decisionSource,
				MemberID:       strings.TrimSpace(actorMemberID),
			}},
		}),
		ScopeSnapshot: map[string]any{
			"target_kind":                 "thread",
			"target_ref":                  strings.TrimSpace(threadScopeID),
			"delivery_scope_id":           strings.TrimSpace(threadScopeID),
			"permission_check_scope_id":   permissionCheckScopeID,
			"authority_subject_member_id": strings.TrimSpace(actorMemberID),
			"parent_message_id":           parentMessageID,
		},
	}, nil
}

func resolveServiceEventDM(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, dmThreadID string) (ServiceEventTarget, error) {
	if err := requireServiceEventDMParticipant(ctx, queryer, workspaceID, actorMemberID, dmThreadID); err != nil {
		return ServiceEventTarget{}, err
	}
	return ServiceEventTarget{
		TargetKind:               "dm",
		TargetRef:                dmThreadID,
		DeliveryScopeID:          dmThreadID,
		PermissionCheckScopeID:   dmThreadID,
		AuthoritySubjectMemberID: strings.TrimSpace(actorMemberID),
		ResolvedScopeTarget: serviceEventResolvedScopeTarget(workspaceID, "dm", dmThreadID, dmThreadID, actorMemberID, serviceEventResolvedScopeTargetOptions{
			PermissionBasis: []access.ScopePermissionBasis{{
				Kind:           access.ScopePermissionBasisDirectMembership,
				Scope:          access.ScopeRef{ScopeID: dmThreadID, ScopeType: access.ScopeTargetKindDM},
				DecisionSource: access.DecisionSourceDMParticipant,
				MemberID:       strings.TrimSpace(actorMemberID),
			}},
		}),
		ScopeSnapshot: map[string]any{
			"target_kind":                 "dm",
			"target_ref":                  dmThreadID,
			"delivery_scope_id":           dmThreadID,
			"permission_check_scope_id":   dmThreadID,
			"authority_subject_member_id": strings.TrimSpace(actorMemberID),
		},
	}, nil
}

func requireServiceEventDMParticipant(ctx context.Context, queryer coredb.QueryRower, workspaceID string, actorMemberID string, dmThreadID string) error {
	row := queryer.QueryRow(ctx, `
SELECT 1
FROM scopes s
JOIN scope_memberships sm
  ON sm.scope_id = s.id
 AND sm.member_id = $3
 AND sm.removed_at IS NULL
 AND sm.archived_at IS NULL
 AND sm.effective_to_seq IS NULL
WHERE s.workspace_id = $1
  AND s.id = $2
  AND s.type = 'dm'
  AND s.status = 'active'
LIMIT 1
`, workspaceID, dmThreadID, actorMemberID)
	var marker int
	if err := row.Scan(&marker); err != nil {
		if coredb.IsNotFound(err) {
			return storeerr.ErrNotFound
		}
		return fmt.Errorf("check service event dm participant: %w", err)
	}
	return nil
}

func validateServiceEventTarget(target ServiceEventTarget) error {
	if strings.TrimSpace(target.TargetKind) == "" || strings.TrimSpace(target.TargetRef) == "" || strings.TrimSpace(target.DeliveryScopeID) == "" || strings.TrimSpace(target.PermissionCheckScopeID) == "" {
		return badRequest("target kind, target ref, delivery scope, and permission-check scope are required")
	}
	switch strings.TrimSpace(target.TargetKind) {
	case "channel", "thread", "task", "dm":
		return nil
	default:
		return badRequest("target kind is not supported")
	}
}
