package user

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

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

// ForceReleaseUserBans 是后台管理员覆盖全部封禁来源的唯一持久化入口。
// 用户主状态、管理员封禁、经理封禁和命令幂等事实必须在同一事务提交，避免出现“状态已恢复但封禁仍 active”的中间态。
func (r *Repository) ForceReleaseUserBans(ctx context.Context, command userservice.AdminForceUnbanCommand) (userservice.AdminForceUnbanResult, error) {
	if r == nil || r.db == nil {
		return userservice.AdminForceUnbanResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	appCode := appcode.FromContext(ctx)
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	defer func() { _ = tx.Rollback() }()

	// 先锁 users 行，使同一用户的状态切换串行化；事实表的条件 UPDATE 随后锁定当前全部 active 范围。
	// 经理封禁切状态前也会复验其 block 仍 active，因此与本事务并发时不会留下 released block + banned 用户的不一致组合。
	currentStatus, err := lockUserStatus(ctx, tx, appCode, command.TargetUserID)
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	existing, result, err := claimAdminForceUnbanCommand(ctx, tx, appCode, command)
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	if existing {
		if err := tx.Commit(); err != nil {
			return userservice.AdminForceUnbanResult{}, err
		}
		// 幂等重试返回当前用户状态，但绝不重新扫描 active 事实；首次命令之后的新封禁必须由新的 request_id 显式释放。
		result.User, err = r.GetUser(ctx, command.TargetUserID)
		return result, err
	}

	releaseReason := fmt.Sprintf("admin_force_unban:%d:%s", command.OperatorAdminID, command.Reason)
	adminUpdate, err := tx.ExecContext(ctx, `
		UPDATE admin_user_bans
		SET status = ?, released_by_admin_id = ?, released_reason = ?, released_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND target_user_id = ? AND status = ?`,
		userservice.AdminUserBanStatusReleased, command.OperatorAdminID, releaseReason, command.NowMs, command.NowMs,
		appCode, command.TargetUserID, userservice.AdminUserBanStatusActive,
	)
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	result.ReleasedAdminBanCount, err = adminUpdate.RowsAffected()
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}

	managerUpdate, err := tx.ExecContext(ctx, `
		UPDATE manager_user_blocks
		SET status = ?, released_reason = ?, released_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND target_user_id = ? AND status = ?`,
		userservice.ManagerUserBlockStatusReleased, releaseReason, command.NowMs, command.NowMs,
		appCode, command.TargetUserID, userservice.ManagerUserBlockStatusActive,
	)
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	result.ReleasedManagerBlockCount, err = managerUpdate.RowsAffected()
	if err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}

	newStatus := currentStatus
	switch currentStatus {
	case userdomain.StatusActive:
		// 数据修复场景可能存在 active 用户仍挂 active 封禁事实；释放事实即可，不制造 active -> active 审计噪声。
	case userdomain.StatusBanned, userdomain.StatusDisabled:
		newStatus = userdomain.StatusActive
		result.StatusRestored = true
		if _, err := tx.ExecContext(ctx, `
			UPDATE users SET status = ?, updated_at_ms = ?
			WHERE app_code = ? AND user_id = ?`,
			string(newStatus), command.NowMs, appCode, command.TargetUserID,
		); err != nil {
			return userservice.AdminForceUnbanResult{}, 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, string(currentStatus), string(newStatus), userservice.OperatorTypeAdmin,
			command.OperatorAdminID, "admin_force_unban", command.RequestID, command.NowMs,
		); err != nil {
			return userservice.AdminForceUnbanResult{}, err
		}
	default:
		// 未知主状态不能被强制改写，否则会把未来新增的独立治理状态静默降级为 active。
		return userservice.AdminForceUnbanResult{}, xerr.New(xerr.Conflict, "user status cannot be force unbanned")
	}

	if _, err := tx.ExecContext(ctx, `
		UPDATE admin_user_force_unban_commands
		SET released_admin_ban_count = ?, released_manager_block_count = ?,
		    old_status = ?, new_status = ?, status_restored = ?, updated_at_ms = ?
		WHERE app_code = ? AND command_id = ?`,
		result.ReleasedAdminBanCount, result.ReleasedManagerBlockCount,
		string(currentStatus), string(newStatus), result.StatusRestored, command.NowMs,
		appCode, command.RequestID,
	); err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	if err := tx.Commit(); err != nil {
		return userservice.AdminForceUnbanResult{}, err
	}
	result.User, err = r.GetUser(ctx, command.TargetUserID)
	return result, err
}

// claimAdminForceUnbanCommand 固化一次“释放全部来源”的命令载荷和首次结果。
// INSERT IGNORE + FOR UPDATE 使并发重试等待首个事务完成，并拒绝同一 request_id 被复用到其他用户或管理员。
func claimAdminForceUnbanCommand(ctx context.Context, tx *sql.Tx, appCode string, command userservice.AdminForceUnbanCommand) (bool, userservice.AdminForceUnbanResult, error) {
	result := userservice.AdminForceUnbanResult{}
	insert, err := tx.ExecContext(ctx, `
		INSERT IGNORE INTO admin_user_force_unban_commands (
			app_code, command_id, target_user_id, operator_admin_id, reason,
			created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?)`,
		appCode, command.RequestID, command.TargetUserID, command.OperatorAdminID, command.Reason,
		command.NowMs, command.NowMs,
	)
	if err != nil {
		return false, result, err
	}
	inserted, err := insert.RowsAffected()
	if err != nil {
		return false, result, err
	}
	if inserted == 1 {
		return false, result, nil
	}

	var targetUserID, operatorAdminID int64
	var reason string
	var restored bool
	err = tx.QueryRowContext(ctx, `
		SELECT target_user_id, operator_admin_id, reason,
		       released_admin_ban_count, released_manager_block_count, status_restored
		FROM admin_user_force_unban_commands
		WHERE app_code = ? AND command_id = ?
		FOR UPDATE`, appCode, command.RequestID).Scan(
		&targetUserID, &operatorAdminID, &reason,
		&result.ReleasedAdminBanCount, &result.ReleasedManagerBlockCount, &restored,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return false, result, xerr.New(xerr.Conflict, "admin force unban command disappeared")
	}
	if err != nil {
		return false, result, err
	}
	if targetUserID != command.TargetUserID || operatorAdminID != command.OperatorAdminID || reason != command.Reason {
		return false, result, xerr.New(xerr.Conflict, "admin force unban command payload does not match existing command")
	}
	result.StatusRestored = restored
	return true, result, nil
}
