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) ApplyPrettyDisplayUserID(ctx context.Context, command userdomain.PrettyDisplayUserIDCommand) (userdomain.Identity, string, error) {
	// 靓号申请涉及租约、默认短号 held、当前展示号覆盖和变更日志，必须事务化。
	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 {
		// 用户不存在不能创建租约。
		return userdomain.Identity{}, "", xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	if user.DisplayUserIDExpired(command.NowMs) {
		// 如果旧靓号已过期，先恢复默认短号再申请新靓号。
		identity, err := r.expirePrettyInTx(ctx, tx, user, "", command.NowMs, command.RequestID)
		if err != nil {
			return userdomain.Identity{}, "", err
		}
		user.CurrentDisplayUserID = identity.DisplayUserID
		user.CurrentDisplayUserIDKind = identity.DisplayUserIDKind
		user.CurrentDisplayUserIDExpiresAtMs = 0
	}
	if user.CurrentDisplayUserIDKind == userdomain.DisplayUserIDKindPretty {
		// 同一用户不能叠加 active 靓号。
		return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPrettyActive, "pretty display_user_id is active")
	}
	if err := r.assertDisplayUserIDAvailable(ctx, tx, command.PrettyDisplayID, command.UserID, false); err != nil {
		if xerr.IsCode(err, xerr.DisplayUserIDExists) {
			// 靓号冲突使用专用 reason，便于前端展示购买失败原因。
			return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display_user_id is not available")
		}
		return userdomain.Identity{}, "", err
	}

	// 默认短号从 active 改为 held，过期时会重新激活。
	expiresAtMs := command.NowMs + command.LeaseDurationMs
	_, err = tx.ExecContext(ctx, `
		UPDATE user_display_user_ids
		SET status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
	`, string(userdomain.DisplayUserIDStatusHeld), command.NowMs, 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 pretty_display_user_id_leases (app_code, lease_id, display_user_id, user_id, status, starts_at_ms, expires_at_ms, payment_receipt_id, source, created_at_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(command.AppCode), command.LeaseID, command.PrettyDisplayID, command.UserID, string(userdomain.PrettyLeaseStatusActive), command.NowMs, expiresAtMs, shared.NullableString(command.PaymentReceiptID), command.Source, command.NowMs, command.NowMs)
	if err != nil {
		return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(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, expires_at_ms, created_at_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(command.AppCode), command.PrettyDisplayID, command.UserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive), command.NowMs, command.NowMs, expiresAtMs, command.NowMs, command.NowMs)
	if err != nil {
		return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err)
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE users
		SET current_display_user_id = ?, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, command.PrettyDisplayID, string(userdomain.DisplayUserIDKindPretty), expiresAtMs, command.NowMs, appcode.Normalize(command.AppCode), command.UserID)
	if err != nil {
		return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err)
	}

	if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{
		AppCode:     appcode.Normalize(command.AppCode),
		UserID:      command.UserID,
		OldID:       user.CurrentDisplayUserID,
		NewID:       command.PrettyDisplayID,
		OldKind:     user.CurrentDisplayUserIDKind,
		NewKind:     userdomain.DisplayUserIDKindPretty,
		ChangeType:  "pretty_apply",
		LeaseID:     command.LeaseID,
		Reason:      "pretty_apply",
		OperatorID:  command.OperatorUserID,
		RequestID:   command.RequestID,
		CreatedAtMs: command.NowMs,
	}); err != nil {
		return userdomain.Identity{}, "", err
	}

	if err := tx.Commit(); err != nil {
		// commit 失败时租约和展示号覆盖都不能视为成功。
		return userdomain.Identity{}, "", err
	}

	return userdomain.Identity{
		AppCode:                  appcode.Normalize(command.AppCode),
		UserID:                   command.UserID,
		DisplayUserID:            command.PrettyDisplayID,
		DefaultDisplayUserID:     user.DefaultDisplayUserID,
		DisplayUserIDKind:        userdomain.DisplayUserIDKindPretty,
		DisplayUserIDExpiresAtMs: expiresAtMs,
		Status:                   userdomain.DisplayUserIDStatusActive,
	}, command.LeaseID, nil
}

// ExpirePrettyDisplayUserID 把到期靓号恢复成默认短号；该方法是定时和懒过期的共同事务。

func (r *Repository) ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) {
	// 定时过期、懒过期和手动过期都复用同一事务入口。
	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 {
		// 用户不存在时不能恢复短号。
		return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}

	identity, err := r.expirePrettyInTx(ctx, tx, user, command.LeaseID, command.NowMs, command.RequestID)
	if err != nil {
		return userdomain.Identity{}, err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.Identity{}, err
	}

	return identity, nil
}

