package host

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	hostdomain "hyapp/services/user-service/internal/domain/host"
	hostservice "hyapp/services/user-service/internal/service/host"
)

// ExternalInviteBD 创建“外管操作者 -> BD”的待确认邀请。
// Portal 身份不属于 user-service，但操作者和目标用户的 active/region 事实必须在本事务内锁定重验。
func (r *Repository) ExternalInviteBD(ctx context.Context, command hostservice.ExternalInviteBDCommand) (hostdomain.RoleInvitation, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	defer tx.Rollback()

	if replayed, ok, err := replayExternalInvitation(ctx, tx, command.CommandID, hostdomain.CommandTypeExternalInviteBD); err != nil || ok {
		return replayed, err
	}
	if err := requireExternalOperatorAndTargetRegion(ctx, tx, command.OperatorUserID, command.TargetUserID, command.ExpectedRegionID); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if _, ok, err := queryBDProfileMaybe(ctx, tx, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has active bd profile")
	}
	if pending, ok, err := queryPendingRoleInvitationByTarget(ctx, tx, hostdomain.InvitationTypeExternalBD, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		if pending.ExternalOperatorUserID != command.OperatorUserID || pending.ParentOwnerUserID != command.OperatorUserID {
			return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has pending bd invitation")
		}
		if err := bindExternalCommandReplay(ctx, tx, command.CommandID, hostdomain.CommandTypeExternalInviteBD, pending.InvitationID, command.NowMs); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		if err := tx.Commit(); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		return pending, nil
	}

	invitation := hostdomain.RoleInvitation{
		InvitationID:           command.InvitationID,
		CommandID:              command.CommandID,
		InvitationType:         hostdomain.InvitationTypeExternalBD,
		Status:                 hostdomain.InvitationStatusPending,
		InviterUserID:          command.OperatorUserID,
		TargetUserID:           command.TargetUserID,
		ExternalOperatorUserID: command.OperatorUserID,
		RegionID:               command.ExpectedRegionID,
		ParentOwnerUserID:      command.OperatorUserID,
		CreatedAtMs:            command.NowMs,
		UpdatedAtMs:            command.NowMs,
	}
	if err := persistExternalInvitation(ctx, tx, invitation, hostdomain.CommandTypeExternalInviteBD, command.NowMs); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if err := tx.Commit(); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	return invitation, nil
}

// ExternalInviteAgency 为 scope 内明确选择的 BD 创建 Agency 邀请。
// BD 的 parent_owner_user_id 是授权索引；不能用 created_by_user_id 或 parent_leader_user_id 猜外管归属。
func (r *Repository) ExternalInviteAgency(ctx context.Context, command hostservice.ExternalInviteAgencyCommand) (hostdomain.RoleInvitation, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	defer tx.Rollback()

	if replayed, ok, err := replayExternalInvitation(ctx, tx, command.CommandID, hostdomain.CommandTypeExternalInviteAgency); err != nil || ok {
		return replayed, err
	}
	if err := requireExternalOperatorAndTargetRegion(ctx, tx, command.OperatorUserID, command.TargetUserID, command.ExpectedRegionID); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	bd, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.ParentBDUserID)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if bd.Status != hostdomain.BDStatusActive || bd.Role != hostdomain.BDRoleBD || bd.RegionID != command.ExpectedRegionID ||
		bd.ParentLeaderUserID != 0 || bd.ParentOwnerUserID <= 0 ||
		!containsExternalScopeOwner(command.ScopeOwnerUserIDs, bd.ParentOwnerUserID) {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "parent bd is outside external team scope")
	}
	if _, err := requireExternalScopeOwnerRegion(ctx, tx, bd.ParentOwnerUserID, command.ExpectedRegionID); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if _, ok, err := queryActiveAgencyByOwner(ctx, tx, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already owns active agency")
	}
	if pending, ok, err := queryPendingRoleInvitationByTarget(ctx, tx, hostdomain.InvitationTypeExternalAgency, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		if pending.ExternalOperatorUserID != command.OperatorUserID || pending.ParentBDUserID != bd.UserID || pending.ParentOwnerUserID != bd.ParentOwnerUserID {
			return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has pending agency invitation")
		}
		if err := bindExternalCommandReplay(ctx, tx, command.CommandID, hostdomain.CommandTypeExternalInviteAgency, pending.InvitationID, command.NowMs); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		if err := tx.Commit(); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		return pending, nil
	}

	invitation := hostdomain.RoleInvitation{
		InvitationID:           command.InvitationID,
		CommandID:              command.CommandID,
		InvitationType:         hostdomain.InvitationTypeExternalAgency,
		Status:                 hostdomain.InvitationStatusPending,
		InviterUserID:          command.OperatorUserID,
		InviterBDUserID:        bd.UserID,
		TargetUserID:           command.TargetUserID,
		ExternalOperatorUserID: command.OperatorUserID,
		RegionID:               command.ExpectedRegionID,
		AgencyName:             command.AgencyName,
		ParentBDUserID:         bd.UserID,
		ParentLeaderUserID:     bd.ParentLeaderUserID,
		ParentOwnerUserID:      bd.ParentOwnerUserID,
		CreatedAtMs:            command.NowMs,
		UpdatedAtMs:            command.NowMs,
	}
	if err := persistExternalInvitation(ctx, tx, invitation, hostdomain.CommandTypeExternalInviteAgency, command.NowMs); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if err := tx.Commit(); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	return invitation, nil
}

