package host

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"

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

// commandResult 读取写命令流水。
// 命中表示前一次业务事务已经提交，调用方必须按持久化结果重建返回值。
func commandResult(ctx context.Context, q sqlQueryer, commandID string) (hostdomain.CommandResult, bool, error) {
	var result hostdomain.CommandResult
	err := q.QueryRowContext(ctx, `
		SELECT command_id, command_type, result_type, result_id, created_at_ms
		FROM host_command_results
		WHERE app_code = ? AND command_id = ?
	`, appcode.FromContext(ctx), commandID).Scan(&result.CommandID, &result.CommandType, &result.ResultType, &result.ResultID, &result.CreatedAtMs)
	if err == sql.ErrNoRows {
		return hostdomain.CommandResult{}, false, nil
	}
	if err != nil {
		return hostdomain.CommandResult{}, false, err
	}
	return result, true, nil
}

// insertCommandResult 必须放在每个状态机成功之后写入。
// 它必须和领域事实共用同一事务，避免重试看到半提交命令。
func insertCommandResult(ctx context.Context, tx *sql.Tx, result hostdomain.CommandResult) error {
	_, err := tx.ExecContext(ctx, `
		INSERT INTO host_command_results (app_code, command_id, command_type, result_type, result_id, created_at_ms)
		VALUES (?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), result.CommandID, result.CommandType, result.ResultType, result.ResultID, result.CreatedAtMs)
	return err
}

// insertHostOutbox 在产生事实的同一事务里记录可靠集成事件。
// 消费者可以延迟或重建；真正的事实来源仍是和本行一起提交的领域表。
func insertHostOutbox(ctx context.Context, tx *sql.Tx, eventID int64, eventType string, aggregateType string, aggregateID int64, nowMs int64) error {
	payload, err := json.Marshal(map[string]any{
		"event_type":     eventType,
		"app_code":       appcode.FromContext(ctx),
		"aggregate_type": aggregateType,
		"aggregate_id":   aggregateID,
	})
	if err != nil {
		return err
	}
	_, err = tx.ExecContext(ctx, `
		INSERT INTO host_outbox (app_code, event_id, event_type, aggregate_type, aggregate_id, status, payload_json, created_at_ms)
		VALUES (?, ?, ?, ?, ?, 'pending', CAST(? AS JSON), ?)
	`, appcode.FromContext(ctx), eventID, eventType, aggregateType, fmt.Sprintf("%d", aggregateID), string(payload), nowMs)
	return err
}

func ensureHostProfileForMembership(ctx context.Context, tx *sql.Tx, userID int64, regionID int64, membershipID int64, agencyID int64, source string, nowMs int64) (hostdomain.HostProfile, bool, error) {
	// host_profile 对每个用户只有一行，并且会跨 Agency 成员关系变化长期保留。
	// FOR UPDATE 保证“创建身份或挂载当前成员关系”对同一用户是原子的。
	profile, err := scanHostProfile(tx.QueryRowContext(ctx, fmt.Sprintf(`SELECT %s FROM host_profiles WHERE app_code = ? AND user_id = ? FOR UPDATE`, hostProfileColumns), appcode.FromContext(ctx), userID))
	if err == sql.ErrNoRows {
		// 第一次通过申请时创建长期 Host 身份，并记录 first_became_host_at_ms。
		profile = hostdomain.HostProfile{
			UserID:              userID,
			Status:              hostdomain.HostStatusActive,
			RegionID:            regionID,
			CurrentAgencyID:     agencyID,
			CurrentMembershipID: membershipID,
			Source:              source,
			FirstBecameHostAtMs: nowMs,
			CreatedAtMs:         nowMs,
			UpdatedAtMs:         nowMs,
		}
		return profile, true, insertHostProfile(ctx, tx, profile)
	}
	if err != nil {
		return hostdomain.HostProfile{}, false, err
	}
	if profile.Status != hostdomain.HostStatusActive {
		// disabled 不再算有效 Host 权限；当用户通过邀请或后台开通重新获得 Host 身份时，
		// 复用原主键行并挂到本次新 membership，避免因为历史停用记录导致身份补齐失败。
		profile.Status = hostdomain.HostStatusActive
		profile.RegionID = regionID
		profile.CurrentAgencyID = agencyID
		profile.CurrentMembershipID = membershipID
		profile.UpdatedAtMs = nowMs
		return profile, true, updateHostProfileForMembership(ctx, tx, profile)
	}
	if profile.CurrentMembershipID > 0 {
		// current_membership 是为“我的 Agency”快速读取做的冗余；非 0 表示已经挂载。
		return hostdomain.HostProfile{}, false, xerr.New(xerr.Conflict, "host already has active agency membership")
	}
	// 复用已有身份行时不能重置最早成为主播的时间和来源。
	profile.RegionID = regionID
	profile.CurrentAgencyID = agencyID
	profile.CurrentMembershipID = membershipID
	profile.UpdatedAtMs = nowMs
	return profile, false, updateHostCurrentMembership(ctx, tx, profile)
}

func ensureHostProfileForExistingMembership(ctx context.Context, tx *sql.Tx, userID int64, regionID int64, membership hostdomain.AgencyMembership, source string, nowMs int64) (hostdomain.HostProfile, bool, error) {
	profile, ok, err := queryHostProfileByUserMaybe(ctx, tx, userID, true)
	if err != nil {
		return hostdomain.HostProfile{}, false, err
	}
	if !ok {
		// 极端历史数据可能只有 active membership 没有 host_profile；这里补回 Host 身份，
		// 但指针仍指向已有 membership，绝不把用户重新绑定到新创建的 Agency。
		profile = hostdomain.HostProfile{
			UserID:              userID,
			Status:              hostdomain.HostStatusActive,
			RegionID:            regionID,
			CurrentAgencyID:     membership.AgencyID,
			CurrentMembershipID: membership.MembershipID,
			Source:              source,
			FirstBecameHostAtMs: nowMs,
			CreatedAtMs:         nowMs,
			UpdatedAtMs:         nowMs,
		}
		return profile, true, insertHostProfile(ctx, tx, profile)
	}
	changed := profile.Status != hostdomain.HostStatusActive || profile.CurrentAgencyID != membership.AgencyID || profile.CurrentMembershipID != membership.MembershipID
	profile.Status = hostdomain.HostStatusActive
	profile.RegionID = regionID
	profile.CurrentAgencyID = membership.AgencyID
	profile.CurrentMembershipID = membership.MembershipID
	profile.UpdatedAtMs = nowMs
	if !changed {
		return profile, false, nil
	}
	return profile, true, updateHostProfileForMembership(ctx, tx, profile)
}

func insertHostProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.HostProfile) error {
	// 这里只用于首次创建身份；成员关系变化走 updateHostCurrentMembership。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO host_profiles (
			app_code, user_id, status, current_agency_id, current_membership_id, source,
			first_became_host_at_ms, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), profile.UserID, profile.Status, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.Source, profile.FirstBecameHostAtMs, profile.CreatedAtMs, profile.UpdatedAtMs)
	return err
}

func updateHostCurrentMembership(ctx context.Context, tx *sql.Tx, profile hostdomain.HostProfile) error {
	// 这里只更新可变的“当前成员关系指针”字段。
	// 状态、来源、首次成为主播时间属于身份历史，不在这里重写。
	_, err := tx.ExecContext(ctx, `
		UPDATE host_profiles
		SET current_agency_id = ?, current_membership_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func updateHostProfileForMembership(ctx context.Context, tx *sql.Tx, profile hostdomain.HostProfile) error {
	// 恢复 disabled Host 或修复历史指针时，需要同时写 status 和当前归属；
	// source/first_became_host_at_ms 仍然保留首次成为 Host 的历史语义。
	_, err := tx.ExecContext(ctx, `
		UPDATE host_profiles
		SET status = ?, current_agency_id = ?, current_membership_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, profile.Status, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func updateUserRegion(ctx context.Context, tx *sql.Tx, userID int64, regionID int64, nowMs int64) error {
	_, err := tx.ExecContext(ctx, `
		UPDATE users
		SET region_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, nullableRegionID(regionID), nowMs, appcode.FromContext(ctx), userID)
	return err
}

func insertAgency(ctx context.Context, tx *sql.Tx, agency hostdomain.Agency) error {
	// 第一阶段只有接受 Agency 邀请时会创建 Agency。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO agencies (
			app_code, agency_id, owner_user_id, parent_bd_user_id, name, avatar, status,
			join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Avatar, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs)
	return err
}

func updateAgencyAdminState(ctx context.Context, tx *sql.Tx, agency hostdomain.Agency) error {
	// 后台 Phase 2 只允许调整 Agency 状态和入会开关；owner/parent_bd/region 不在这里迁移。
	_, err := tx.ExecContext(ctx, `
		UPDATE agencies
		SET status = ?, join_enabled = ?, updated_at_ms = ?
		WHERE app_code = ? AND agency_id = ?
	`, agency.Status, agency.JoinEnabled, agency.UpdatedAtMs, appcode.FromContext(ctx), agency.AgencyID)
	return err
}

func updateAgencyProfile(ctx context.Context, tx *sql.Tx, agency hostdomain.Agency) error {
	// Agency 资料是独立组织资料；这里绝不更新 users 表，也不从 owner 用户资料重新派生。
	_, err := tx.ExecContext(ctx, `
		UPDATE agencies
		SET name = ?, avatar = ?, updated_at_ms = ?
		WHERE app_code = ? AND agency_id = ?
	`, agency.Name, agency.Avatar, agency.UpdatedAtMs, appcode.FromContext(ctx), agency.AgencyID)
	return err
}

func insertBDProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// 普通 BD 身份行和 host_profile 相互独立；BD 用户不会自动成为 Host，也不会隐式拥有 BD Leader 权限。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO bd_profiles (
			app_code, user_id, role, parent_leader_user_id, status,
			created_by_user_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), profile.UserID, profile.Role, nullableRegionID(profile.ParentLeaderUserID), profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs)
	return err
}

func insertExternalBDProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// 外管 BD 与普通 App BD 共用身份事实，但仅专用入口写 parent_owner_user_id；
	// parent_leader_user_id 固定为空，确保 Manager/BD Leader 的既有树查询不会纳入外管关系。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO bd_profiles (
			app_code, user_id, role, parent_leader_user_id, parent_owner_user_id, status,
			created_by_user_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), profile.UserID, profile.Role, profile.ParentOwnerUserID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs)
	return err
}

func insertBDLeaderProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// BD Leader 使用独立表承载负责人身份，避免和普通 BD 工资、状态、团队归属混成同一条事实。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO bd_leader_profiles (
			app_code, user_id, status,
			created_by_user_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), profile.UserID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs)
	return err
}

func updateBDProfileStatus(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// 状态变更只更新 status/updated_at_ms，不改角色、区域和上级 Leader 这些身份历史字段。
	_, err := tx.ExecContext(ctx, `
		UPDATE bd_profiles
		SET status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND role = 'bd'
	`, profile.Status, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func updateBDProfileForActivation(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// disabled 普通 BD 被重新授予权限时才允许重写父级 Leader；
	// active BD 的 parent 代表既有业务关系，任何补齐逻辑都不能覆盖它。
	_, err := tx.ExecContext(ctx, `
		UPDATE bd_profiles
		SET parent_leader_user_id = ?, status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND role = 'bd'
	`, nullableRegionID(profile.ParentLeaderUserID), profile.Status, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func updateExternalBDProfileForActivation(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// 只有 external_bd 接受分支能把 disabled BD 重新绑定为外管关系。
	// 同一语句清空旧 parent_leader，避免一条 active BD 同时出现在 App Leader 树和外管 owner 树。
	_, err := tx.ExecContext(ctx, `
		UPDATE bd_profiles
		SET parent_leader_user_id = NULL, parent_owner_user_id = ?, status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND role = 'bd'
	`, profile.ParentOwnerUserID, profile.Status, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func updateBDLeaderProfileStatus(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error {
	// Leader 状态只影响负责人权限和 ADMIN_SALARY_USD 钱包入口，不影响同用户可能存在的普通 BD 身份。
	_, err := tx.ExecContext(ctx, `
		UPDATE bd_leader_profiles
		SET status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, profile.Status, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func ensureActiveBDLeaderProfile(ctx context.Context, tx *sql.Tx, userID int64, regionID int64, createdByUserID int64, nowMs int64) (hostdomain.BDProfile, string, error) {
	profile, ok, err := queryBDLeaderProfileByUserMaybe(ctx, tx, userID, true)
	if err != nil {
		return hostdomain.BDProfile{}, "", err
	}
	if !ok {
		// 直开 BD Leader 时，负责人身份先成为 active 事实；普通 BD 钱包/工资身份由后续 helper 补齐。
		profile = hostdomain.BDProfile{
			UserID:          userID,
			Role:            hostdomain.BDRoleLeader,
			RegionID:        regionID,
			Status:          hostdomain.BDStatusActive,
			CreatedByUserID: createdByUserID,
			CreatedAtMs:     nowMs,
			UpdatedAtMs:     nowMs,
		}
		if err := insertBDLeaderProfile(ctx, tx, profile); err != nil {
			return hostdomain.BDProfile{}, "", mapHostDuplicateError(err)
		}
		return profile, "BDCreated", nil
	}
	if profile.Status == hostdomain.BDStatusActive {
		// 已经是 active Leader 时只回放当前事实，不重写 created_by，也不重复发状态事件。
		return profile, "", nil
	}
	profile.Status = hostdomain.BDStatusActive
	profile.UpdatedAtMs = nowMs
	if err := updateBDLeaderProfileStatus(ctx, tx, profile); err != nil {
		return hostdomain.BDProfile{}, "", err
	}
	return profile, "BDStatusChanged", nil
}

func ensureSelfBDProfileForLeader(ctx context.Context, tx *sql.Tx, userID int64, regionID int64, createdByUserID int64, nowMs int64) (hostdomain.BDProfile, string, error) {
	profile, ok, err := queryBDProfileByUserMaybe(ctx, tx, userID, true)
	if err != nil {
		return hostdomain.BDProfile{}, "", err
	}
	if !ok {
		// BD Leader 需要普通 BD 身份承载下级 Agency/工资等 BD 体系能力；
		// 首次补齐时把 parent_leader_user_id 指向自己，形成自有 BD Leader 体系。
		profile = hostdomain.BDProfile{
			UserID:             userID,
			Role:               hostdomain.BDRoleBD,
			RegionID:           regionID,
			ParentLeaderUserID: userID,
			Status:             hostdomain.BDStatusActive,
			CreatedByUserID:    createdByUserID,
			CreatedAtMs:        nowMs,
			UpdatedAtMs:        nowMs,
		}
		if err := insertBDProfile(ctx, tx, profile); err != nil {
			return hostdomain.BDProfile{}, "", mapHostDuplicateError(err)
		}
		return profile, "BDCreated", nil
	}
	if profile.Status == hostdomain.BDStatusActive {
		// 已有 active 普通 BD 说明用户已经在某个 BD 关系里；开通 Leader 不能覆盖原 parent。
		return profile, "", nil
	}
	profile.ParentLeaderUserID = userID
	profile.Status = hostdomain.BDStatusActive
	profile.UpdatedAtMs = nowMs
	if err := updateBDProfileForActivation(ctx, tx, profile); err != nil {
		return hostdomain.BDProfile{}, "", err
	}
	return profile, "BDStatusChanged", nil
}

func insertCoinSellerProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.CoinSellerProfile) error {
	// 币商身份是独立角色事实；账户余额属于 wallet-service，不在此表冗余。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO coin_seller_profiles (
			app_code, user_id, status, merchant_asset_type, can_manage_sub_coin_sellers,
			created_by_user_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), profile.UserID, profile.Status, profile.MerchantAssetType, profile.CanManageSubCoinSellers, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs)
	return err
}

func updateCoinSellerProfileStatus(ctx context.Context, tx *sql.Tx, profile hostdomain.CoinSellerProfile) error {
	// 启停币商身份只改状态；merchant_asset_type 是账户边界，创建后不能静默迁移。
	_, err := tx.ExecContext(ctx, `
		UPDATE coin_seller_profiles
		SET status = ?, can_manage_sub_coin_sellers = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, profile.Status, profile.CanManageSubCoinSellers, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID)
	return err
}

func insertCoinSellerRelation(ctx context.Context, tx *sql.Tx, relation hostdomain.CoinSellerSubRelation) error {
	// 直属关系是 H5 子币商转账的授权边界；唯一键保证一个子币商同一时间只有一个有效父币商。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO coin_seller_relations (
			app_code, relation_id, parent_user_id, child_user_id, status,
			created_by_user_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), relation.RelationID, relation.ParentUserID, relation.ChildUserID, relation.Status, relation.CreatedByUserID, relation.CreatedAtMs, relation.UpdatedAtMs)
	return err
}

func insertAgencyMembership(ctx context.Context, tx *sql.Tx, membership hostdomain.AgencyMembership) error {
	// 成员关系是 Agency 关系历史行；有效且当前的关系也会冗余到身份表。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO agency_memberships (
			app_code, membership_id, agency_id, host_user_id, membership_type, status,
			joined_at_ms, ended_at_ms, ended_by_user_id, ended_reason, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), membership.MembershipID, membership.AgencyID, membership.HostUserID, membership.MembershipType, membership.Status, membership.JoinedAtMs, nullableRegionID(membership.EndedAtMs), nullableRegionID(membership.EndedByUserID), membership.EndedReason, membership.CreatedAtMs, membership.UpdatedAtMs)
	return err
}

func endAgencyMembership(ctx context.Context, tx *sql.Tx, membership hostdomain.AgencyMembership) error {
	// 结束成员关系只改状态，不删除历史行，便于审计和列表回看。
	_, err := tx.ExecContext(ctx, `
		UPDATE agency_memberships
		SET status = ?, ended_at_ms = ?, ended_by_user_id = ?, ended_reason = ?, updated_at_ms = ?
		WHERE app_code = ? AND membership_id = ?
	`, membership.Status, nullableRegionID(membership.EndedAtMs), nullableRegionID(membership.EndedByUserID), membership.EndedReason, membership.UpdatedAtMs, appcode.FromContext(ctx), membership.MembershipID)
	return err
}

func insertAgencyApplication(ctx context.Context, tx *sql.Tx, application hostdomain.AgencyApplication) error {
	// 申请记录只保留申请人和 Agency；区域每次读取时都从申请人 users 行投影。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO agency_applications (
			app_code, application_id, command_id, applicant_user_id, agency_id, status,
			reviewed_by_user_id, review_reason, reviewed_at_ms, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), application.ApplicationID, application.CommandID, application.ApplicantUserID, application.AgencyID, application.Status, nullableRegionID(application.ReviewedByUserID), application.ReviewReason, nullableRegionID(application.ReviewedAtMs), application.CreatedAtMs, application.UpdatedAtMs)
	return err
}

func updateAgencyApplicationReview(ctx context.Context, tx *sql.Tx, application hostdomain.AgencyApplication) error {
	// 审核更新是申请终态的唯一写入口。
	_, err := tx.ExecContext(ctx, `
		UPDATE agency_applications
		SET status = ?, reviewed_by_user_id = ?, review_reason = ?, reviewed_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND application_id = ?
	`, application.Status, nullableRegionID(application.ReviewedByUserID), application.ReviewReason, nullableRegionID(application.ReviewedAtMs), application.UpdatedAtMs, appcode.FromContext(ctx), application.ApplicationID)
	return err
}

func insertRoleInvitation(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation) error {
	// 邀请记录保留足够的上级 BD 信息，接受时不需要重新推导组织链路。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO role_invitations (
			app_code, invitation_id, command_id, invitation_type, status, inviter_user_id,
			inviter_bd_user_id, target_user_id, agency_name, parent_bd_user_id,
			parent_leader_user_id, processed_by_user_id, process_reason, processed_at_ms,
			created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), invitation.InvitationID, invitation.CommandID, invitation.InvitationType, invitation.Status, invitation.InviterUserID, invitation.InviterBDUserID, invitation.TargetUserID, invitation.AgencyName, invitation.ParentBDUserID, nullableRegionID(invitation.ParentLeaderUserID), nullableRegionID(invitation.ProcessedByUserID), invitation.ProcessReason, nullableRegionID(invitation.ProcessedAtMs), invitation.CreatedAtMs, invitation.UpdatedAtMs)
	return err
}

