#!/usr/bin/perl
use feature "switch";
use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use Time::Piece;

sub processMembers;
sub processGroups;

local $::SERVICE_NAME = "ad_mu";
local $::PROTOCOL_VERSION = "3.0.0";
my $SCRIPT_VERSION = "3.0.0";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".ldif";
my $fileNameGroups = "$DIRECTORY/$::SERVICE_NAME"."_groups";  # printed for each OU
my $fileNameOus = "$DIRECTORY/$::SERVICE_NAME"."_ous.ldif";
my $baseDnFileName = "$DIRECTORY/baseDN";
my $baseDnFileNameGroups = "$DIRECTORY/baseDNGroups";
my $fileNameFID = "$DIRECTORY/facilityId";

my $data = perunServicesInit::getHashedDataWithGroups;

# Constants
our $A_F_BASE_DN;  *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:adBaseDN';
our $A_F_GROUP_BASE_DN;  *A_F_GROUP_BASE_DN = \'urn:perun:facility:attribute-def:def:adGroupBaseDN';
our $A_F_DOMAIN;  *A_F_DOMAIN = \'urn:perun:facility:attribute-def:def:adDomain';
our $A_F_AZURE_DOMAIN;  *A_F_AZURE_DOMAIN = \'urn:perun:facility:attribute-def:def:adAzureDomain';
our $A_F_UAC;  *A_F_UAC = \'urn:perun:facility:attribute-def:def:adUAC';
our $A_F_ID;  *A_F_ID = \'urn:perun:facility:attribute-def:core:id';
our $A_F_O365_ALL_MAIN_LICENSES; *A_F_O365_ALL_MAIN_LICENSES = \'urn:perun:facility:attribute-def:def:o365AllMainLicenses';
our $A_F_O365_USED_MAIN_LICENSES; *A_F_O365_USED_MAIN_LICENSES = \'urn:perun:facility:attribute-def:def:o365UsedMainLicenses';

# OU, Group
our $A_R_OU_NAME;  *A_R_OU_NAME = \'urn:perun:resource:attribute-def:def:adOuName';

our $A_G_AD_NAME;  *A_G_AD_NAME = \'urn:perun:group:attribute-def:def:adName:o365mu';

our $A_G_AD_DISPLAY_NAME;  *A_G_AD_DISPLAY_NAME = \'urn:perun:group:attribute-def:virt:adDisplayName:o365mu';

our $A_G_R_msExchRequireAuthToSendTo;  *A_G_R_msExchRequireAuthToSendTo = \'urn:perun:group_resource:attribute-def:def:adRequireSenderAuthenticationEnabled';
our $A_G_AD_EMAIL_ADDRESSES;  *A_G_AD_EMAIL_ADDRESSES = \'urn:perun:group:attribute-def:def:o365EmailAddresses:o365mu';

# User/member attributes
our $A_FIRST_NAME;  *A_FIRST_NAME = \'urn:perun:user:attribute-def:core:firstName';
our $A_LAST_NAME;  *A_LAST_NAME = \'urn:perun:user:attribute-def:core:lastName';
our $A_U_MAILS;  *A_U_MAILS = \'urn:perun:user:attribute-def:def:o365EmailAddresses:mu';
our $A_LOGIN; *A_LOGIN = \'urn:perun:user_facility:attribute-def:virt:login';
our $A_M_G_O365_SEND_ON_BEHALF; *A_M_G_O365_SEND_ON_BEHALF = \'urn:perun:member_group:attribute-def:virt:o365SendOnBehalf';
our $A_U_F_O365_PREFERRED_LANGUAGE; *A_U_F_O365_PREFERRED_LANGUAGE = \'urn:perun:user_facility:attribute-def:def:o365PreferredLanguage';
our $A_U_F_O365_LICENCE; *A_U_F_O365_LICENCE = \'urn:perun:user_facility:attribute-def:def:o365Licence';
our $A_MEMBER_STATUS; *A_MEMBER_STATUS = \'urn:perun:member:attribute-def:core:status';

our $MEMBERS;	*MEMBERS = \'MEMBERS';
our $STATUS_VALID;                   *STATUS_VALID =                   \'VALID';
our $CLOUD_EXTENSION_A_2; *CLOUD_EXTENSION_A_2 = \'msDS-cloudExtensionAttribute2';

