package message

import "strings"

// taskParentAuthorityPolicyMode is intentionally explicit: the event
// frontier and the policy shape are selected together, rather than inferred
// from an empty author expression. Live admission authorizes the viewer's
// active parent-Channel membership. Historical rows authorize the message
// author at m.created_at using the same parent topology and private-owner
// candidate relation.
type taskParentAuthorityPolicyMode string

const (
	taskParentAuthorityPolicyLive       taskParentAuthorityPolicyMode = "live"
	taskParentAuthorityPolicyHistorical taskParentAuthorityPolicyMode = "historical"
)

// taskParentAuthorityRelationOptions describes the aliases bound by one
// database read/write surface. Task authority is always derived from
// task.board_id -> task_boards.channel_id; tasks.channel_id is retained only
// as an integrity equality fence. The membership alias is the viewer's
// canonical parent-Channel membership.
type taskParentAuthorityRelationOptions struct {
	TaskAlias                 string
	BoardAlias                string
	ChannelAlias              string
	MembershipAlias           string
	GranteeMemberSQL          string
	AuthorMemberSQL           string
	OwnerMemberSQL            string
	PublicMembershipActiveSQL string
	EventAtSQL                string
	PolicyMode                taskParentAuthorityPolicyMode
}

// taskParentAuthorityRelationSQL is the sole composable Task authority
// relation consumed by historical readable-row predicates and live
// visible-scope admission. Both modes share topology, membership intervals,
// source-thread fences, and owner checks. Only the typed policy frontier
// differs: live uses CURRENT_TIMESTAMP and the authenticated Agent owner;
// historical uses the message-created timestamp and author/grant/participant
// candidates.
func taskParentAuthorityRelationSQL(options taskParentAuthorityRelationOptions) string {
	taskAlias := strings.TrimSpace(options.TaskAlias)
	boardAlias := strings.TrimSpace(options.BoardAlias)
	channelAlias := strings.TrimSpace(options.ChannelAlias)
	membershipAlias := strings.TrimSpace(options.MembershipAlias)
	granteeSQL := strings.TrimSpace(options.GranteeMemberSQL)
	authorSQL := strings.TrimSpace(options.AuthorMemberSQL)
	eventAtSQL := strings.TrimSpace(options.EventAtSQL)
	if taskAlias == "" || boardAlias == "" || channelAlias == "" || membershipAlias == "" || eventAtSQL == "" {
		return "(FALSE)"
	}
	if options.PolicyMode != taskParentAuthorityPolicyLive && options.PolicyMode != taskParentAuthorityPolicyHistorical {
		return "(FALSE)"
	}
	if options.PolicyMode == taskParentAuthorityPolicyHistorical && (granteeSQL == "" || authorSQL == "") {
		return "(FALSE)"
	}
	if options.PolicyMode == taskParentAuthorityPolicyLive && authorSQL != "" {
		return "(FALSE)"
	}

	ownerSQL := membershipAlias + ".owner_member_id"
	if suppliedOwner := strings.TrimSpace(options.OwnerMemberSQL); suppliedOwner != "" {
		ownerSQL = suppliedOwner
	}

	privateAuthority := "(" + ownerSQL + " IS NOT NULL)"
	if options.PolicyMode == taskParentAuthorityPolicyHistorical {
		privateAuthority = "(" + authorSQL + " = " + membershipAlias + ".member_id OR " +
			authorSQL + " = " + membershipAlias + ".owner_member_id OR " +
			taskParentAuthorityCandidateSQL(options) + ")"
	}

	publicMembershipSQL := strings.TrimSpace(options.PublicMembershipActiveSQL)
	if publicMembershipSQL == "" {
		publicMembershipSQL = "TRUE"
	}

	return "(" +
		taskCanonicalBoardChannelFenceSQL(taskAlias, boardAlias, channelAlias) +
		" AND " + membershipAlias + ".scope_id = " + channelAlias + ".id" +
		" AND " + membershipAlias + ".removed_at IS NULL" +
		" AND " + membershipAlias + ".effective_to_seq IS NULL" +
		" AND " + taskParentMembershipCoversAtSQL(membershipAlias, eventAtSQL) +
		" AND " + taskParentChannelOwnerActiveSQL(membershipAlias, channelAlias) +
		" AND (COALESCE(" + taskAlias + ".source_thread_channel_id, '') = '' OR (" +
		"COALESCE(" + taskAlias + ".source_thread_parent_channel_id, '') = '' OR " + taskAlias + ".source_thread_parent_channel_id = " + channelAlias + ".id)" +
		" AND EXISTS (SELECT 1 FROM scopes task_parent_source_thread " +
		"WHERE task_parent_source_thread.id = " + taskAlias + ".source_thread_channel_id " +
		"AND task_parent_source_thread.workspace_id = " + channelAlias + ".workspace_id " +
		"AND task_parent_source_thread.type = 'thread' " +
		"AND task_parent_source_thread.parent_scope_id = " + channelAlias + ".id " +
		"AND task_parent_source_thread.status = 'active' " +
		"AND task_parent_source_thread.archived_at IS NULL))" +
		" AND ( (" + membershipAlias + ".visibility = 'public' AND (" + publicMembershipSQL + ")) OR (" +
		membershipAlias + ".visibility = 'private'" +
		" AND COALESCE(" + membershipAlias + ".owner_member_id, '') <> ''" +
		" AND " + membershipAlias + ".owner_member_id = " + ownerSQL +
		" AND " + membershipAlias + ".archived_at IS NULL" +
		" AND " + privateAuthority +
		")))"
}

