#
# midPoint utility functions
#
use LWP::UserAgent;
use HTTP::Request;
use XML::Simple;
use XML::LibXML;

# mp_find_user_oid_by_name
#
# Parameters:
#   $prefs_ref     - reference to prefs hash
#   $midpoint_name - user name to search for (OSU UID)
#
sub mp_find_user_oid_by_name {
	my ($prefs_ref, $midpoint_name) = @_;

	my $query_xml = <<"END_QUERY_XML";
<?xml version="1.0"?>
<q:query xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3">
	<q:filter>
		<q:equal>
			<q:matching>polyStringNorm</q:matching>
			<q:path>name</q:path>
			<q:value>$midpoint_name</q:value>
		</q:equal>
	</q:filter>
</q:query>
END_QUERY_XML

	my $xml = new XML::Simple;
	my $response = &mp_call_api($prefs_ref, 'POST', '/midpoint/ws/rest/users/search', $query_xml);
	if ($response->is_success) {
		my $data = $xml->XMLin($response->decoded_content);
		if (defined ($data->{'apti:object'}->{'oid'})) {
			return $data->{'apti:object'}->{'oid'};
		} else {
			return "Error: mp_find_user_oid_by_name - midpoint name '$midpoint_name' not found";
		}
	} else {
		return "Error: mp_find_user_oid_by_name - " . $response->status_line;
	}
}

# mp_find_user_oid_by_username
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $username  - username to search for (ONID username)
#
sub mp_find_user_oid_by_username {
	my ($prefs_ref, $username) = @_;

	my $query_xml = <<"END_QUERY_XML";
<?xml version="1.0"?>
<q:query xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3">
	<q:filter>
		<q:equal>
			<q:path>extension/username</q:path>
			<q:value>$username</q:value>
		</q:equal>
	</q:filter>
</q:query>
END_QUERY_XML

	my $xml = new XML::Simple;
	my $response = &mp_call_api($prefs_ref, 'POST', '/midpoint/ws/rest/users/search', $query_xml);
	if ($response->is_success) {
		my $data = $xml->XMLin($response->decoded_content);
		if (defined ($data->{'apti:object'}->{'oid'})) {
			return $data->{'apti:object'}->{'oid'};
		} else {
			return "Error: mp_find_user_oid_by_username - username '$username' not found";
		}
	} else {
		return "Error: mp_find_user_oid_by_username - " . $response->status_line;
	}
}

# mp_find_user_oid_by_osuid
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $osuid     - OSU ID# to search for
#
sub mp_find_user_oid_by_osuid {
	my ($prefs_ref, $osuid) = @_;

	my $query_xml = <<"END_QUERY_XML";
<?xml version="1.0"?>
<q:query xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3">
	<q:filter>
		<q:equal>
			<q:path>extension/idBannerOSUID</q:path>
			<q:value>$osuid</q:value>
		</q:equal>
	</q:filter>
</q:query>
END_QUERY_XML

	my $xml = new XML::Simple;
	my $response = &mp_call_api($prefs_ref, 'POST', '/midpoint/ws/rest/users/search', $query_xml);
	if ($response->is_success) {
		my $data = $xml->XMLin($response->decoded_content);
		if (defined ($data->{'apti:object'}->{'oid'})) {
			return $data->{'apti:object'}->{'oid'};
		} else {
			return "Error: mp_find_user_oid_by_osuid - OSU ID '$osuid' not found";
		}
	} else {
		return "Error: mp_find_user_oid_by_osuid - " . $response->status_line;
	}
}

# mp_find_role_oid
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $role_name - role name to search for
#
sub mp_find_role_oid {
	my ($prefs_ref, $role_name) = @_;

	my $query_xml = <<"END_QUERY_XML";
<?xml version="1.0"?>
<q:query xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3">
	<q:filter>
		<q:equal>
			<q:matching>polyStringNorm</q:matching>
			<q:path>name</q:path>
			<q:value>$role_name</q:value>
		</q:equal>
	</q:filter>
</q:query>
END_QUERY_XML

	my $xml = new XML::Simple;
	my $response = &mp_call_api($prefs_ref, 'POST', '/midpoint/ws/rest/roles/search', $query_xml);
	if ($response->is_success) {
		my $data = $xml->XMLin($response->decoded_content);
		if (defined ($data->{'apti:object'}->{'oid'})) {
			return $data->{'apti:object'}->{'oid'};
		} else {
			return "Error: mp_find_role_oid - role '$role_name' not found";
		}
	} else {
		return "Error: mp_find_role_oid - " . $response->status_line;
	}
}