# GATHER USERS
my $users;  # $users->{$login}->{ATTR} = $attrValue;
my $groups; # $groups->{ouName}->{group DN}->{ATTR} = $attrValue;
my $ous; # $ous->{ouName} = 1;

# CHECK ON FACILITY ATTRIBUTES
my $facilityId = $data->getFacilityId();

if (!defined($data->getFacilityAttributeValue( attrName => $A_F_BASE_DN ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_GROUP_BASE_DN ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_DOMAIN ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_UAC ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_AZURE_DOMAIN ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_O365_ALL_MAIN_LICENSES ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_O365_USED_MAIN_LICENSES ))) {
	exit 1;
}


#
# PRINT BASE_DN FILEs
my $domain = $data->getFacilityAttributeValue( attrName => $A_F_DOMAIN );
my $azureDomain = $data->getFacilityAttributeValue( attrName => $A_F_AZURE_DOMAIN );
my $adUac = $data->getFacilityAttributeValue( attrName => $A_F_UAC );
my @allMainLicences = @{$data->getFacilityAttributeValue( attrName => $A_F_O365_ALL_MAIN_LICENSES )};
my @usedMainLicences = @{$data->getFacilityAttributeValue( attrName => $A_F_O365_USED_MAIN_LICENSES )};
my $baseDNUsers = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN );
my $baseDNGroups = $data->getFacilityAttributeValue( attrName => $A_F_GROUP_BASE_DN );

open FILE,">:encoding(UTF-8)","$baseDnFileName" or die "Cannot open $baseDnFileName: $! \n";
print FILE $baseDNUsers;
close(FILE);

open FILE,">:encoding(UTF-8)","$baseDnFileNameGroups" or die "Cannot open $baseDnFileNameGroups: $! \n";
print FILE $baseDNGroups;
close(FILE);

open FILE,">:encoding(UTF-8)","$fileNameFID" or die "Cannot open $fileNameFID: $! \n";
print FILE $facilityId;
close(FILE);

#
# AGGREGATE DATA ABOUT MEMBERS from all resources
#
foreach my $resourceId ( $data->getResourceIds() ) {

	processMembers($resourceId);
}

#
# AGGREGATE DATA ABOUT GROUPS from OU resources
#
foreach my $resourceId ( $data->getResourceIds() ) {

	my $ouName = $data->getResourceAttributeValue( resource => $resourceId, attrName => $A_R_OU_NAME );

	if ($ouName) {

		# groups resources (per-each end-service)
		foreach my $groupId ( $data->getGroupIdsForResource( resource => $resourceId ) ) {
			processGroups($ouName, $groupId, $resourceId);
		}

		# Store to the list of OUs
		$ous->{$ouName} = 1;

	}

}

#
# Process members
#
sub processMembers() {

	my $resourceId = shift;

	for my $memberId ( $data->getMemberIdsForResource( resource => $resourceId )) {

		my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_LOGIN );

		#if ($memberAttributes{$A_MEMBER_STATUS} eq $STATUS_VALID and $login =~ /^[0-9]+$/) { #FIXME
		if ($login =~ /^[0-9]+$/) {

			# store users relations
			my $dn = "CN=" . $login . "," . $baseDNUsers;

			# store standard user attributes
			$users->{$login}->{"DN"} = $dn;
			$users->{$login}->{$A_FIRST_NAME} = $data->getUserAttributeValue( member => $memberId, attrName => $A_FIRST_NAME );
			$users->{$login}->{$A_LAST_NAME} = $data->getUserAttributeValue( member => $memberId, attrName => $A_LAST_NAME );
			$users->{$login}->{$A_U_MAILS} = $data->getUserAttributeValue( member => $memberId, attrName => $A_U_MAILS );
			$users->{$login}->{$A_U_F_O365_PREFERRED_LANGUAGE} = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_U_F_O365_PREFERRED_LANGUAGE );

			## process user main licence
			my $userLicence = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_U_F_O365_LICENCE );
			if (defined $userLicence && $userLicence ne "0") {

				my $mainLicenceKey = "CN=O365Lic_" . $userLicence . "_group.muni.cz,OU=licenses," . $baseDNGroups;
				my $mainStudent2LicenceKey = "CN=O365Lic_A3s-2_group.muni.cz,OU=licenses," . $baseDNGroups;

				# In case group for student licenses exeed maximum capacity (49999), rest of the users need to be added to A3s-2 group. 
				if ($userLicence eq "A3s" and keys %{$groups->{"licenses"}->{$mainLicenceKey}->{$MEMBERS}} >= 49999) {
					$groups->{"licenses"}->{$mainStudent2LicenceKey}->{$MEMBERS}->{"CN=".$login.",".$baseDNUsers} = 1;
				} else {
					$groups->{"licenses"}->{$mainLicenceKey}->{$MEMBERS}->{"CN=".$login.",".$baseDNUsers} = 1;
				}
				$users->{$login}->{$CLOUD_EXTENSION_A_2} = "TRUE";
			} else {
				$users->{$login}->{$CLOUD_EXTENSION_A_2} = "FALSE";
			}
		}
	}

}