// ExternalInviteHost 为 scope 内明确选择的 Agency 创建 Host 邀请。
func (r *Repository) ExternalInviteHost(ctx context.Context, command hostservice.ExternalInviteHostCommand) (hostdomain.RoleInvitation, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	defer tx.Rollback()

	if replayed, ok, err := replayExternalInvitation(ctx, tx, command.CommandID, hostdomain.CommandTypeExternalInviteHost); err != nil || ok {
		return replayed, err
	}
	if err := requireExternalOperatorAndTargetRegion(ctx, tx, command.OperatorUserID, command.TargetUserID, command.ExpectedRegionID); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	agency, err := queryAgency(ctx, tx, "WHERE agency_id = ? FOR UPDATE", command.AgencyID)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if agency.Status != hostdomain.AgencyStatusActive || !agency.JoinEnabled || agency.RegionID != command.ExpectedRegionID {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "agency is not active in external region")
	}
	bd, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", agency.ParentBDUserID)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if bd.Status != hostdomain.BDStatusActive || bd.ParentLeaderUserID != 0 || bd.ParentOwnerUserID <= 0 ||
		!containsExternalScopeOwner(command.ScopeOwnerUserIDs, bd.ParentOwnerUserID) {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "agency is outside external team scope")
	}
	if _, err := requireExternalScopeOwnerRegion(ctx, tx, bd.ParentOwnerUserID, command.ExpectedRegionID); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if _, ok, err := queryActiveMembershipByHost(ctx, tx, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has active agency membership")
	}
	if _, ok, err := queryActiveAgencyByOwner(ctx, tx, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already owns active agency")
	}
	if pending, ok, err := queryPendingRoleInvitationByTarget(ctx, tx, hostdomain.InvitationTypeExternalHost, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		if pending.ExternalOperatorUserID != command.OperatorUserID || pending.ParentAgencyID != agency.AgencyID || pending.ParentOwnerUserID != bd.ParentOwnerUserID {
			return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has pending host invitation")
		}
		if err := bindExternalCommandReplay(ctx, tx, command.CommandID, hostdomain.CommandTypeExternalInviteHost, pending.InvitationID, command.NowMs); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		if err := tx.Commit(); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		return pending, nil
	}

	invitation := hostdomain.RoleInvitation{
		InvitationID:           command.InvitationID,
		CommandID:              command.CommandID,
		InvitationType:         hostdomain.InvitationTypeExternalHost,
		Status:                 hostdomain.InvitationStatusPending,
		InviterUserID:          command.OperatorUserID,
		InviterBDUserID:        bd.UserID,
		TargetUserID:           command.TargetUserID,
		ExternalOperatorUserID: command.OperatorUserID,
		RegionID:               command.ExpectedRegionID,
		AgencyName:             agency.Name,
		ParentBDUserID:         bd.UserID,
		ParentLeaderUserID:     bd.ParentLeaderUserID,
		ParentOwnerUserID:      bd.ParentOwnerUserID,
		ParentAgencyID:         agency.AgencyID,
		CreatedAtMs:            command.NowMs,
		UpdatedAtMs:            command.NowMs,
	}
	if err := persistExternalInvitation(ctx, tx, invitation, hostdomain.CommandTypeExternalInviteHost, command.NowMs); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if err := tx.Commit(); err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	return invitation, nil
}

