package host

import (
	"context"
	"database/sql"

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

// InviteAgency 创建 Agency 角色邀请，校验 inviter BD 身份和目标用户区域。
func (r *Repository) InviteAgency(ctx context.Context, command hostservice.InviteAgencyCommand) (hostdomain.RoleInvitation, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	defer tx.Rollback()

	// 邀请是可重试写命令；命中幂等结果时返回既有邀请，
	// 不重新校验当前 BD/用户状态，保持“已提交结果可回放”的语义。
	if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		if err := requireCommandResult(existing, hostdomain.CommandTypeInviteAgency, hostdomain.ResultTypeInvitation); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		return queryRoleInvitation(ctx, tx, "WHERE invitation_id = ?", existing.ResultID)
	}

	// 邀请人的 BD 身份行同时是授权凭证和邀请归属来源；
	// 锁住它可以避免 BD 被停用/换区时仍继续发出旧区域邀请。
	inviter, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.InviterUserID)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if inviter.Status != hostdomain.BDStatusActive {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "inviter bd is not active")
	}
	// 目标用户还不是 Host/Agency 时没有领域表行，所以锁 users 行来串行化目标身份变更。
	targetRegionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE")
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if targetRegionID != inviter.RegionID {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "target region does not match inviter region")
	}
	// Agency 拥有者在本阶段也会被建成 host_profile 和拥有者成员关系，
	// 因此已是有效 Host 的用户不能再走“成为 Agency”邀请。
	if _, ok, err := queryHostProfileMaybe(ctx, tx, command.TargetUserID, true); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "active host cannot become agency")
	}
	// 一个用户只能拥有一个有效 Agency；这里先拦截，最终仍依赖唯一索引兜底并发。
	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")
	}

	// BD 发出的 Agency 邀请要记录直接上级 BD 和负责人链路；
	// 如果邀请人自己就是负责人，则 parent_leader_user_id 指向自己。
	parentLeaderUserID := inviter.ParentLeaderUserID
	if inviter.Role == hostdomain.BDRoleLeader {
		parentLeaderUserID = inviter.UserID
	}
	invitation := hostdomain.RoleInvitation{
		InvitationID:       command.InvitationID,
		CommandID:          command.CommandID,
		InvitationType:     hostdomain.InvitationTypeAgency,
		Status:             hostdomain.InvitationStatusPending,
		InviterUserID:      command.InviterUserID,
		InviterBDUserID:    inviter.UserID,
		TargetUserID:       command.TargetUserID,
		RegionID:           inviter.RegionID,
		AgencyName:         command.AgencyName,
		ParentBDUserID:     inviter.UserID,
		ParentLeaderUserID: parentLeaderUserID,
		CreatedAtMs:        command.NowMs,
		UpdatedAtMs:        command.NowMs,
	}
	// 待处理邀请与 command_result 同事务写入，保证重复提交能按 invitation_id 回放。
	if err := insertRoleInvitation(ctx, tx, invitation); err != nil {
		return hostdomain.RoleInvitation{}, mapHostDuplicateError(err)
	}
	if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{
		CommandID:   command.CommandID,
		CommandType: hostdomain.CommandTypeInviteAgency,
		ResultType:  hostdomain.ResultTypeInvitation,
		ResultID:    invitation.InvitationID,
		CreatedAtMs: command.NowMs,
	}); err != nil {
		return hostdomain.RoleInvitation{}, mapHostDuplicateError(err)
	}
	if err := tx.Commit(); err != nil {
		return hostdomain.RoleInvitation{}, err
	}

	return invitation, nil
}