# mp_find_task_oid
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $task_name - task name to search for
#
sub mp_find_task_oid {
	my ($prefs_ref, $task_name) = @_;

	my $query_xml = <<"END_QUERY_XML";
<?xml version="1.0"?>
<q:query xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3">
	<q:filter>
		<q:equal>
			<q:matching>polyStringNorm</q:matching>
			<q:path>name</q:path>
			<q:value>$task_name</q:value>
		</q:equal>
	</q:filter>
</q:query>
END_QUERY_XML

	my $xml = new XML::Simple;
	my $response = &mp_call_api($prefs_ref, 'POST', '/midpoint/ws/rest/tasks/search', $query_xml);
	if ($response->is_success) {
		my $data = $xml->XMLin($response->decoded_content);
		if (defined ($data->{'apti:object'}->{'oid'})) {
			return $data->{'apti:object'}->{'oid'};
		} else {
			return "Error: mp_find_task_oid - task '$task_name' not found";
		}
	} else {
		return "Error: mp_find_task_oid - " . $response->status_line;
	}
}

# mp_add_role_by_oid_to_user
#
# Parameters:
#   $prefs_ref     - reference to prefs hash
#   $user_oid      - OID of target user
#   $role_oids_ref - reference to array of role OIDs to add
#
sub mp_add_role_by_oid_to_user {
	my ($prefs_ref, $user_oid, $role_oids_ref) = @_;

	my $mod_xml = <<"END_MOD_XML";
<objectModification
	xmlns='http://midpoint.evolveum.com/xml/ns/public/common/api-types-3'
	xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'
	xmlns:t="http://prism.evolveum.com/xml/ns/public/types-3">
	<itemDelta>
		<t:modificationType>add</t:modificationType>
		<t:path>c:assignment</t:path>
END_MOD_XML
	foreach my $role_oid (@$role_oids_ref) {
		$mod_xml .= <<"END_MOD_XML";
		<t:value>
			<c:targetRef oid="$role_oid" type="c:RoleType" />
		</t:value>
END_MOD_XML
	}
	$mod_xml .= <<"END_MOD_XML";
	</itemDelta>
</objectModification>
END_MOD_XML

	my $response = &mp_call_api($prefs_ref, 'PATCH', "/midpoint/ws/rest/users/$user_oid", $mod_xml);
	if ($response->is_success) {
		return "SUCCESS";
	} else {
		return "Error: mp_add_role_by_oid_to_user - " . $response->status_line;
	}
}

# mp_add_role_by_name_to_user
#
# Parameters:
#   $prefs_ref      - reference to prefs hash
#   $user_oid       - OID of target user
#   $role_names_ref - reference to array of names of roles to add
#
sub mp_add_role_by_name_to_user {
	my ($prefs_ref, $user_oid, $role_names_ref) = @_;

	my @role_oids = ();
	foreach my $role_name (@$role_names_ref) {
		my $role_oid = &mp_find_role_oid($prefs_ref, $role_name);
		if ($role_oid =~ /^Error:/) {
			return "Error: mp_add_role_by_name_to_user - an error occurred while finding role oid for name '" . $role_name . "'";
		}
		push @role_oids, $role_oid;
	}

	return &mp_add_role_by_oid_to_user($prefs_ref, \@role_oids, $user_oid);
}