func replayExternalInvitation(ctx context.Context, tx *sql.Tx, commandID string, commandType string) (hostdomain.RoleInvitation, bool, error) {
	existing, ok, err := commandResult(ctx, tx, commandID)
	if err != nil || !ok {
		return hostdomain.RoleInvitation{}, false, err
	}
	if err := requireCommandResult(existing, commandType, hostdomain.ResultTypeInvitation); err != nil {
		return hostdomain.RoleInvitation{}, true, err
	}
	invitation, err := queryRoleInvitation(ctx, tx, "WHERE invitation_id = ?", existing.ResultID)
	return invitation, true, err
}

func bindExternalCommandReplay(ctx context.Context, tx *sql.Tx, commandID string, commandType string, invitationID int64, nowMs int64) error {
	if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{
		CommandID:   commandID,
		CommandType: commandType,
		ResultType:  hostdomain.ResultTypeInvitation,
		ResultID:    invitationID,
		CreatedAtMs: nowMs,
	}); err != nil {
		return mapHostDuplicateError(err)
	}
	return nil
}

func persistExternalInvitation(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation, commandType string, nowMs int64) error {
	if err := insertExternalRoleInvitation(ctx, tx, invitation); err != nil {
		return mapHostDuplicateError(err)
	}
	if err := bindExternalCommandReplay(ctx, tx, invitation.CommandID, commandType, invitation.InvitationID, nowMs); err != nil {
		return err
	}
	return insertRoleInvitationCreatedOutbox(ctx, tx, invitation, nowMs)
}

func requireExternalOperatorAndTargetRegion(ctx context.Context, tx *sql.Tx, operatorUserID int64, targetUserID int64, expectedRegionID int64) error {
	operatorRegionID, err := currentUserRegion(ctx, tx, operatorUserID, "FOR UPDATE")
	if err != nil {
		return err
	}
	if operatorRegionID <= 0 || operatorRegionID != expectedRegionID {
		return xerr.New(xerr.RegionMismatch, "external operator region has changed")
	}
	targetRegionID, err := currentUserRegion(ctx, tx, targetUserID, "FOR UPDATE")
	if err != nil {
		return err
	}
	if targetRegionID != expectedRegionID {
		return xerr.New(xerr.RegionMismatch, "target region does not match external operator region")
	}
	return nil
}

func requireExternalScopeOwnerRegion(ctx context.Context, tx *sql.Tx, ownerUserID int64, expectedRegionID int64) (int64, error) {
	regionID, err := currentUserRegion(ctx, tx, ownerUserID, "FOR UPDATE")
	if err != nil {
		return 0, err
	}
	if regionID != expectedRegionID {
		return 0, xerr.New(xerr.RegionMismatch, "external team owner region has changed")
	}
	return regionID, nil
}

func containsExternalScopeOwner(ownerUserIDs []int64, ownerUserID int64) bool {
	for _, candidate := range ownerUserIDs {
		if candidate == ownerUserID {
			return true
		}
	}
	return false
}

func validateExternalBDInvitationAcceptance(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation, targetRegionID int64) error {
	if invitation.InvitationType != hostdomain.InvitationTypeExternalBD ||
		invitation.ExternalOperatorUserID <= 0 ||
		invitation.InviterBDUserID != 0 ||
		invitation.ParentLeaderUserID != 0 {
		return xerr.New(xerr.InvalidArgument, "external bd invitation metadata is invalid")
	}
	operatorRegionID, err := currentUserRegion(ctx, tx, invitation.ExternalOperatorUserID, "FOR UPDATE")
	if err != nil {
		return err
	}
	if invitation.ParentOwnerUserID != invitation.ExternalOperatorUserID ||
		invitation.InviterUserID != invitation.ExternalOperatorUserID ||
		invitation.RegionID <= 0 ||
		operatorRegionID != invitation.RegionID ||
		targetRegionID != invitation.RegionID {
		return xerr.New(xerr.RegionMismatch, "external bd invitation scope has changed")
	}
	return nil
}

