package auth

import (
	"context"
	"database/sql"

	"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"
	identitystorage "hyapp/services/user-service/internal/storage/mysql/identity"
	invitestorage "hyapp/services/user-service/internal/storage/mysql/invite"
	userstorage "hyapp/services/user-service/internal/storage/mysql/user"
)

func (r *Repository) SetPassword(ctx context.Context, account authdomain.PasswordAccount) error {
	// password_accounts 以 user_id 唯一，v1 只允许首次设置密码。
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO password_accounts (app_code, user_id, password_hash, hash_alg, created_at_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(account.AppCode), account.UserID, account.PasswordHash, account.HashAlg, account.CreatedAtMs, account.UpdatedAtMs)
	if err != nil {
		// 唯一键冲突映射为 PASSWORD_ALREADY_SET。
		return mapAuthDuplicateError(err)
	}

	return nil
}

// CreatePasswordUser 原子创建完整密码账号，服务快捷建号和联调账号准备。
func (r *Repository) CreatePasswordUser(ctx context.Context, user userdomain.User, identity userdomain.Identity, account authdomain.PasswordAccount, session authdomain.Session, inviteBind invitedomain.BindCommand, maxAccountsPerDevice int32) error {
	// 普通快捷账号必须和 refresh session 同事务提交，避免返回不可登录的 uid；
	// 全站机器人会传入空 session，只写资料和密码身份，不产生任何可续期登录态。
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	if err := ensureRegisterDeviceQuotaTx(ctx, tx, user.AppCode, user.RegisterDeviceID, maxAccountsPerDevice); err != nil {
		// 快捷建号也写 users.register_device_id；事务内锁定同设备行，避免绕过注册风控上限。
		return err
	}
	if err := userstorage.InsertUserIdentity(ctx, tx, user, identity); err != nil {
		// 默认短号唯一键冲突交给 service 使用新 user_id/display_user_id 重试。
		return mapAuthDuplicateError(err)
	}
	if _, err := invitestorage.EnsurePrimaryCodeForUser(ctx, tx, user.AppCode, user.UserID, user.CreatedAtMs); err != nil {
		return err
	}
	if _, err := invitestorage.BindRelation(ctx, tx, inviteBind); err != nil {
		return err
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO password_accounts (app_code, user_id, password_hash, hash_alg, created_at_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(account.AppCode), account.UserID, account.PasswordHash, account.HashAlg, account.CreatedAtMs, account.UpdatedAtMs); err != nil {
		return mapAuthDuplicateError(err)
	}
	if session.SessionID != "" {
		if err := insertSession(ctx, tx, session); err != nil {
			// session 写入失败时必须回滚用户和密码身份，否则客户端拿不到 refresh token 却占用账号。
			return err
		}
	}

	return tx.Commit()
}

// FindPasswordByDisplayUserID 通过当前 active 展示号或 held 默认短号登录别名查找用户密码身份。

func (r *Repository) FindPasswordByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (authdomain.PasswordAccount, error) {
	// 密码登录前先按输入展示号触发靓号懒过期；输入 held 默认短号时也能恢复已到期靓号。
	if err := identitystorage.ExpirePrettyByDisplayUserID(ctx, r.db, displayUserID, nowMs, "lazy_password_login"); err != nil {
		return authdomain.PasswordAccount{}, err
	}

	var account authdomain.PasswordAccount
	var displayUserIDKind string
	var displayUserIDExpiresAtMs int64
	err := r.db.QueryRowContext(ctx, `
		SELECT
		  pa.app_code,
		  pa.user_id,
		  active_du.display_user_id,
		  active_du.display_user_id_kind,
		  COALESCE(active_du.expires_at_ms, 0),
		  pa.password_hash,
		  pa.hash_alg,
		  pa.created_at_ms,
		  pa.updated_at_ms
		FROM user_display_user_ids login_du
		INNER JOIN user_display_user_ids active_du
		  ON active_du.app_code = login_du.app_code
		 AND active_du.user_id = login_du.user_id
		 AND active_du.status = ?
		 AND (active_du.expires_at_ms IS NULL OR active_du.expires_at_ms = 0 OR active_du.expires_at_ms > ?)
		INNER JOIN password_accounts pa
		  ON pa.app_code = login_du.app_code
		 AND pa.user_id = login_du.user_id
		WHERE login_du.app_code = ?
		  AND login_du.display_user_id = ?
		  AND (
		    (
		      login_du.status = ?
		      AND (login_du.expires_at_ms IS NULL OR login_du.expires_at_ms = 0 OR login_du.expires_at_ms > ?)
		    )
		    OR (
		      login_du.status = ?
		      AND login_du.display_user_id_kind = ?
		    )
		  )
		LIMIT 1
	`, string(userdomain.DisplayUserIDStatusActive), nowMs,
		appcode.FromContext(ctx), displayUserID,
		string(userdomain.DisplayUserIDStatusActive), nowMs,
		string(userdomain.DisplayUserIDStatusHeld), string(userdomain.DisplayUserIDKindDefault)).
		Scan(&account.AppCode, &account.UserID, &account.DisplayUserID, &displayUserIDKind, &displayUserIDExpiresAtMs, &account.PasswordHash, &account.HashAlg, &account.CreatedAtMs, &account.UpdatedAtMs)
	if err == sql.ErrNoRows {
		// 短号不存在、处于 released/expired、没有对应 active 展示号或未设置密码都返回 not found，由 service 统一映射 AUTH_FAILED。
		return authdomain.PasswordAccount{}, xerr.New(xerr.NotFound, "password account not found")
	}
	if err != nil {
		return authdomain.PasswordAccount{}, err
	}
	if err := r.repairPasswordLoginIdentity(ctx, account.AppCode, account.UserID, account.DisplayUserID, displayUserIDKind, displayUserIDExpiresAtMs, nowMs); err != nil {
		return authdomain.PasswordAccount{}, err
	}

	return account, nil
}

func (r *Repository) repairPasswordLoginIdentity(ctx context.Context, appCode string, userID int64, displayUserID string, displayUserIDKind string, expiresAtMs int64, nowMs int64) error {
	// 密码登录以 user_display_user_ids 的 active 行为准；同步 users 快照后，后续 freshUser 和 token 会携带同一个当前展示号。
	_, err := r.db.ExecContext(ctx, `
		UPDATE users
		SET current_display_user_id = ?,
		    current_display_user_id_kind = ?,
		    current_display_user_id_expires_at_ms = ?,
		    updated_at_ms = GREATEST(updated_at_ms, ?)
		WHERE app_code = ?
		  AND user_id = ?
		  AND (
		    current_display_user_id <> ?
		    OR current_display_user_id_kind <> ?
		    OR COALESCE(current_display_user_id_expires_at_ms, 0) <> ?
		  )
	`, displayUserID, displayUserIDKind, nullableExpiresAt(expiresAtMs), nowMs,
		appcode.Normalize(appCode), userID, displayUserID, displayUserIDKind, expiresAtMs)
	return err
}

func nullableExpiresAt(value int64) any {
	if value <= 0 {
		return nil
	}
	return value
}

// CreateSession 创建新的 refresh session。