# mp_remove_role_by_oid_from_user
#
# Parameters:
#   $prefs_ref     - reference to prefs hash
#   $user_oid      - OID of target user
#   $role_oids_ref - reference to array of role OIDs to remove
#
sub mp_remove_role_by_oid_from_user {
	my ($prefs_ref, $user_oid, $role_oids_ref) = @_;

	my $mod_xml = <<"END_MOD_XML";
<objectModification
	xmlns='http://midpoint.evolveum.com/xml/ns/public/common/api-types-3'
	xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'
	xmlns:t="http://prism.evolveum.com/xml/ns/public/types-3">
	<itemDelta>
		<t:modificationType>delete</t:modificationType>
		<t:path>c:assignment</t:path>
END_MOD_XML
	foreach my $role_oid (@$role_oids_ref) {
		$mod_xml .= <<"END_MOD_XML";
		<t:value>
			<c:targetRef oid="$role_oid" type="c:RoleType" />
		</t:value>
END_MOD_XML
	}
	$mod_xml .= <<"END_MOD_XML";
	</itemDelta>
</objectModification>
END_MOD_XML

	my $response = &mp_call_api($prefs_ref, 'PATCH', "/midpoint/ws/rest/users/$user_oid", $mod_xml);
	if ($response->is_success) {
		return "SUCCESS";
	} else {
		return "Error: mp_remove_role_by_oid_from_user - " . $response->status_line;
	}
}

# mp_remove_role_by_name_from_user
#
# Parameters:
#   $prefs_ref      - reference to prefs hash
#   $user_oid       - OID of target user
#   $role_names_ref - reference to array of names of roles to remove
#
sub mp_remove_role_by_name_from_user {
	my ($prefs_ref, $user_oid, $role_names_ref) = @_;

	my @role_oids = ();
	foreach my $role_name (@$role_names_ref) {
		my $role_oid = &mp_find_role_oid($prefs_ref, $role_name);
		if ($role_oid =~ /^Error:/) {
			return "Error: mp_remove_role_by_name_from_user - an error occurred while finding role oid for name '" . $role_name . "'";
		}
		push @role_oids, $role_oid;
	}

	return &mp_remove_role_by_oid_from_user($prefs_ref, \@role_oids, $user_oid);
}

# mp_add_prior_username
#
# Parameters:
#   $prefs_ref      - reference to prefs hash
#   $user_oid       - oid of user to edit
#   $prior_username - old username to store in priorUsernames
#
sub mp_add_prior_username {
	my ($prefs_ref, $user_oid, $prior_username) = @_;

	my $mod_xml = <<"END_MOD_XML";
<?xml version="1.0" encoding="UTF-8"?>
<objectModification
	xmlns='http://midpoint.evolveum.com/xml/ns/public/common/api-types-3'
	xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'
	xmlns:t="http://prism.evolveum.com/xml/ns/public/types-3">
	<t:itemDelta>
		<t:modificationType>add</t:modificationType>
		<t:path>extension/priorUsernames</t:path>
		<t:value>$prior_username</t:value>
	</t:itemDelta>
</objectModification>
END_MOD_XML

	my $response = &mp_call_api($prefs_ref, 'PATCH', "/midpoint/ws/rest/users/$user_oid", $mod_xml);
	if ($response->is_success) {
		return "SUCCESS";
	} else {
		return "Error: mp_add_prior_username - " . $response->status_line;
	}
}

# mp_change_username
#
# Parameters:
#   $prefs_ref    - reference to prefs hash
#   $old_username - user's current username
#   $new_username - user's new username
#
sub mp_change_username {
	my ($prefs_ref, $old_username, $new_username) = @_;

	my $user_oid = &mp_find_user_oid_by_username($prefs_ref, $old_username);
	if ($user_oid =~ /^Error:/) {
		return "Error: mp_change_username - $user_oid";
	}

	my $mod_xml = <<"END_MOD_XML";
<?xml version="1.0" encoding="UTF-8"?>
<objectModification
	xmlns='http://midpoint.evolveum.com/xml/ns/public/common/api-types-3'
	xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'
	xmlns:t="http://prism.evolveum.com/xml/ns/public/types-3">
	<t:itemDelta>
		<t:modificationType>replace</t:modificationType>
		<t:path>extension/username</t:path>
		<t:value>$new_username</t:value>
	</t:itemDelta>
</objectModification>
END_MOD_XML

	my $response = &mp_call_api($prefs_ref, 'PATCH', "/midpoint/ws/rest/users/$user_oid", $mod_xml);
	if ($response->is_success) {
		my $rv = &mp_add_prior_username($prefs_ref, $user_oid, $old_username);
		if ($rv eq 'SUCCESS') {
			return 'SUCCESS';
		} else {
			return "Error: $rv";
		}
	} else {
		return "Error: mp_change_username - " . $response->status_line;
	}
}

