package auth

import (
	"context"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	authdomain "hyapp/services/user-service/internal/domain/auth"
	invitedomain "hyapp/services/user-service/internal/domain/invite"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

// QuickCreateAccount 创建一个已完成资料且已设置密码的账号。
// 普通联调账号会立即返回登录态；全站机器人只返回 uid/display_id，不创建 session，也不能通过后续接口登录。
func (s *Service) QuickCreateAccount(ctx context.Context, password string, registration authdomain.ThirdPartyRegistration, meta Meta) (authdomain.Token, error) {
	meta.AppCode = appcode.Normalize(meta.AppCode)
	registration.AppCode = meta.AppCode
	registration = normalizeThirdPartyRegistration(registration, meta)
	meta.Platform = registration.Platform
	meta.AppVersion = registration.AppVersion
	meta.BuildNumber = registration.BuildNumber
	meta.Language = registration.Language
	meta.Timezone = registration.Timezone
	password = strings.TrimSpace(password)
	if password == "" {
		return authdomain.Token{}, xerr.New(xerr.InvalidArgument, "password is required")
	}
	if registration.DeviceID == "" || registration.Platform == "" {
		// refresh session 必须绑定设备，平台也会进入注册来源和风控审计。
		return authdomain.Token{}, xerr.New(xerr.InvalidArgument, "device_id and platform are required")
	}
	if err := validateThirdPartyRegistrationRequiredForCreate(registration); err != nil {
		// 快捷账号直接跳过 onboarding，因此必须一次性写齐客户端准入所需的最小资料。
		return authdomain.Token{}, err
	}
	if err := validateThirdPartyRegistration(registration); err != nil {
		return authdomain.Token{}, err
	}
	if s.authRepository == nil {
		return authdomain.Token{}, xerr.New(xerr.Unavailable, "auth repository is not configured")
	}
	maxAccountsPerDevice, err := s.ensureDeviceCanCreatePasswordAccount(ctx, registration, meta)
	if err != nil {
		return authdomain.Token{}, err
	}

	registration, err = s.resolveRegistrationCountry(ctx, registration)
	if err != nil {
		return authdomain.Token{}, err
	}
	passwordHash, err := hashPassword(password)
	if err != nil {
		return authdomain.Token{}, err
	}
	isGameRobot := userdomain.IsGameRobotRegisterSource(registration.Source)

	var lastErr error
	for attempt := 0; attempt < s.allocateMaxAttempts; attempt++ {
		user, displayIdentity := s.newUserWithIdentity(attempt)
		user = applyThirdPartyRegistration(user, registration)
		user.ProfileCompleted = true
		user.ProfileCompletedAtMs = user.CreatedAtMs
		user.OnboardingStatus = userdomain.OnboardingStatusCompleted

		displayUserID, err := s.authRepository.AllocateDisplayUserID(ctx, registration.AppCode)
		if err != nil {
			return authdomain.Token{}, err
		}
		user.DefaultDisplayUserID = displayUserID
		user.CurrentDisplayUserID = displayUserID
		displayIdentity.AppCode = registration.AppCode
		displayIdentity.DisplayUserID = displayUserID
		displayIdentity.DefaultDisplayUserID = displayUserID
		if !userdomain.ValidDisplayUserID(displayIdentity.DisplayUserID) {
			// 分配器或历史种子异常时不写库，直接尝试下一个候选。
			continue
		}

		var session authdomain.Session
		var refreshToken string
		if !isGameRobot {
			var err error
			session, refreshToken, err = s.newSession(registration.AppCode, user.UserID, registration.DeviceID)
			if err != nil {
				return authdomain.Token{}, err
			}
		}
		account := authdomain.PasswordAccount{
			AppCode:       registration.AppCode,
			UserID:        user.UserID,
			DisplayUserID: displayUserID,
			PasswordHash:  passwordHash,
			HashAlg:       hashAlgBcrypt,
			CreatedAtMs:   user.CreatedAtMs,
			UpdatedAtMs:   user.UpdatedAtMs,
		}
		inviteBind := invitedomain.BindCommand{
			AppCode:         registration.AppCode,
			InvitedUserID:   user.UserID,
			InvitedRegionID: registration.RegionID,
			InviteCode:      registration.InviteCode,
			Source:          "quick_account_creation",
			RequestID:       meta.RequestID,
			BoundAtMs:       user.CreatedAtMs,
		}
		err = s.authRepository.CreatePasswordUser(ctx, user, displayIdentity, account, session, inviteBind, maxAccountsPerDevice)
		if err == nil {
			if isGameRobot {
				// 全站机器人账号是后台运营资产，不是 App 登录主体；创建成功后不给 access/refresh token，
				// 也不写登录成功审计和 IP 风控任务，避免后台批量造号污染登录链路与新增用户统计。
				return authdomain.Token{
					AppCode:              user.AppCode,
					UserID:               user.UserID,
					DisplayUserID:        user.CurrentDisplayUserID,
					DefaultDisplayUserID: user.DefaultDisplayUserID,
					DisplayUserIDKind:    string(user.CurrentDisplayUserIDKind),
					ProfileCompleted:     user.ProfileCompleted,
					OnboardingStatus:     string(user.OnboardingStatus),
					TokenType:            tokenType,
				}, nil
			}
			s.audit(ctx, meta, user.UserID, loginPassword, "", resultSuccess, "")
			// 快捷账号也是可登录主体，设备档案与普通注册保持一致；机器人分支在上方已提前返回。
			s.recordUserDevice(ctx, deviceMetaWithRegistration(meta, registration), user.UserID, registration.DeviceID)
			token, tokenErr := s.issueToken(user, session.SessionID, session.DeviceID, refreshToken, session.ExpiresAtMs)
			if tokenErr != nil {
				return authdomain.Token{}, tokenErr
			}
			s.importIMAccountAfterCommit(ctx, user, meta)
			s.enqueueLoginIPRiskJob(ctx, meta, token, "account", registration.Platform, registration.Language, registration.Timezone)
			return token, nil
		}
		if xerr.IsCode(err, xerr.DisplayUserIDExists) {
			// 默认短号冲突可重试，避免少量并发建号把快捷入口打成失败。
			lastErr = err
			continue
		}

		s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.CodeOf(err)))
		return authdomain.Token{}, err
	}

	if lastErr != nil {
		s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.DisplayUserIDAllocateFailed))
	}
	return authdomain.Token{}, xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id allocation failed")
}

func (s *Service) ensureDeviceCanCreatePasswordAccount(ctx context.Context, registration authdomain.ThirdPartyRegistration, meta Meta) (int32, error) {
	config, err := s.GetRegisterRiskConfig(ctx)
	if err != nil {
		s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.CodeOf(err)))
		return 0, err
	}
	displayUserIDs, err := s.authRepository.ListDisplayUserIDsByRegisterDeviceID(ctx, registration.AppCode, registration.DeviceID)
	if err != nil {
		// 快捷建号和三方注册共享设备注册上限；读取失败不能放行，否则会突破账号数量约束。
		s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.CodeOf(err)))
		return 0, err
	}
	count := len(displayUserIDs)
	if count < int(config.MaxAccountsPerDevice) {
		return config.MaxAccountsPerDevice, nil
	}
	s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.DeviceAlreadyRegistered))
	return 0, xerr.New(xerr.DeviceAlreadyRegistered, xerr.DeviceAlreadyRegisteredMessage(count, displayUserIDs))
}