#
# Process groups and its members
#
sub processGroups {

	my $ouName = shift;
	my $groupId = shift;
	my $resourceId = shift;

	my $adName = $data->getGroupAttributeValue( group => $groupId, attrName => $A_G_AD_NAME );
	my $displayName = $data->getGroupAttributeValue( group => $groupId, attrName => $A_G_AD_DISPLAY_NAME );
	#if displayName is missing, use adName instead
	my $finalDisplayName = $displayName ? $displayName : $adName;

	# process normal groups
	unless ($ouName eq 'licenses') {

		if (defined $adName) {
			# create group entry
			my $key = "CN=".$adName."_group.muni.cz,OU=".$ouName.",".$baseDNGroups;

			$groups->{$ouName}->{$key}->{$A_G_AD_NAME} = $adName;
			$groups->{$ouName}->{$key}->{$A_G_AD_DISPLAY_NAME} = $finalDisplayName;
			$groups->{$ouName}->{$key}->{$A_G_R_msExchRequireAuthToSendTo} = $data->getGroupResourceAttributeValue( group => $groupId, resource => $resourceId, attrName => $A_G_R_msExchRequireAuthToSendTo );
			$groups->{$ouName}->{$key}->{$A_G_AD_EMAIL_ADDRESSES} = $data->getGroupAttributeValue( group => $groupId, attrName => $A_G_AD_EMAIL_ADDRESSES );
			# resolve groups members
			for my $memberId ( $data->getMemberIdsForResourceAndGroup( resource => $resourceId, group => $groupId )) {
				my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_LOGIN );
				#if ($memberAttributes{$A_MEMBER_STATUS} eq $STATUS_VALID and $login =~ /^[0-9]+$/) {  FIXME
				if ($login =~ /^[0-9]+$/) {
					$groups->{$ouName}->{$key}->{$MEMBERS}->{"CN=".$login.",".$baseDNUsers} = 1;
					# get o365SendOnBehalf for non-license groups
					my $sendOnBehalf = $data->getMemberGroupAttributeValue( member => $memberId, group => $groupId, attrName => $A_M_G_O365_SEND_ON_BEHALF );
					if (defined $sendOnBehalf and $sendOnBehalf == 1) {
						#FIXME ukladat DN
						#$groups->{$ouName}->{$key}->{$A_M_G_O365_SEND_ON_BEHALF}->{$login."\@muni.cz"} = 1;
						$groups->{$ouName}->{$key}->{$A_M_G_O365_SEND_ON_BEHALF}->{"CN=".$login.",".$baseDNUsers} = 1;
						
					}
				}
			}
		}
	} else {
		# handle license groups for all main licenses
		for my $mainLicenceName (@usedMainLicences) {

			# create group entry
			my $key = "CN=O365Lic_" . $mainLicenceName . "_" . $adName . "_group.muni.cz,OU=" . $ouName . "," . $baseDNGroups;

			$groups->{$ouName}->{$key}->{$A_G_AD_NAME} =			"O365Lic_" . $mainLicenceName . "_" . $adName;
			$groups->{$ouName}->{$key}->{$A_G_AD_DISPLAY_NAME} =	"O365Lic_" . $mainLicenceName . "_" . $finalDisplayName;
		}

		# resolve groups members
		for my $memberId ( $data->getMemberIdsForResourceAndGroup( resource => $resourceId, group => $groupId )) {
			my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_LOGIN );
			my $userLicence = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_U_F_O365_LICENCE );

			#if ($memberAttributes{$A_MEMBER_STATUS} eq $STATUS_VALID and $login =~ /^[0-9]+$/) { FIXME
			if ($login =~ /^[0-9]+$/ && defined $userLicence && $userLicence ne "0" && $userLicence ne "Abs") {

				my $aditionalLicenceKey = "CN=O365Lic_" . $userLicence . "_" . $adName . "_group.muni.cz,OU=" . $ouName . "," . $baseDNGroups;

				$groups->{$ouName}->{$aditionalLicenceKey}->{$MEMBERS}->{"CN=".$login.",".$baseDNUsers} = 1;
			}
		}

	}

	# process all sub-groups
	#foreach my $subGroupData ($subGroupsElement->getChildElements) {
	#	processGroups($ouName, $subGroupData);
	#}

}