// InviteBD 创建 BD Leader 到 BD 的邀请。
func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDCommand) (hostdomain.RoleInvitation, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	defer tx.Rollback()

	// BD 邀请也以 command_id 去重；已成功的邀请不会因为负责人后续状态变化而回放失败。
	if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
		return hostdomain.RoleInvitation{}, err
	} else if ok {
		if err := requireCommandResult(existing, hostdomain.CommandTypeInviteBD, hostdomain.ResultTypeInvitation); err != nil {
			return hostdomain.RoleInvitation{}, err
		}
		return queryRoleInvitation(ctx, tx, "WHERE invitation_id = ?", existing.ResultID)
	}

	// 只有有效 BD 负责人能发展 BD；锁住负责人身份行让停用/降级和邀请创建串行。
	inviter, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.InviterUserID)
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if inviter.Status != hostdomain.BDStatusActive || inviter.Role != hostdomain.BDRoleLeader {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "inviter is not active bd leader")
	}
	// BD 从属于负责人的区域，邀请创建时先锁目标用户当前区域。
	targetRegionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE")
	if err != nil {
		return hostdomain.RoleInvitation{}, err
	}
	if targetRegionID != inviter.RegionID {
		return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "target region does not match leader region")
	}
	// 目标已有有效 BD 身份行时不再创建新邀请，避免多条 BD 身份事实竞争。
	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")
	}

	invitation := hostdomain.RoleInvitation{
		InvitationID:       command.InvitationID,
		CommandID:          command.CommandID,
		InvitationType:     hostdomain.InvitationTypeBD,
		Status:             hostdomain.InvitationStatusPending,
		InviterUserID:      command.InviterUserID,
		InviterBDUserID:    inviter.UserID,
		TargetUserID:       command.TargetUserID,
		RegionID:           inviter.RegionID,
		ParentLeaderUserID: inviter.UserID,
		CreatedAtMs:        command.NowMs,
		UpdatedAtMs:        command.NowMs,
	}
	// 这里只创建待处理邀请；真正的 BD 身份行在目标用户接受时再落库。
	if err := insertRoleInvitation(ctx, tx, invitation); err != nil {
		return hostdomain.RoleInvitation{}, mapHostDuplicateError(err)
	}
	if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{
		CommandID:   command.CommandID,
		CommandType: hostdomain.CommandTypeInviteBD,
		ResultType:  hostdomain.ResultTypeInvitation,
		ResultID:    invitation.InvitationID,
		CreatedAtMs: command.NowMs,
	}); err != nil {
		return hostdomain.RoleInvitation{}, mapHostDuplicateError(err)
	}
	if err := tx.Commit(); err != nil {
		return hostdomain.RoleInvitation{}, err
	}

	return invitation, nil
}