func validateExternalAgencyInvitationAcceptance(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation, targetRegionID int64) error {
	if invitation.InvitationType != hostdomain.InvitationTypeExternalAgency ||
		invitation.ExternalOperatorUserID <= 0 ||
		invitation.InviterUserID != invitation.ExternalOperatorUserID ||
		invitation.InviterBDUserID != invitation.ParentBDUserID {
		return xerr.New(xerr.InvalidArgument, "external agency invitation metadata is invalid")
	}
	operatorRegionID, err := currentUserRegion(ctx, tx, invitation.ExternalOperatorUserID, "FOR UPDATE")
	if err != nil {
		return err
	}
	if operatorRegionID != invitation.RegionID || targetRegionID != invitation.RegionID || invitation.ParentOwnerUserID <= 0 {
		return xerr.New(xerr.RegionMismatch, "external agency invitation region has changed")
	}
	bd, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", invitation.ParentBDUserID)
	if err != nil {
		return err
	}
	if bd.Status != hostdomain.BDStatusActive ||
		bd.RegionID != invitation.RegionID ||
		bd.ParentLeaderUserID != 0 ||
		bd.ParentOwnerUserID != invitation.ParentOwnerUserID {
		return xerr.New(xerr.Conflict, "external agency parent bd ownership has changed")
	}
	_, err = requireExternalScopeOwnerRegion(ctx, tx, bd.ParentOwnerUserID, invitation.RegionID)
	return err
}

func validateExternalHostInvitationAcceptance(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation, targetRegionID int64) (hostdomain.Agency, error) {
	if invitation.InvitationType != hostdomain.InvitationTypeExternalHost ||
		invitation.ExternalOperatorUserID <= 0 ||
		invitation.InviterUserID != invitation.ExternalOperatorUserID ||
		invitation.InviterBDUserID != invitation.ParentBDUserID {
		return hostdomain.Agency{}, xerr.New(xerr.InvalidArgument, "external host invitation metadata is invalid")
	}
	operatorRegionID, err := currentUserRegion(ctx, tx, invitation.ExternalOperatorUserID, "FOR UPDATE")
	if err != nil {
		return hostdomain.Agency{}, err
	}
	if operatorRegionID != invitation.RegionID || targetRegionID != invitation.RegionID || invitation.ParentAgencyID <= 0 || invitation.ParentOwnerUserID <= 0 {
		return hostdomain.Agency{}, xerr.New(xerr.RegionMismatch, "external host invitation region has changed")
	}
	agency, err := queryAgency(ctx, tx, "WHERE agency_id = ? FOR UPDATE", invitation.ParentAgencyID)
	if err != nil {
		return hostdomain.Agency{}, err
	}
	if agency.Status != hostdomain.AgencyStatusActive || !agency.JoinEnabled ||
		agency.RegionID != invitation.RegionID || agency.ParentBDUserID != invitation.ParentBDUserID {
		return hostdomain.Agency{}, xerr.New(xerr.Conflict, "external host parent agency has changed")
	}
	bd, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", invitation.ParentBDUserID)
	if err != nil {
		return hostdomain.Agency{}, err
	}
	if bd.Status != hostdomain.BDStatusActive || bd.ParentLeaderUserID != 0 ||
		bd.ParentOwnerUserID != invitation.ParentOwnerUserID || bd.RegionID != invitation.RegionID {
		return hostdomain.Agency{}, xerr.New(xerr.Conflict, "external host parent bd ownership has changed")
	}
	if _, err := requireExternalScopeOwnerRegion(ctx, tx, bd.ParentOwnerUserID, invitation.RegionID); err != nil {
		return hostdomain.Agency{}, err
	}
	return agency, nil
}