# mp_get_user_extensions
#
# Parameters:
#   $prefs_ref     - reference to prefs hash
#   $user_oid      - OID of user to return
#   $user_data_ref - reference to hash to load with user data
#
sub mp_get_user_extensions {
	my ($prefs_ref, $user_oid, $user_data_ref) = @_;

	my $response = &mp_call_api($prefs_ref, 'GET', "/midpoint/ws/rest/users/$user_oid");
	if ($response->is_success) {
		my $dom = XML::LibXML->load_xml(string => $response->decoded_content, no_blanks => 1);

		my $ext_nodes = pop(@{$dom->findnodes('//c:extension')});
		foreach my $extension ($ext_nodes->childNodes) {
			$user_data_ref->{$extension->localname} = $extension->to_literal;
		}
		return 'SUCCESS';
	} else {
		return "Error: mp_get_user_object - " . $response->status_line;
	}
}

# mp_get_task_status
#
# Parameters:
#   $prefs_ref     - reference to prefs hash
#   $task_oid      - OID of task to check
#
sub mp_get_task_status {
	my ($prefs_ref, $task_oid) = @_;

	my $response = &mp_call_api($prefs_ref, "GET", "/midpoint/ws/rest/tasks/$task_oid");
	if ($response->is_success) {
		my $dom = XML::LibXML->load_xml(string => $response->decoded_content, no_blanks => 1);

		# Assume there is only 1 node
		my ($node) = $dom->findnodes('//c:executionStatus');
		my ($execstatus) = $node->childNodes();
		return $execstatus->to_literal;
	} else {
		return "Error: mp_get_task_status - " . $response->status_line;
	}
}

# mp_resume_task
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $task_oid  - OID of task to resume
#
sub mp_resume_task {
	my ($prefs_ref, $task_oid) = @_;

	my $response = &mp_call_api($prefs_ref, 'POST', "/midpoint/ws/rest/tasks/$task_oid/resume");
	if ($response->is_success) {
		return 'SUCCESS';
	} else {
		return "Error: mp_resume_task - " . $response->status_line;
	}
}

# mp_delete_user
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $user_oid  - OID of user to delete
#
sub mp_delete_user {
	my ($prefs_ref, $user_oid) = @_;

	my $response = &mp_call_api($prefs_ref, "DELETE", "/midpoint/ws/rest/users/$user_oid");
	if ($response->is_success) {
		return 'SUCCESS';
	} else {
		return "Error: mp_delete_user - " . $response->status_line;
	}
}

# mp_call_api
#
# Parameters:
#   $prefs_ref - reference to prefs hash
#   $method    - GET, POST, or PATCH
#   $endpoint  - midPoint REST API endpoint path
#   $payload   - data to send in POST requests
#
sub mp_call_api {
	my ($prefs_ref, $method, $endpoint, $payload) = @_;

	my $base_midpoint_url = 'https://' . $prefs_ref->{'midpoint_host'};

	my $ua = LWP::UserAgent->new;
	$ua->credentials($prefs_ref->{'midpoint_host'}, 'midpoint', $prefs_ref->{'midpoint_admin'}, $prefs_ref->{'midpoint_admin_password'});
	if ($method eq 'GET') {
		return $ua->get($base_midpoint_url . $endpoint);
	} elsif ($method eq 'POST') {
		return $ua->post($base_midpoint_url . $endpoint, Content_Type => 'application/xml', Content => $payload);
	} elsif ($method eq 'PATCH') {
		my $req = HTTP::Request->new('PATCH', $base_midpoint_url . $endpoint, [Content_Type => 'application/xml'], $payload);
		return $ua->request($req);
	} elsif ($method eq 'DELETE') {
		return $ua->delete($base_midpoint_url . $endpoint);
	} else {
		return "Error: invalid method - '$method'";
	}
}
1;