####################
#
# CREATE SPECIFIC LICENSE GROUPS
#
####################
foreach my $mainLicenceName (@allMainLicences) {
	$groups->{"licenses"}->{"CN=O365Lic_" . $mainLicenceName . "_group.muni.cz,OU=licenses," . $baseDNGroups}->{$A_G_AD_NAME} = "O365Lic_" . $mainLicenceName;
	$groups->{"licenses"}->{"CN=O365Lic_" . $mainLicenceName . "_group.muni.cz,OU=licenses," . $baseDNGroups}->{$A_G_AD_DISPLAY_NAME} = "O365Lic_" . $mainLicenceName;
}

############################
#
# PRINT OUs LDIF
#
############################
open FILE,">:encoding(UTF-8)","$fileNameOus" or die "Cannot open $fileNameOus: $! \n";
my @ouKeys = sort keys %{$ous};
for my $key (@ouKeys) {

	# we create OUs only in groups section !!
	print FILE "dn: OU=" . $key . "," . $baseDNGroups . "\n";
	print FILE "ou: " . $key . "\n";
	print FILE "objectclass: top\n";
	print FILE "objectclass: organizationalUnit\n";

	# there must be empty line after each entry
	print FILE "\n";

}

close FILE;


#############################
#
# PRINT GROUPs LDIF
#
#############################

my @groupOus = sort keys %{$ous};
for my $ouKey (@groupOus) {

	open FILE,">:encoding(UTF-8)","$fileNameGroups\_$ouKey.ldif" or die "Cannot open $fileNameGroups\_$ouKey.ldif: $! \n";

	if (defined $groups->{$ouKey}) {

		my $ouGroups = $groups->{$ouKey};
		my @groupKeys = sort keys %{$ouGroups};
		for my $key (@groupKeys) {

			print FILE "dn: " . $key . "\n";
			print FILE "cn: " . $groups->{$ouKey}->{$key}->{$A_G_AD_NAME} . "_group.muni.cz\n";
			print FILE "samAccountName: " . $groups->{$ouKey}->{$key}->{$A_G_AD_NAME} . "_" . $ouKey . "\n";
			print FILE "displayName: " . $groups->{$ouKey}->{$key}->{$A_G_AD_DISPLAY_NAME} . "\n";
			print FILE "MailNickName: " . $groups->{$ouKey}->{$key}->{$A_G_AD_NAME} . "\n";
			print FILE "extensionAttribute1: TRUE\n";  # group exists (set to false by send script instead of deletion, hence we must set it, if group is recreated with same name).
			my $msExchVal = $groups->{$ouKey}->{$key}->{$A_G_R_msExchRequireAuthToSendTo};
			print FILE "msExchRequireAuthToSendTo: " . ((defined $msExchVal and $msExchVal == 1) ? 'TRUE' : 'FALSE') . "\n";

			# get proxy addresses
			my $proxyAddresses = ($groups->{$ouKey}->{$key}->{$A_G_AD_EMAIL_ADDRESSES}) ? $groups->{$ouKey}->{$key}->{$A_G_AD_EMAIL_ADDRESSES} : ();

			my $first = 0;
			my $mail;
			foreach my $proxyAddress (@$proxyAddresses) {
				if ($proxyAddress) {
					if ($first == 0) {
						print FILE "ProxyAddresses: SMTP:" . $proxyAddress . "\n";
						$mail = $proxyAddress;
						$first = 1;
					} else {
						print FILE "ProxyAddresses: smtp:" . $proxyAddress . "\n";
					}
				}
			}

			# print first of ProxyAddresses to mail too:
			if ($mail) {
				print FILE "mail: " . $mail . "\n";
			}

			if (defined $groups->{$ouKey}->{$key}->{$A_M_G_O365_SEND_ON_BEHALF}) {
				# get public delegates only when they are there
				my @publicDelegates = sort keys %{$groups->{$ouKey}->{$key}->{$A_M_G_O365_SEND_ON_BEHALF}};
				for my $pdVal (@publicDelegates) {
					print FILE "publicDelegates: " . $pdVal . "\n";
				}
			}

			print FILE "objectClass: group\n";
			print FILE "objectClass: top\n";

			if (defined $groups->{$ouKey}->{$key}->{$MEMBERS}) {
				# print members only when they are there
				my @groupMembers = sort keys %{$groups->{$ouKey}->{$key}->{$MEMBERS}};
				for my $member (@groupMembers) {
					print FILE "member: " . $member . "\n";
				}
			}

			# there must be empty line after each entry
			print FILE "\n";

		}

	}

	close FILE;

}

