package identity

import (
	"context"
	"database/sql"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
	"hyapp/services/user-service/internal/storage/mysql/shared"
	userstorage "hyapp/services/user-service/internal/storage/mysql/user"
)

func (r *Repository) GetUserIdentity(ctx context.Context, userID int64) (userdomain.Identity, error) {
	// 直接读取 users 当前快照；是否懒过期由 service 层统一判断。
	user, err := userstorage.New(r.db).GetUser(ctx, userID)
	if err != nil {
		return userdomain.Identity{}, err
	}
	if user.CurrentDisplayUserID == "" {
		// 理论上用户创建事务会保证短号存在，该分支保护脏数据。
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDNotFound, "display_user_id not found")
	}

	return user.EffectiveIdentity(), nil
}

// ResolveDisplayUserID 只解析当前有效展示号。
// 查询前先按输入短号触发懒过期，保证过期靓号不继续解析，默认短号能恢复解析。

func (r *Repository) ResolveDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (userdomain.Identity, error) {
	// 解析前按输入短号触发懒过期，避免过期靓号继续被解析。
	if err := r.expirePrettyByDisplayUserID(ctx, displayUserID, nowMs, "lazy_resolve"); err != nil {
		return userdomain.Identity{}, err
	}

	user, err := userstorage.QueryUser(ctx, r.db, "WHERE current_display_user_id = ?", displayUserID)
	if err == sql.ErrNoRows {
		// held 默认号和过期靓号都不会匹配 current_display_user_id。
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDNotFound, "display_user_id not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}

	return user.EffectiveIdentity(), nil
}

// ChangeDisplayUserID 修改默认短号；有 active 靓号时拒绝，避免过期恢复目标不清晰。

func (r *Repository) ChangeDisplayUserID(ctx context.Context, command userdomain.DisplayUserIDChangeCommand) (userdomain.Identity, error) {
	// 默认短号修改涉及 users、短号记录和变更日志，必须在一个事务内完成。
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.Identity{}, err
	}
	defer tx.Rollback()

	user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID)
	if err == sql.ErrNoRows {
		// FOR UPDATE 锁住用户行，避免并发修改默认短号或申请靓号。
		return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}
	if user.DisplayUserIDExpired(command.ChangedAtMs) {
		// 修改前先恢复过期靓号，避免默认短号仍处于 held。
		identity, err := r.expirePrettyInTx(ctx, tx, user, "", command.ChangedAtMs, command.RequestID)
		if err != nil {
			return userdomain.Identity{}, err
		}
		user = userdomain.User{
			AppCode:                  user.AppCode,
			UserID:                   identity.UserID,
			DefaultDisplayUserID:     identity.DefaultDisplayUserID,
			CurrentDisplayUserID:     identity.DisplayUserID,
			CurrentDisplayUserIDKind: identity.DisplayUserIDKind,
			Status:                   user.Status,
			CreatedAtMs:              user.CreatedAtMs,
			UpdatedAtMs:              command.ChangedAtMs,
		}
	}
	if user.CurrentDisplayUserIDKind == userdomain.DisplayUserIDKindPretty {
		// active 靓号期间拒绝改默认短号，保证过期恢复目标稳定。
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDPrettyActive, "pretty display_user_id is active")
	}
	if user.DefaultDisplayUserID == command.NewDisplayUserID {
		// 修改为相同默认短号按幂等成功处理，不写日志。
		return user.EffectiveIdentity(), nil
	}
	if err := r.assertDisplayUserIDAvailable(ctx, tx, command.NewDisplayUserID, command.UserID, true); err != nil {
		return userdomain.Identity{}, err
	}
	if command.CooldownMs > 0 {
		// 冷却期检查必须在事务内读取变更日志，避免并发绕过。
		if err := r.assertChangeCooldown(ctx, tx, command); err != nil {
			return userdomain.Identity{}, err
		}
	}

	// 旧默认短号释放，新默认短号插入为 active。
	_, err = tx.ExecContext(ctx, `
		UPDATE user_display_user_ids
		SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
	`, string(userdomain.DisplayUserIDStatusReleased), command.ChangedAtMs, command.Reason, command.ChangedAtMs, appcode.Normalize(command.AppCode), command.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive))
	if err != nil {
		return userdomain.Identity{}, err
	}

	_, err = tx.ExecContext(ctx, `
		INSERT INTO user_display_user_ids (app_code, display_user_id, user_id, display_user_id_kind, status, assigned_at_ms, activated_at_ms, created_at_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(command.AppCode), command.NewDisplayUserID, command.UserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive), command.ChangedAtMs, command.ChangedAtMs, command.ChangedAtMs, command.ChangedAtMs)
	if err != nil {
		return userdomain.Identity{}, shared.MapDisplayDuplicateError(err)
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE users
		SET default_display_user_id = ?, current_display_user_id = ?, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = NULL, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, command.NewDisplayUserID, command.NewDisplayUserID, string(userdomain.DisplayUserIDKindDefault), command.ChangedAtMs, appcode.Normalize(command.AppCode), command.UserID)
	if err != nil {
		return userdomain.Identity{}, shared.MapDisplayDuplicateError(err)
	}

	if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{
		AppCode:     appcode.Normalize(command.AppCode),
		UserID:      command.UserID,
		OldID:       user.DefaultDisplayUserID,
		NewID:       command.NewDisplayUserID,
		OldKind:     userdomain.DisplayUserIDKindDefault,
		NewKind:     userdomain.DisplayUserIDKindDefault,
		ChangeType:  "default_change",
		Reason:      command.Reason,
		OperatorID:  command.OperatorUserID,
		RequestID:   command.RequestID,
		CreatedAtMs: command.ChangedAtMs,
	}); err != nil {
		return userdomain.Identity{}, err
	}

	if err := tx.Commit(); err != nil {
		// commit 失败时调用方不能使用返回 identity。
		return userdomain.Identity{}, err
	}

	return userdomain.Identity{
		AppCode:              appcode.Normalize(command.AppCode),
		UserID:               command.UserID,
		DisplayUserID:        command.NewDisplayUserID,
		DefaultDisplayUserID: command.NewDisplayUserID,
		DisplayUserIDKind:    userdomain.DisplayUserIDKindDefault,
		Status:               userdomain.DisplayUserIDStatusActive,
	}, nil
}

// ApplyPrettyDisplayUserID 创建临时靓号租约，并把默认短号置为 held。