// ListExternalTeamUsers 通过 parent_owner 索引先收敛 BD，再向下连接 Agency/Host。
// region/status 条件在 users 权威表上执行；scope 绝不允许为空。
func (r *Repository) ListExternalTeamUsers(ctx context.Context, command hostservice.ListExternalTeamUsersCommand) (hostdomain.ExternalTeamUsers, error) {
	if len(command.ScopeOwnerUserIDs) == 0 || command.RegionID <= 0 {
		return hostdomain.ExternalTeamUsers{}, xerr.New(xerr.PermissionDenied, "external team scope is empty")
	}
	cte, cteArgs := externalTeamCTE(ctx, command.ScopeOwnerUserIDs, command.RegionID)
	filter, filterArgs := externalTeamFilter(command)
	outerArgs := []any{appcode.FromContext(ctx), command.RegionID, command.Status}
	outerArgs = append(outerArgs, filterArgs...)
	countArgs := append(append([]any{}, cteArgs...), outerArgs...)
	var total int64
	if err := r.db.QueryRowContext(ctx, cte+`
		SELECT COUNT(DISTINCT tr.user_id)
		FROM team_relations tr
		INNER JOIN users u ON u.app_code = ? AND u.user_id = tr.user_id
		WHERE u.region_id = ? AND u.status = ?`+filter,
		countArgs...,
	).Scan(&total); err != nil {
		return hostdomain.ExternalTeamUsers{}, err
	}

	offset := (command.Page - 1) * command.PageSize
	listArgs := append(append([]any{}, cteArgs...), outerArgs...)
	listArgs = append(listArgs, command.PageSize, offset)
	rows, err := r.db.QueryContext(ctx, cte+`
		SELECT
			u.user_id,
			COALESCE(u.default_display_user_id, ''),
			COALESCE(u.current_display_user_id, ''),
			COALESCE(u.username, ''),
			COALESCE(u.avatar, ''),
			u.status,
			COALESCE(u.region_id, 0),
			GROUP_CONCAT(DISTINCT tr.role ORDER BY FIELD(tr.role, 'bd', 'agency', 'host') SEPARATOR ','),
			MAX(tr.parent_owner_user_id),
			MAX(tr.parent_bd_user_id),
			MAX(tr.agency_id)
		FROM team_relations tr
		INNER JOIN users u ON u.app_code = ? AND u.user_id = tr.user_id
		WHERE u.region_id = ? AND u.status = ?`+filter+`
		GROUP BY u.user_id, u.default_display_user_id, u.current_display_user_id, u.username, u.avatar, u.status, u.region_id
		ORDER BY MAX(u.updated_at_ms) DESC, u.user_id DESC
		LIMIT ? OFFSET ?`,
		listArgs...,
	)
	if err != nil {
		return hostdomain.ExternalTeamUsers{}, err
	}
	defer rows.Close()
	users := make([]hostdomain.ExternalTeamUser, 0, command.PageSize)
	for rows.Next() {
		var user hostdomain.ExternalTeamUser
		var roles string
		if err := rows.Scan(
			&user.UserID,
			&user.DefaultDisplayUserID,
			&user.CurrentDisplayUserID,
			&user.Username,
			&user.Avatar,
			&user.Status,
			&user.RegionID,
			&roles,
			&user.ParentOwnerUserID,
			&user.ParentBDUserID,
			&user.AgencyID,
		); err != nil {
			return hostdomain.ExternalTeamUsers{}, err
		}
		if roles != "" {
			user.Roles = strings.Split(roles, ",")
		}
		users = append(users, user)
	}
	if err := rows.Err(); err != nil {
		return hostdomain.ExternalTeamUsers{}, err
	}
	return hostdomain.ExternalTeamUsers{Users: users, Total: total, Page: command.Page, PageSize: command.PageSize}, nil
}

func externalTeamCTE(ctx context.Context, ownerUserIDs []int64, regionID int64) (string, []any) {
	args := make([]any, 0, len(ownerUserIDs)+9)
	args = append(args, regionID, regionID, appcode.FromContext(ctx))
	for _, userID := range ownerUserIDs {
		args = append(args, userID)
	}
	args = append(args,
		hostdomain.BDStatusActive,
		appcode.FromContext(ctx),
		hostdomain.AgencyStatusActive,
		appcode.FromContext(ctx),
		hostdomain.MembershipStatusActive,
	)
	query := `
		WITH scoped_bd AS (
			SELECT b.user_id AS bd_user_id, b.parent_owner_user_id
			FROM bd_profiles b
			INNER JOIN users scope_owner
				ON scope_owner.app_code = b.app_code AND scope_owner.user_id = b.parent_owner_user_id
				AND scope_owner.status = 'active' AND scope_owner.region_id = ?
			INNER JOIN users bd_user
				ON bd_user.app_code = b.app_code AND bd_user.user_id = b.user_id
				AND bd_user.region_id = ?
			WHERE b.app_code = ?
			  AND b.parent_owner_user_id IN (` + placeholderList(len(ownerUserIDs)) + `)
			  AND b.parent_leader_user_id IS NULL
			  AND b.status = ?
		),
		scoped_agencies AS (
			SELECT a.agency_id, a.owner_user_id, a.parent_bd_user_id, sb.parent_owner_user_id
			FROM scoped_bd sb
			INNER JOIN agencies a
				ON a.app_code = ? AND a.parent_bd_user_id = sb.bd_user_id AND a.status = ?
		),
		team_relations AS (
			SELECT bd_user_id AS user_id, 'bd' AS role, parent_owner_user_id, bd_user_id AS parent_bd_user_id, 0 AS agency_id
			FROM scoped_bd
			UNION ALL
			SELECT owner_user_id, 'agency', parent_owner_user_id, parent_bd_user_id, agency_id
			FROM scoped_agencies
			UNION ALL
			SELECT m.host_user_id, 'host', sa.parent_owner_user_id, sa.parent_bd_user_id, sa.agency_id
			FROM scoped_agencies sa
			INNER JOIN agency_memberships m
				ON m.app_code = ? AND m.agency_id = sa.agency_id
				AND m.status = ? AND m.membership_type = 'member'
		)`
	return query, args
}

