<?php
/**
 * Ability: wp-agent/set-user-role
 *
 * Setzt die Rolle eines bestehenden Nutzers direkt (ersetzt ALLE bisherigen
 * Rollen durch genau eine, über wp_update_user()). Nutzt die Capability
 * promote_users statt der breiteren edit_users: das ist die präzise
 * WordPress-Capability, an die der Kern selbst Rollenänderungen bindet
 * (siehe wp-admin/users.php, Bulk-Aktion "Rolle ändern zu"), unabhängig
 * davon, welche sonstigen Profilfelder edit_users freischaltet.
 *
 * Zwei harte, nicht per Flag umgehbare Schutzmechanismen:
 *  - User-ID 1 (die ursprüngliche Administrator-Kennung fast jeder
 *    WordPress-Installation) darf nie umgeschrieben werden.
 *  - Der aufrufende Account selbst (get_current_user_id()) darf seine eigene
 *    Rolle nie ändern. Da die Abilities API immer im Kontext des per
 *    Application Password authentifizierten Nutzers läuft, IST dieser
 *    Nutzer im produktiven Einsatz der Companion-Bot; die Selbst-Sperre
 *    schützt also genau den in der Aufgabenstellung geforderten
 *    "Companion-Bot darf sich nicht selbst anfassen"-Fall, ohne einen
 *    fest einprogrammierten Benutzernamen vorauszusetzen.
 *
 * Zusätzliches weiches Gate: Rollen, deren Capability-Satz manage_options
 * enthält (das ist administrator-äquivalent), werden nur mit dem
 * expliziten Input-Flag i_grant_admin=true akzeptiert, sonst 403.
 *
 * @package WPAgentCompanion
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

function wpagent_ability_set_user_role(): array {
	return array(
		'label'               => __( 'Nutzerrolle setzen', 'wp-agent-companion' ),
		'description'         => __( 'Setzt die Rolle eines bestehenden Nutzers direkt (ersetzt alle bisherigen Rollen durch genau eine). Administrator-äquivalente Rollen (Capability manage_options) erfordern das explizite Flag i_grant_admin=true, sonst 403. User-ID 1 und der aufrufende Account selbst dürfen nie geändert werden. Re-Read-verifiziert.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'user_id'       => array( 'type' => 'integer' ),
				'role'          => array(
					'type'        => 'string',
					'description' => __( 'Muss eine registrierte Rolle sein (wp_roles()).', 'wp-agent-companion' ),
				),
				'i_grant_admin' => array(
					'type'        => 'boolean',
					'default'     => false,
					'description' => __( 'Pflicht (true), wenn die Zielrolle die Capability manage_options besitzt (z. B. administrator).', 'wp-agent-companion' ),
				),
			),
			'required'   => array( 'user_id', 'role' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'user_id'  => array( 'type' => 'integer' ),
				'role'     => array( 'type' => 'string' ),
				'verified' => array( 'type' => 'boolean' ),
			),
		),
		'execute_callback'    => 'wpagent_exec_set_user_role',
		'permission_callback' => 'wpagent_perm_promote_users',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => false,
				'destructive' => false,
				'idempotent'  => true,
			),
		),
	);
}

/**
 * Permission callback: der Aufrufer muss promote_users besitzen. Diese
 * Capability ist in einer Standardinstallation Administratoren vorbehalten;
 * der als Editor eingerichtete Companion-Bot besitzt sie nie.
 *
 * @param mixed $input Unused ability input.
 * @return bool
 */
function wpagent_perm_promote_users( $input = null ): bool {
	return current_user_can( 'promote_users' );
}

/**
 * Prüft, ob eine Rolle Administrator-äquivalente Rechte trägt: entweder
 * die Rolle "administrator" selbst, oder jede Rolle, deren Capability-Satz
 * manage_options enthält. Bewusst über die Capability statt nur über den
 * Rollen-Slug geprüft, damit auch eine individuell benannte Rolle mit
 * manage_options nicht am Gate vorbeikommt.
 *
 * @param string $role
 * @return bool
 */
function wpagent_role_grants_admin_capability( string $role ): bool {
	if ( 'administrator' === $role ) {
		return true;
	}
	$role_object = get_role( $role );
	if ( ! $role_object instanceof WP_Role ) {
		return false;
	}
	return ! empty( $role_object->capabilities['manage_options'] );
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_set_user_role( $input ) {
	$input   = is_array( $input ) ? $input : array();
	$user_id = (int) ( $input['user_id'] ?? 0 );
	$user    = get_userdata( $user_id );

	if ( ! $user ) {
		return new WP_Error( 'wpagent_no_user', __( 'Nutzer nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( ! current_user_can( 'promote_users' ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}

	$role = (string) ( $input['role'] ?? '' );
	if ( '' === $role || ! wp_roles()->is_role( $role ) ) {
		return new WP_Error(
			'wpagent_bad_role',
			sprintf( __( 'Ungültige Rolle "%s".', 'wp-agent-companion' ), $role ),
			array( 'status' => 400 )
		);
	}

	if ( 1 === $user_id ) {
		return new WP_Error(
			'wpagent_protected_user',
			__( 'User-ID 1 ist geschützt und darf nicht geändert werden.', 'wp-agent-companion' ),
			array( 'status' => 400 )
		);
	}
	if ( get_current_user_id() === $user_id ) {
		return new WP_Error(
			'wpagent_protected_user',
			__( 'Der aufrufende Account darf seine eigene Rolle nicht ändern.', 'wp-agent-companion' ),
			array( 'status' => 400 )
		);
	}

	$i_grant_admin = ! empty( $input['i_grant_admin'] );
	if ( wpagent_role_grants_admin_capability( $role ) && ! $i_grant_admin ) {
		return new WP_Error(
			'wpagent_admin_grant_required',
			__( 'Diese Rolle besitzt Administrator-Rechte (manage_options). Setze i_grant_admin=true, um das explizit zu bestätigen.', 'wp-agent-companion' ),
			array( 'status' => 403 )
		);
	}

	$res = wp_update_user(
		array(
			'ID'   => $user_id,
			'role' => $role,
		)
	);
	if ( is_wp_error( $res ) ) {
		return $res;
	}

	$fresh    = get_userdata( $user_id );
	$verified = ( $fresh instanceof WP_User ) && ( 1 === count( $fresh->roles ) ) && in_array( $role, $fresh->roles, true );

	return array(
		'user_id'  => $user_id,
		'role'     => $fresh ? (string) ( $fresh->roles[0] ?? '' ) : '',
		'verified' => $verified,
	);
}