// ProcessRoleInvitation 处理角色邀请，接受路径在同一事务内创建 Agency/BD 角色事实。
func (r *Repository) ProcessRoleInvitation(ctx context.Context, command hostservice.ProcessRoleInvitationCommand) (hostdomain.ProcessRoleInvitationResult, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return hostdomain.ProcessRoleInvitationResult{}, err
	}
	defer tx.Rollback()

	// 接受、拒绝、取消共用同一个处理命令类型；重试时通过 invitation_id 重建结果，
	// 不重复创建 Agency、成员关系或 BD 身份行。
	if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
		return hostdomain.ProcessRoleInvitationResult{}, err
	} else if ok {
		if err := requireCommandResult(existing, hostdomain.CommandTypeProcessRoleInvitation, hostdomain.ResultTypeInvitation); err != nil {
			return hostdomain.ProcessRoleInvitationResult{}, err
		}
		return processResultByInvitationID(ctx, tx, existing.ResultID)
	}

	// 邀请行是邀请状态机的唯一锁；待处理状态只能被推进一次。
	invitation, err := queryRoleInvitation(ctx, tx, "WHERE invitation_id = ? FOR UPDATE", command.InvitationID)
	if err != nil {
		return hostdomain.ProcessRoleInvitationResult{}, err
	}
	if invitation.Status != hostdomain.InvitationStatusPending {
		return hostdomain.ProcessRoleInvitationResult{}, xerr.New(xerr.Conflict, "invitation is not pending")
	}
	// 取消属于发起人撤回；接受/拒绝属于目标用户处理。
	// 这两个授权分支不要下沉到传输层，否则批处理或内部调用会绕过规则。
	if command.Action == hostdomain.InvitationActionCancel && command.ActorUserID != invitation.InviterUserID {
		return hostdomain.ProcessRoleInvitationResult{}, xerr.New(xerr.PermissionDenied, "only inviter can cancel invitation")
	}
	if command.Action != hostdomain.InvitationActionCancel && command.ActorUserID != invitation.TargetUserID {
		return hostdomain.ProcessRoleInvitationResult{}, xerr.New(xerr.PermissionDenied, "only target can process invitation")
	}

	invitation.ProcessedByUserID = command.ActorUserID
	invitation.ProcessReason = command.Reason
	invitation.ProcessedAtMs = command.NowMs
	invitation.UpdatedAtMs = command.NowMs
	result := hostdomain.ProcessRoleInvitationResult{Invitation: invitation}

	switch command.Action {
	case hostdomain.InvitationActionReject:
		// 拒绝只改变邀请终态，不创建任何角色事实。
		invitation.Status = hostdomain.InvitationStatusRejected
	case hostdomain.InvitationActionCancel:
		// 取消同样只关闭邀请，用于邀请人在目标处理前撤回。
		invitation.Status = hostdomain.InvitationStatusCancelled
	case hostdomain.InvitationActionAccept:
		invitation.Status = hostdomain.InvitationStatusAccepted
		if invitation.InvitationType == hostdomain.InvitationTypeAgency {
			// Agency 邀请的接受会一次性创建 Agency 拥有者身份、Agency 和拥有者成员关系。
			created, err := r.acceptAgencyInvitation(ctx, tx, command, &invitation, &result)
			if err != nil {
				return hostdomain.ProcessRoleInvitationResult{}, err
			}
			if created {
				// 事件 ID 在业务层预分配；这里按固定偏移写多条事件箱记录，
				// 让同一个接受事务中的派生事件保持可追踪顺序。
				if err := insertHostOutbox(ctx, tx, command.EventID, "HostCreated", "host_profile", invitation.TargetUserID, command.NowMs); err != nil {
					return hostdomain.ProcessRoleInvitationResult{}, err
				}
				if err := insertHostOutbox(ctx, tx, command.EventID+1, "AgencyCreated", "agency", command.AgencyID, command.NowMs); err != nil {
					return hostdomain.ProcessRoleInvitationResult{}, err
				}
				if err := insertHostOutbox(ctx, tx, command.EventID+2, "AgencyMembershipChanged", "agency_membership", command.MembershipID, command.NowMs); err != nil {
					return hostdomain.ProcessRoleInvitationResult{}, err
				}
			}
		} else if invitation.InvitationType == hostdomain.InvitationTypeBD {
			// BD 邀请的接受只创建 bd_profile；BD 不需要 host_profile。
			if err := r.acceptBDInvitation(ctx, tx, command, &invitation, &result); err != nil {
				return hostdomain.ProcessRoleInvitationResult{}, err
			}
			if err := insertHostOutbox(ctx, tx, command.EventID, "BDCreated", "bd_profile", invitation.TargetUserID, command.NowMs); err != nil {
				return hostdomain.ProcessRoleInvitationResult{}, err
			}
		} else {
			return hostdomain.ProcessRoleInvitationResult{}, xerr.New(xerr.InvalidArgument, "invitation type is invalid")
		}
	}
	// 邀请终态必须在角色事实之后更新；如果事实创建失败，待处理状态保留给后续重试/处理。
	if err := updateRoleInvitationProcessed(ctx, tx, invitation); err != nil {
		return hostdomain.ProcessRoleInvitationResult{}, err
	}
	result.Invitation = invitation
	if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{
		CommandID:   command.CommandID,
		CommandType: hostdomain.CommandTypeProcessRoleInvitation,
		ResultType:  hostdomain.ResultTypeInvitation,
		ResultID:    invitation.InvitationID,
		CreatedAtMs: command.NowMs,
	}); err != nil {
		return hostdomain.ProcessRoleInvitationResult{}, mapHostDuplicateError(err)
	}
	if err := tx.Commit(); err != nil {
		return hostdomain.ProcessRoleInvitationResult{}, err
	}

	return result, nil
}