func insertExternalRoleInvitation(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation) error {
	// 外管邀请使用独立 invitation_type 并把 operator/owner/Agency 快照一次性写入。
	// 普通 InviteBD/Agency/Host 继续调用原 insertRoleInvitation，完全不接触这些扩展列。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO role_invitations (
			app_code, invitation_id, command_id, invitation_type, status, inviter_user_id,
			inviter_bd_user_id, target_user_id, external_operator_user_id, agency_name, parent_bd_user_id,
			parent_leader_user_id, parent_owner_user_id, parent_agency_id,
			processed_by_user_id, process_reason, processed_at_ms,
			created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.FromContext(ctx), invitation.InvitationID, invitation.CommandID, invitation.InvitationType, invitation.Status, invitation.InviterUserID, invitation.InviterBDUserID, invitation.TargetUserID, invitation.ExternalOperatorUserID, invitation.AgencyName, invitation.ParentBDUserID, nullableRegionID(invitation.ParentLeaderUserID), invitation.ParentOwnerUserID, nullableRegionID(invitation.ParentAgencyID), nullableRegionID(invitation.ProcessedByUserID), invitation.ProcessReason, nullableRegionID(invitation.ProcessedAtMs), invitation.CreatedAtMs, invitation.UpdatedAtMs)
	return err
}

func updateRoleInvitationProcessed(ctx context.Context, tx *sql.Tx, invitation hostdomain.RoleInvitation) error {
	// 处理邀请时统一记录处理人、原因和时间，覆盖接受、拒绝和取消。
	_, err := tx.ExecContext(ctx, `
		UPDATE role_invitations
		SET status = ?, processed_by_user_id = ?, process_reason = ?, processed_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND invitation_id = ?
	`, invitation.Status, nullableRegionID(invitation.ProcessedByUserID), invitation.ProcessReason, nullableRegionID(invitation.ProcessedAtMs), invitation.UpdatedAtMs, appcode.FromContext(ctx), invitation.InvitationID)
	return err
}
