package user

import (
	"context"
	"database/sql"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
	userservice "hyapp/services/user-service/internal/service/user"
)

// SetUserStatus 原子更新用户主状态，并在非 active 状态下吊销该用户所有 active refresh session。
func (r *Repository) SetUserStatus(ctx context.Context, command userservice.UserStatusCommand) (userservice.UserStatusPersistenceResult, error) {
	if r == nil || r.db == nil {
		return userservice.UserStatusPersistenceResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	appCode := appcode.Normalize(command.AppCode)
	if appCode == "" {
		appCode = appcode.FromContext(ctx)
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userservice.UserStatusPersistenceResult{}, err
	}
	defer tx.Rollback()

	var oldStatus string
	if err := tx.QueryRowContext(ctx, `
		SELECT status
		FROM users
		WHERE app_code = ? AND user_id = ?
		FOR UPDATE
	`, appCode, command.TargetUserID).Scan(&oldStatus); err != nil {
		if err == sql.ErrNoRows {
			return userservice.UserStatusPersistenceResult{}, xerr.New(xerr.NotFound, "user not found")
		}
		return userservice.UserStatusPersistenceResult{}, err
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE users
		SET status = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, string(command.Status), command.NowMs, appCode, command.TargetUserID)
	if err != nil {
		return userservice.UserStatusPersistenceResult{}, err
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO user_status_change_logs (
			app_code, target_user_id, old_status, new_status, operator_type, operator_user_id, reason, request_id, created_at_ms
		)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appCode, command.TargetUserID, oldStatus, string(command.Status), command.OperatorType, command.OperatorUserID, command.Reason, command.RequestID, command.NowMs); err != nil {
		return userservice.UserStatusPersistenceResult{}, err
	}

	sessionIDs := make([]string, 0)
	if command.Status != userdomain.StatusActive {
		rows, err := tx.QueryContext(ctx, `
			SELECT session_id
			FROM auth_sessions
			WHERE app_code = ? AND user_id = ? AND expires_at_ms > ?
			FOR UPDATE
		`, appCode, command.TargetUserID, command.NowMs)
		if err != nil {
			return userservice.UserStatusPersistenceResult{}, err
		}
		for rows.Next() {
			var sessionID string
			if err := rows.Scan(&sessionID); err != nil {
				_ = rows.Close()
				return userservice.UserStatusPersistenceResult{}, err
			}
			sessionIDs = append(sessionIDs, sessionID)
		}
		if err := rows.Close(); err != nil {
			return userservice.UserStatusPersistenceResult{}, err
		}
		if _, err := tx.ExecContext(ctx, `
			UPDATE auth_sessions
			SET revoked_at_ms = ?, revoked_reason = ?, revoked_request_id = ?, revoked_by = ?, updated_at_ms = ?
			WHERE app_code = ? AND user_id = ? AND revoked_at_ms IS NULL
		`, command.NowMs, userStatusRevokeReason(command.Status), command.RequestID, revokedBy(command.OperatorUserID), command.NowMs, appCode, command.TargetUserID); err != nil {
			return userservice.UserStatusPersistenceResult{}, err
		}
	}

	if err := tx.Commit(); err != nil {
		return userservice.UserStatusPersistenceResult{}, err
	}

	user, err := r.GetUser(appcode.WithContext(ctx, appCode), command.TargetUserID)
	if err != nil {
		return userservice.UserStatusPersistenceResult{}, err
	}
	return userservice.UserStatusPersistenceResult{User: user, RevokedSessionIDs: sessionIDs}, nil
}

func userStatusRevokeReason(status userdomain.Status) string {
	switch status {
	case userdomain.StatusBanned:
		return "USER_BANNED"
	case userdomain.StatusDisabled:
		return "USER_DISABLED"
	default:
		return "USER_STATUS_CHANGED"
	}
}

func revokedBy(operatorUserID int64) string {
	if operatorUserID > 0 {
		return "user_status_operator"
	}
	return "user_status"
}