func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, command hostservice.ProcessRoleInvitationCommand, invitation *hostdomain.RoleInvitation, result *hostdomain.ProcessRoleInvitationResult) (bool, error) {
	// 邀请可能放置一段时间后才被接受，所以必须用当前用户区域再校验一次。
	regionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE")
	if err != nil {
		return false, err
	}
	if regionID != invitation.RegionID {
		return false, xerr.New(xerr.PermissionDenied, "target region no longer matches invitation")
	}
	// 目标用户在邀请创建后可能已经通过申请成为 Host，接受时需要重新阻断。
	if _, ok, err := queryHostProfileMaybe(ctx, tx, invitation.TargetUserID, true); err != nil {
		return false, err
	} else if ok {
		return false, xerr.New(xerr.Conflict, "active host cannot become agency")
	}
	// 同一用户不能拥有多个有效 Agency；这里和唯一索引共同处理并发接受。
	if _, ok, err := queryActiveAgencyByOwner(ctx, tx, invitation.TargetUserID, true); err != nil {
		return false, err
	} else if ok {
		return false, xerr.New(xerr.Conflict, "target already owns active agency")
	}

	// Agency、拥有者 host_profile、拥有者成员关系是同一业务事实的三张表投影，
	// 必须全部创建成功后邀请才能进入已接受状态。
	agency := hostdomain.Agency{
		AgencyID:        command.AgencyID,
		OwnerUserID:     invitation.TargetUserID,
		RegionID:        invitation.RegionID,
		ParentBDUserID:  invitation.ParentBDUserID,
		Name:            invitation.AgencyName,
		Status:          hostdomain.AgencyStatusActive,
		JoinEnabled:     true,
		MaxHosts:        0,
		CreatedByUserID: invitation.InviterUserID,
		CreatedAtMs:     command.NowMs,
		UpdatedAtMs:     command.NowMs,
	}
	if err := insertAgency(ctx, tx, agency); err != nil {
		return false, mapHostDuplicateError(err)
	}
	hostProfile := hostdomain.HostProfile{
		UserID:              invitation.TargetUserID,
		Status:              hostdomain.HostStatusActive,
		RegionID:            invitation.RegionID,
		CurrentAgencyID:     agency.AgencyID,
		CurrentMembershipID: command.MembershipID,
		Source:              hostdomain.HostSourceAgencyInvitation,
		FirstBecameHostAtMs: command.NowMs,
		CreatedAtMs:         command.NowMs,
		UpdatedAtMs:         command.NowMs,
	}
	if err := insertHostProfile(ctx, tx, hostProfile); err != nil {
		return false, mapHostDuplicateError(err)
	}
	membership := hostdomain.AgencyMembership{
		MembershipID:   command.MembershipID,
		AgencyID:       agency.AgencyID,
		HostUserID:     invitation.TargetUserID,
		RegionID:       invitation.RegionID,
		MembershipType: hostdomain.MembershipTypeOwner,
		Status:         hostdomain.MembershipStatusActive,
		JoinedAtMs:     command.NowMs,
		CreatedAtMs:    command.NowMs,
		UpdatedAtMs:    command.NowMs,
	}
	if err := insertAgencyMembership(ctx, tx, membership); err != nil {
		return false, mapHostDuplicateError(err)
	}
	result.HostProfile = hostProfile
	result.Agency = agency
	result.Membership = membership
	return true, nil
}

func (r *Repository) acceptBDInvitation(ctx context.Context, tx *sql.Tx, command hostservice.ProcessRoleInvitationCommand, invitation *hostdomain.RoleInvitation, result *hostdomain.ProcessRoleInvitationResult) error {
	// BD 邀请同样以当前用户区域为准；跨区调整后不能接受旧区域邀请。
	regionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE")
	if err != nil {
		return err
	}
	if regionID != invitation.RegionID {
		return xerr.New(xerr.PermissionDenied, "target region no longer matches invitation")
	}
	// BD 身份是独立角色事实，不依赖 host_profile；只需要阻止重复有效 BD。
	if _, ok, err := queryBDProfileMaybe(ctx, tx, invitation.TargetUserID, true); err != nil {
		return err
	} else if ok {
		return xerr.New(xerr.Conflict, "target already has active bd profile")
	}
	bd := hostdomain.BDProfile{
		UserID:             invitation.TargetUserID,
		Role:               hostdomain.BDRoleBD,
		RegionID:           invitation.RegionID,
		ParentLeaderUserID: invitation.ParentLeaderUserID,
		Status:             hostdomain.BDStatusActive,
		CreatedByUserID:    invitation.InviterUserID,
		CreatedAtMs:        command.NowMs,
		UpdatedAtMs:        command.NowMs,
	}
	if err := insertBDProfile(ctx, tx, bd); err != nil {
		return mapHostDuplicateError(err)
	}
	result.BDProfile = bd
	return nil
}