// taskParentAuthorityCandidateSQL is intentionally private to the canonical
// relation above. No caller may reimplement task-local grant/participant
// topology: both historical and live callers consume this exact candidate
// source.
func taskParentAuthorityCandidateSQL(options taskParentAuthorityRelationOptions) string {
	taskAlias := strings.TrimSpace(options.TaskAlias)
	channelAlias := strings.TrimSpace(options.ChannelAlias)
	membershipAlias := strings.TrimSpace(options.MembershipAlias)
	granteeSQL := strings.TrimSpace(options.GranteeMemberSQL)
	eventAtSQL := strings.TrimSpace(options.EventAtSQL)
	if taskAlias == "" || channelAlias == "" || membershipAlias == "" || granteeSQL == "" || eventAtSQL == "" {
		return "FALSE"
	}

	ownerSQL := membershipAlias + ".owner_member_id"
	return "(" +
		"EXISTS (SELECT 1 FROM scope_memberships task_parent_authority_sibling " +
		"JOIN agents task_parent_authority_sibling_agent " +
		"ON task_parent_authority_sibling_agent.workspace_id = " + channelAlias + ".workspace_id " +
		"AND task_parent_authority_sibling_agent.member_id = task_parent_authority_sibling.member_id " +
		"AND task_parent_authority_sibling_agent.status <> 'archived' " +
		"WHERE task_parent_authority_sibling.scope_id = " + channelAlias + ".id " +
		"AND task_parent_authority_sibling.member_id = " + granteeSQL +
		" AND task_parent_authority_sibling.member_id <> " + membershipAlias + ".member_id " +
		"AND task_parent_authority_sibling.owner_member_id = " + ownerSQL +
		" AND task_parent_authority_sibling.visibility = 'private' " +
		"AND task_parent_authority_sibling.removed_at IS NULL " +
		"AND task_parent_authority_sibling.archived_at IS NULL " +
		"AND task_parent_authority_sibling.effective_to_seq IS NULL " +
		"AND " + taskParentMembershipCoversAtSQL("task_parent_authority_sibling", eventAtSQL) + ")" +
		" OR EXISTS (SELECT 1 FROM scope_private_grants task_parent_authority_grant " +
		"WHERE task_parent_authority_grant.workspace_id = " + channelAlias + ".workspace_id " +
		"AND task_parent_authority_grant.owner_member_id = " + ownerSQL +
		" AND task_parent_authority_grant.grantee_member_id = " + granteeSQL +
		" AND " + eventAtSQL + " >= task_parent_authority_grant.created_at " +
		"AND (task_parent_authority_grant.expires_at IS NULL OR " + eventAtSQL + " < task_parent_authority_grant.expires_at) " +
		"AND (task_parent_authority_grant.revoked_at IS NULL OR " + eventAtSQL + " < task_parent_authority_grant.revoked_at) " +
		"AND (task_parent_authority_grant.scope_id = " + taskAlias + ".id " +
		"OR task_parent_authority_grant.scope_id = " + channelAlias + ".id " +
		"OR task_parent_authority_grant.scope_id = " + taskAlias + ".source_thread_channel_id))" +
		" OR EXISTS (SELECT 1 FROM task_scope_executions task_parent_authority_execution " +
		"JOIN task_scope_participant_metadata task_parent_authority_participant " +
		"ON task_parent_authority_participant.owner_scope_execution_id = task_parent_authority_execution.id " +
		"AND task_parent_authority_participant.member_id = " + granteeSQL +
		" AND " + eventAtSQL + " >= task_parent_authority_participant.added_at " +
		"AND (task_parent_authority_participant.expires_at IS NULL OR " + eventAtSQL + " < task_parent_authority_participant.expires_at) " +
		"AND (task_parent_authority_participant.removed_at IS NULL OR " + eventAtSQL + " < task_parent_authority_participant.removed_at) " +
		"AND (task_parent_authority_participant.left_at IS NULL OR " + eventAtSQL + " < task_parent_authority_participant.left_at) " +
		"WHERE task_parent_authority_execution.workspace_id = " + channelAlias + ".workspace_id " +
		"AND task_parent_authority_execution.owner_member_id = " + ownerSQL +
		" AND ((task_parent_authority_execution.scope_type = 'task' AND task_parent_authority_execution.scope_id = " + taskAlias + ".id) " +
		"OR (task_parent_authority_execution.scope_type = 'channel' AND task_parent_authority_execution.scope_id = " + channelAlias + ".id) " +
		"OR (task_parent_authority_execution.scope_type = 'thread' AND task_parent_authority_execution.scope_id = " + taskAlias + ".source_thread_channel_id)))" +
		")"
}

var taskHistoricalParentAuthoritySQL = taskParentAuthorityRelationSQL(taskParentAuthorityRelationOptions{
	TaskAlias:                 "task_read_source",
	BoardAlias:                "task_read_board",
	ChannelAlias:              "source_scope",
	MembershipAlias:           "sm",
	GranteeMemberSQL:          "m.author_member_id",
	AuthorMemberSQL:           "m.author_member_id",
	PublicMembershipActiveSQL: "sm.archived_at IS NULL",
	EventAtSQL:                "m.created_at",
	PolicyMode:                taskParentAuthorityPolicyHistorical,
})

var taskLiveParentAuthoritySQL = taskParentAuthorityRelationSQL(taskParentAuthorityRelationOptions{
	TaskAlias:                 "task",
	BoardAlias:                "task_board",
	ChannelAlias:              "source_scope",
	MembershipAlias:           "membership",
	GranteeMemberSQL:          "$2",
	OwnerMemberSQL:            "private_agent_owner.id",
	PublicMembershipActiveSQL: "$3 = '' OR membership.archived_at IS NULL",
	EventAtSQL:                "CURRENT_TIMESTAMP",
	PolicyMode:                taskParentAuthorityPolicyLive,
})