func externalTeamFilter(command hostservice.ListExternalTeamUsersCommand) (string, []any) {
	filter := ""
	args := make([]any, 0, 5)
	if command.Role != "" {
		filter += " AND tr.role = ?"
		args = append(args, command.Role)
	}
	if command.Keyword != "" {
		filter += " AND (u.user_id = ? OR u.current_display_user_id = ? OR u.default_display_user_id = ? OR u.username LIKE ?)"
		args = append(args, int64FromKeyword(command.Keyword), command.Keyword, command.Keyword, "%"+command.Keyword+"%")
	}
	return filter, args
}

// SearchExternalInvitationCandidate 只执行最多四个有唯一索引支撑的精确查询；
// 不对 username 做 LIKE，避免候选接口演变成区域用户目录。
func (r *Repository) SearchExternalInvitationCandidate(ctx context.Context, command hostservice.SearchExternalInvitationCandidateCommand) (hostdomain.ExternalTeamUser, error) {
	operatorRegionID, err := currentUserRegion(ctx, r.db, command.OperatorUserID, "")
	if err != nil {
		return hostdomain.ExternalTeamUser{}, err
	}
	if operatorRegionID <= 0 || operatorRegionID != command.ExpectedRegionID {
		return hostdomain.ExternalTeamUser{}, xerr.New(xerr.RegionMismatch, "external operator region has changed")
	}
	user, err := r.findExactExternalCandidate(ctx, command.ExpectedRegionID, command.Keyword, command.NowMs)
	if err != nil {
		return hostdomain.ExternalTeamUser{}, err
	}
	if err := r.requireCandidateRoleAvailable(ctx, command.InvitationType, user.UserID); err != nil {
		return hostdomain.ExternalTeamUser{}, err
	}
	return user, nil
}