##################################
#
# PRINT USERs LDIF
#
##################################
open FILE,">:encoding(UTF-8)","$fileName" or die "Cannot open $fileName: $! \n";

# FOR EACH USER ON FACILITY
my @logins = sort keys %{$users};
for my $login (@logins) {

	# Localy defined attributes
	my $userPrincipalName = "$login\@$domain";
	my $samAccountName = $login;

	# print attributes, which are never empty
	print FILE "dn: " . $users->{$login}->{"DN"} . "\n";
	print FILE "cn: " . $login . "\n";

	# skip attributes which are empty and LDAP can't handle it (FIRST_NAME, EMAIL)
	my $sn = $users->{$login}->{$A_LAST_NAME};
	my $givenName = $users->{$login}->{$A_FIRST_NAME};

	# print display name from firstName/lastName only
	my $printedDisplayName = undef;
	if (defined $givenName and length $givenName and defined $sn and length $sn) {
		$printedDisplayName = $givenName . " " . $sn;
	} elsif (defined $givenName and length $givenName and !(defined $sn and length $sn)) {
		$printedDisplayName = $givenName;
	} elsif (!(defined $givenName and length $givenName) and defined $sn and length $sn) {
		$printedDisplayName = $sn;
	}
	if (defined $printedDisplayName and length $printedDisplayName) {
		print FILE "displayName: " . $printedDisplayName . "\n";
	}

	if (defined $sn and length $sn) {
		print FILE "sn: " . $sn . "\n";
	}
	if (defined $givenName and length $givenName) {
		print FILE "givenName: " . $givenName . "\n";
	}

	if (defined $samAccountName and length $samAccountName) {
		print FILE "samAccountName: " . $samAccountName . "\n";
	}
	if (defined $userPrincipalName and length $userPrincipalName) {
		print FILE "userPrincipalName: " . $userPrincipalName . "\n";
	}
	print FILE "MailNickName: " . $login . "\n";

	# get proxy addresses of user
	my $proxyAddresses = ($users->{$login}->{$A_U_MAILS}) ? $users->{$login}->{$A_U_MAILS} : ();

	my $first = 0;
	my $mail;
	foreach my $proxyAddress (@$proxyAddresses) {
		if ($proxyAddress) {
			if ($first == 0) {
				print FILE "ProxyAddresses: SMTP:" . $proxyAddress . "\n";
				$mail = $proxyAddress;
				$first = 1;
			} else {
				print FILE "ProxyAddresses: smtp:" . $proxyAddress . "\n";
			}
		}
	}

	if (defined $mail and length $mail) {
		print FILE "mail: " . $mail . "\n";
	}

	print FILE "c: CZ\n";

	if ($users->{$login}->{$A_U_F_O365_PREFERRED_LANGUAGE}) {
		print FILE "preferredLanguage: " . $users->{$login}->{$A_U_F_O365_PREFERRED_LANGUAGE} . "\n";
	}

	print FILE "targetaddress: " . $login . '@mail.muni.cz' . "\n";

	print FILE "msDS-cloudExtensionAttribute1: " . $login . '@' . $azureDomain . "\n";

	print FILE "msDS-cloudExtensionAttribute2: " . $users->{$login}->{$CLOUD_EXTENSION_A_2} . "\n";

	print FILE "msDS-cloudExtensionAttribute3: TRUE\n";

	print FILE "userAccountControl: $adUac\n";

	# print classes
	print FILE "objectclass: top\n";
	print FILE "objectclass: person\n";
	print FILE "objectclass: user\n";
	print FILE "objectclass: organizationalPerson\n";

	# There MUST be an empty line after each entry, so entry sorting and diff works on slave part
	print FILE "\n";

}

close(FILE);

perunServicesInit::finalize;