func (r *Repository) expirePrettyByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64, requestID string) error {
	// 只找可能受输入短号影响的过期 pretty 用户，避免每次解析全表扫描。
	var userID int64
	err := r.db.QueryRowContext(ctx, `
		SELECT user_id
		FROM users
		WHERE app_code = ?
		  AND current_display_user_id_kind = ?
		  AND current_display_user_id_expires_at_ms IS NOT NULL
		  AND current_display_user_id_expires_at_ms <= ?
		  AND (current_display_user_id = ? OR default_display_user_id = ?)
		LIMIT 1
	`, appcode.FromContext(ctx), string(userdomain.DisplayUserIDKindPretty), nowMs, displayUserID, displayUserID).Scan(&userID)
	if err == sql.ErrNoRows {
		// 没有过期记录时无需恢复。
		return nil
	}
	if err != nil {
		return err
	}

	_, err = r.ExpirePrettyDisplayUserID(ctx, userdomain.ExpirePrettyDisplayUserIDCommand{
		UserID:    userID,
		RequestID: requestID,
		NowMs:     nowMs,
	})
	return err
}

// ExpirePrettyByDisplayUserID 给认证存储复用短号懒过期事务。

func ExpirePrettyByDisplayUserID(ctx context.Context, db *sql.DB, displayUserID string, nowMs int64, requestID string) error {
	return New(db).expirePrettyByDisplayUserID(ctx, displayUserID, nowMs, requestID)
}

func (r *Repository) expirePrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, leaseID string, nowMs int64, requestID string) (userdomain.Identity, error) {
	// 调用方必须已经锁住 users 行；本函数继续锁 active lease 行。
	if user.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty {
		// 非 pretty 状态重复过期按幂等成功返回当前身份。
		return user.EffectiveIdentity(), nil
	}
	if user.CurrentDisplayUserIDExpiresAtMs > nowMs {
		// 未到期时拒绝恢复，防止提前释放已购买靓号。
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not expired")
	}

	var activeLeaseID string
	err := tx.QueryRowContext(ctx, `
		SELECT lease_id
		FROM pretty_display_user_id_leases
		WHERE app_code = ? AND user_id = ? AND status = ?
		FOR UPDATE
	`, appcode.FromContext(ctx), user.UserID, string(userdomain.PrettyLeaseStatusActive)).Scan(&activeLeaseID)
	if err != nil && err != sql.ErrNoRows {
		return userdomain.Identity{}, err
	}
	if leaseID != "" && activeLeaseID != "" && leaseID != activeLeaseID {
		// 指定 lease_id 不匹配 active lease 时拒绝，防止旧任务误释放新租约。
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not active")
	}

	// 租约、pretty 短号记录、默认短号记录和 users 快照一起恢复。
	_, err = tx.ExecContext(ctx, `
		UPDATE pretty_display_user_id_leases
		SET status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND status = ?
	`, string(userdomain.PrettyLeaseStatusExpired), nowMs, appcode.FromContext(ctx), user.UserID, string(userdomain.PrettyLeaseStatusActive))
	if err != nil {
		return userdomain.Identity{}, err
	}

	_, 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.DisplayUserIDStatusExpired), nowMs, "pretty_expire", nowMs, appcode.FromContext(ctx), user.UserID, user.CurrentDisplayUserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive))
	if err != nil {
		return userdomain.Identity{}, err
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE user_display_user_ids
		SET status = ?, activated_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
	`, string(userdomain.DisplayUserIDStatusActive), nowMs, nowMs, appcode.FromContext(ctx), user.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusHeld))
	if err != nil {
		return userdomain.Identity{}, err
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE users
		SET current_display_user_id = default_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 = ?
	`, string(userdomain.DisplayUserIDKindDefault), nowMs, appcode.FromContext(ctx), user.UserID)
	if err != nil {
		return userdomain.Identity{}, err
	}

	if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{
		AppCode:     appcode.FromContext(ctx),
		UserID:      user.UserID,
		OldID:       user.CurrentDisplayUserID,
		NewID:       user.DefaultDisplayUserID,
		OldKind:     userdomain.DisplayUserIDKindPretty,
		NewKind:     userdomain.DisplayUserIDKindDefault,
		ChangeType:  "pretty_expire",
		LeaseID:     activeLeaseID,
		Reason:      "pretty_expire",
		RequestID:   requestID,
		CreatedAtMs: nowMs,
	}); err != nil {
		return userdomain.Identity{}, err
	}

	return userdomain.Identity{
		AppCode:              appcode.FromContext(ctx),
		UserID:               user.UserID,
		DisplayUserID:        user.DefaultDisplayUserID,
		DefaultDisplayUserID: user.DefaultDisplayUserID,
		DisplayUserIDKind:    userdomain.DisplayUserIDKindDefault,
		Status:               userdomain.DisplayUserIDStatusActive,
	}, nil
}