func (r *Repository) findExactExternalCandidate(ctx context.Context, regionID int64, keyword string, nowMs int64) (hostdomain.ExternalTeamUser, error) {
	type lookup struct {
		clause string
		args   []any
	}
	lookups := make([]lookup, 0, 3)
	if userID, err := strconv.ParseInt(keyword, 10, 64); err == nil && userID > 0 {
		lookups = append(lookups, lookup{clause: "user_id = ?", args: []any{userID}})
	}
	lookups = append(lookups,
		lookup{
			// users.current_display_user_id 是读取快照；过期 pretty 快照不能继续命中候选人。
			clause: `current_display_user_id = ?
				AND (current_display_user_id_kind <> 'pretty'
					OR current_display_user_id_expires_at_ms IS NULL
					OR current_display_user_id_expires_at_ms = 0
					OR current_display_user_id_expires_at_ms > ?)`,
			args: []any{keyword, nowMs},
		},
		lookup{clause: "default_display_user_id = ?", args: []any{keyword}},
	)
	for _, candidate := range lookups {
		var user hostdomain.ExternalTeamUser
		args := []any{appcode.FromContext(ctx), regionID}
		args = append(args, candidate.args...)
		err := r.db.QueryRowContext(ctx, `
			SELECT user_id, COALESCE(default_display_user_id, ''), COALESCE(current_display_user_id, ''),
			       COALESCE(username, ''), COALESCE(avatar, ''), status, COALESCE(region_id, 0)
			FROM users
			WHERE app_code = ? AND region_id = ? AND status = 'active' AND `+candidate.clause+`
			LIMIT 1`,
			args...,
		).Scan(
			&user.UserID,
			&user.DefaultDisplayUserID,
			&user.CurrentDisplayUserID,
			&user.Username,
			&user.Avatar,
			&user.Status,
			&user.RegionID,
		)
		if err == nil {
			return user, nil
		}
		if err != sql.ErrNoRows {
			return hostdomain.ExternalTeamUser{}, err
		}
	}

	// active lease 是靓号归属事实，不能只依赖 users.current_display_user_id 快照；
	// active_display_user_id 生成列使该精确查询命中租约唯一索引，不扫描区域用户。
	var user hostdomain.ExternalTeamUser
	err := r.db.QueryRowContext(ctx, `
		SELECT u.user_id, COALESCE(u.default_display_user_id, ''), COALESCE(u.current_display_user_id, ''),
		       COALESCE(u.username, ''), COALESCE(u.avatar, ''), u.status, COALESCE(u.region_id, 0)
		FROM pretty_display_user_id_leases lease FORCE INDEX (uk_pretty_display_user_id_active)
		INNER JOIN users u
			ON u.user_id = lease.user_id AND u.app_code = lease.app_code
		WHERE lease.app_code = ? AND lease.active_display_user_id = ?
		  AND (lease.expires_at_ms = 0 OR lease.expires_at_ms > ?)
		  AND u.region_id = ? AND u.status = 'active'
		LIMIT 1`,
		appcode.FromContext(ctx), keyword, nowMs, regionID,
	).Scan(
		&user.UserID,
		&user.DefaultDisplayUserID,
		&user.CurrentDisplayUserID,
		&user.Username,
		&user.Avatar,
		&user.Status,
		&user.RegionID,
	)
	if err == nil {
		return user, nil
	}
	if err != sql.ErrNoRows {
		return hostdomain.ExternalTeamUser{}, err
	}
	return hostdomain.ExternalTeamUser{}, xerr.New(xerr.NotFound, "external invitation candidate not found")
}

func (r *Repository) requireCandidateRoleAvailable(ctx context.Context, invitationType string, userID int64) error {
	var query string
	switch invitationType {
	case hostdomain.InvitationTypeBD:
		query = `SELECT (
			EXISTS(SELECT 1 FROM bd_profiles WHERE app_code = ? AND user_id = ? AND status = 'active')
			OR EXISTS(SELECT 1 FROM role_invitations
				WHERE app_code = ? AND target_user_id = ? AND status = 'pending'
				  AND invitation_type IN ('bd', 'external_bd'))
		)`
	case hostdomain.InvitationTypeAgency:
		query = `SELECT (
			EXISTS(SELECT 1 FROM agencies WHERE app_code = ? AND owner_user_id = ? AND status = 'active')
			OR EXISTS(SELECT 1 FROM role_invitations
				WHERE app_code = ? AND target_user_id = ? AND status = 'pending'
				  AND invitation_type IN ('agency', 'external_agency'))
		)`
	case hostdomain.InvitationTypeHost:
		query = `SELECT (
			EXISTS(SELECT 1 FROM agencies WHERE app_code = ? AND owner_user_id = ? AND status = 'active')
			OR EXISTS(SELECT 1 FROM agency_memberships WHERE app_code = ? AND host_user_id = ? AND status = 'active')
			OR EXISTS(SELECT 1 FROM role_invitations
				WHERE app_code = ? AND target_user_id = ? AND status = 'pending'
				  AND invitation_type IN ('host', 'external_host'))
		)`
	default:
		return xerr.New(xerr.InvalidArgument, "invitation_type is invalid")
	}
	var exists bool
	args := []any{appcode.FromContext(ctx), userID, appcode.FromContext(ctx), userID}
	if invitationType == hostdomain.InvitationTypeHost {
		args = append(args, appcode.FromContext(ctx), userID)
	}
	if err := r.db.QueryRowContext(ctx, query, args...).Scan(&exists); err != nil {
		return err
	}
	if exists {
		return xerr.New(xerr.Conflict, fmt.Sprintf("target already has active %s identity", invitationType))
	}
	return nil
}
