#!/usr/bin/perl

use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use Perun::Agent;
use Date::Calc qw\Delta_Days\;
use open qw/:std :utf8/;
use JSON;
use Time::HiRes qw( gettimeofday );
use utf8;

local $::SERVICE_NAME = "o365_life_cycle_manager_mu";
local $::PROTOCOL_VERSION = "1.2.0";
my $SCRIPT_VERSION = "1.2.1";

#get system time in miliseconds before starting getting data
my $systemTimeInMillis = int (gettimeofday * 1000); 

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $data = perunServicesInit::getHashedDataWithGroups;

#forward declaration
sub processUsers;

#Headers
my $userIdHeader = "userId";
my $memberIdHeader = "memberId";
my $loginHeader = "uco";
my $affiliationsHeader = "affiliations";
my $preferredMailHeader = "preferredMail";
my $groupMembershipExpirationHeader = "groupMembershipExpiration";
my $o365AlumniAccountEnabledHeader = "o365AlumniAccountEnabled";
my $o365PrivilegedLicenceHeader = "o365PrivilegedLicence";
my $eligibleForO365AlumniLicenceHeader = "eligibleForO365AlumniLicence";
my $o365GracePeriodExtensionHeader = "o365GracePeriodExtension";
my $o365UserEmailAddressesHeader = "o365UserEmailAddresses";
my $o365PrimaryEmailAddressHeader = "o365PrimaryEmailAddress";
my $o365MailForwardHeader = "o365MailForward";
my $o365DeleteAccountRequestHeader = "o365DeleteAccountRequest";

#Constants
our $A_USER_LOGIN_MU;                 *A_USER_LOGIN_MU =                 \'urn:perun:user:attribute-def:def:login-namespace:mu';
our $A_USER_PREFERRED_MAIL;           *A_USER_PREFERRED_MAIL =           \'urn:perun:user:attribute-def:def:preferredMail';
our $A_RESOURCE_AFFILIATIONS;         *A_RESOURCE_AFFILIATIONS =         \'urn:perun:resource:attribute-def:def:affiliation';
our $A_GROUP_MEMBERSHIP_EXPIRATION;   *A_GROUP_MEMBERSHIP_EXPIRATION =   \'urn:perun:member_group:attribute-def:def:groupMembershipExpiration';
our $A_MEMBER_STATUS_IN_GROUP;        *A_MEMBER_STATUS_IN_GROUP =        \'urn:perun:member_group:attribute-def:virt:groupStatus';
our $A_USER_O365_USER_EMAILS_MU;      *A_USER_O365_USER_EMAILS_MU =      \'urn:perun:user:attribute-def:def:o365UserEmailAddresses:mu';
our $A_USER_O365_PRIMARY_EMAIL_MU;    *A_USER_O365_PRIMARY_EMAIL_MU =    \'urn:perun:user:attribute-def:def:o365PrimaryEmailAddress:mu';
our $A_USER_FAC_O365_MAIL_FORWARD;    *A_USER_FAC_O365_MAIL_FORWARD =    \'urn:perun:user_facility:attribute-def:def:o365MailForward';
our $A_USER_FAC_DEL_ACC_REQUEST;      *A_USER_FAC_DEL_ACC_REQUEST =      \'urn:perun:user_facility:attribute-def:def:o365DeleteAccountRequest';
our $A_USER_FAC_ALLUMNI_ACC_ENABLED;  *A_USER_FAC_ALLUMNI_ACC_ENABLED =  \'urn:perun:user_facility:attribute-def:def:o365AlumniAccountEnabled';
our $A_USER_FAC_PRIVILEGED_LICENCE;   *A_USER_FAC_PRIVILEGED_LICENCE =   \'urn:perun:user_facility:attribute-def:def:o365PrivilegedLicence';
our $A_USER_FAC_ELIGIBLE_FOR_LICENCE; *A_USER_FAC_ELIGIBLE_FOR_LICENCE = \'urn:perun:user_facility:attribute-def:def:eligibleForO365AlumniLicence';
our $A_USER_FAC_GRACE_PERIOD_EXT;     *A_USER_FAC_GRACE_PERIOD_EXT =     \'urn:perun:user_facility:attribute-def:def:o365GracePeriodExtension';

#it defines UNLIMITED expiration for purpose of this gen script
our $UNLIMITED_EXPIRATION = "UNLIMITED";
#it defines specific affilation or resource where we need to process expiration in groups
our $O365_GUEST_AFFILIATION = "o365guest";

my $userStruc = {};

#file to which we will export the data about users
my $file = $DIRECTORY . "/data.json";

#foreach resource in the structure
foreach my $resourceId ($data->getResourceIds()) {
	#defined affilation for whole resource
	my $affiliation = $data->getResourceAttributeValue( resource => $resourceId, attrName => $A_RESOURCE_AFFILIATIONS );

	#for each group assigned to the resource
	foreach my $groupId ($data->getGroupIdsForResource( resource => $resourceId )) {
		
		#and every member in such group
		foreach my $memberId ($data->getMemberIdsForResourceAndGroup( resource => $resourceId, group => $groupId )) {
			my $userId = $data->getUserIdForMember( member => $memberId );
			#skip expired membership in group
			my $statusInGroup = $data->getMemberGroupAttributeValue( member => $memberId, group => $groupId, attrName => $A_MEMBER_STATUS_IN_GROUP );
			next if $statusInGroup eq 'EXPIRED';
			my $expirationInGroup = $data->getMemberGroupAttributeValue( member => $memberId, group => $groupId, attrName => $A_GROUP_MEMBERSHIP_EXPIRATION );

			#process every member|group|resource combination one by one
			processUsers $affiliation, $expirationInGroup, $memberId;	
		}
	}
}

# PREPARE USERSDATA TO JSON
my @users;
foreach my $userId (sort keys %$userStruc) {
	my $user = {};
	#use *1 to force perl to use integer instead of strings
	$user->{$userIdHeader} = $userId * 1;
	$user->{$memberIdHeader} = $userStruc->{$userId}->{$memberIdHeader} * 1;
	$user->{$loginHeader} = $userStruc->{$userId}->{$loginHeader};
	$user->{$preferredMailHeader} = $userStruc->{$userId}->{$preferredMailHeader};
	$user->{$o365UserEmailAddressesHeader} = $userStruc->{$userId}->{$o365UserEmailAddressesHeader};
	$user->{$o365PrimaryEmailAddressHeader} = $userStruc->{$userId}->{$o365PrimaryEmailAddressHeader};
	$user->{$o365MailForwardHeader} = $userStruc->{$userId}->{$o365MailForwardHeader};
	$user->{$o365DeleteAccountRequestHeader} = $userStruc->{$userId}->{$o365DeleteAccountRequestHeader};
	$user->{$affiliationsHeader} = $userStruc->{$userId}->{$affiliationsHeader};
	$user->{$o365AlumniAccountEnabledHeader} = $userStruc->{$userId}->{$o365AlumniAccountEnabledHeader};
	$user->{$o365PrivilegedLicenceHeader} = $userStruc->{$userId}->{$o365PrivilegedLicenceHeader};
	$user->{$eligibleForO365AlumniLicenceHeader} = $userStruc->{$userId}->{$eligibleForO365AlumniLicenceHeader};
	$user->{$o365GracePeriodExtensionHeader} = $userStruc->{$userId}->{$o365GracePeriodExtensionHeader};
	my $groupExpiration = $userStruc->{$userId}->{$groupMembershipExpirationHeader};
	#UNLIMITED expiration and not defined expirations are for purpose of JSON same => null
	unless($groupExpiration) {
		$user->{$groupMembershipExpirationHeader} = undef;
	} elsif ($groupExpiration eq $UNLIMITED_EXPIRATION) {
		$user->{$groupMembershipExpirationHeader} = undef;
	} else {
		$user->{$groupMembershipExpirationHeader} = $groupExpiration;
	}
	push @users, $user;
}
my %usersFinal = ();
$usersFinal{'users'} = \@users;
$usersFinal{'dataGenerationTimestamp'} = $systemTimeInMillis;

# PRINT USERS TO JSON
open FILE_USERS,">$file" or die "Cannot open $file: $! \n";
binmode(FILE_USERS);
print FILE_USERS JSON::XS->new->utf8->pretty->encode(\%usersFinal);
close (FILE_USERS) or die "Cannot close $file: $! \n";

perunServicesInit::finalize;

##############################################################################
#   Only subs definitions down there
##############################################################################
## creates structure for users.json
sub processUsers {
	my ($affiliation, $expirationInGroup, $memberId) = @_;

	my $userId = $data->getUserIdForMember( member => $memberId );
	my $login = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_LOGIN_MU );
	#skip service accounts (login like 's-*')
	if( $login =~ /s-/ ) { return; }

	#if user was already processed in another group, we can skip same attributes
	if (exists $userStruc->{$userId}) {
		#all these operations need to have defined affiliation
		if($affiliation) {
			my @affiliations = uniqList ($affiliation, @{$userStruc->{$userId}->{$affiliationsHeader}});
			$userStruc->{$userId}->{$affiliationsHeader} = \@affiliations;

			#we need to calculate expriration for guest resources
			if($affiliation eq $O365_GUEST_AFFILIATION) {
				my $lastDefinedExpiration = $userStruc->{$userId}->{$groupMembershipExpirationHeader};
				#if this new expiration is empty, it means is unlimited and we can set it without any more checks
				if(!$expirationInGroup) {
					$userStruc->{$userId}->{$groupMembershipExpirationHeader} = $UNLIMITED_EXPIRATION;
				#if new is not empty and last is empty, we can set a new one instead
				} elsif (!$lastDefinedExpiration) {
					$userStruc->{$userId}->{$groupMembershipExpirationHeader} = $expirationInGroup;
				#if both are not empty, but last is unlimited, we can skip it, in other case we need to choose the biggest
				#for example: 1.1.2001 > 1.1.2000
				} elsif ($lastDefinedExpiration ne $UNLIMITED_EXPIRATION) {
					my ($lastYear, $lastMonth, $lastDay) = unpack "A4xA2xA2", $lastDefinedExpiration;
					my ($newYear, $newMonth, $newDay) = unpack "A4xA2xA2", $expirationInGroup;
					my $diff = Delta_Days($lastYear, $lastMonth, $lastDay, $newYear, $newMonth, $newDay);
					#change only if this new groupExpiration is bigger then the old one
					if($diff>0) {
						$userStruc->{$userId}->{$groupMembershipExpirationHeader} = $expirationInGroup;
					}
				}
			}
		}
	#this is the first time user as occured in any processed group so we need to create a new full record
	} else {
		my $preferredMail = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_PREFERRED_MAIL );
		my $o365AlumniAccountEnabled = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_FAC_ALLUMNI_ACC_ENABLED );
		my $o365GracePeriodExtension = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_FAC_GRACE_PERIOD_EXT );
		my $eligibleForO365AlumniLicence = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_FAC_ELIGIBLE_FOR_LICENCE );
		my $o365PrivilegedLicence = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_FAC_PRIVILEGED_LICENCE );
		my $o365UserEmailAddresses = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_O365_USER_EMAILS_MU );
		my $o365PrimaryEmailAddress = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_O365_PRIMARY_EMAIL_MU );
		my $o365MailForward = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_FAC_O365_MAIL_FORWARD );
		my $o365DeleteAccountRequest = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_FAC_DEL_ACC_REQUEST );
		$userStruc->{$userId}->{$memberIdHeader} = $memberId;
		$userStruc->{$userId}->{$loginHeader} = $login;
		$userStruc->{$userId}->{$preferredMailHeader} = $preferredMail;
		$userStruc->{$userId}->{$o365UserEmailAddressesHeader} = $o365UserEmailAddresses ? $o365UserEmailAddresses : [];
		$userStruc->{$userId}->{$o365PrimaryEmailAddressHeader} = $o365PrimaryEmailAddress;
		$userStruc->{$userId}->{$o365MailForwardHeader} = $o365MailForward;
		$userStruc->{$userId}->{$o365DeleteAccountRequestHeader} = $o365DeleteAccountRequest ? JSON::true : JSON::false;
		$userStruc->{$userId}->{$o365AlumniAccountEnabledHeader} = $o365AlumniAccountEnabled ? JSON::true : JSON::false;
		$userStruc->{$userId}->{$eligibleForO365AlumniLicenceHeader} = $eligibleForO365AlumniLicence ? JSON::true : JSON::false;
		$userStruc->{$userId}->{$o365PrivilegedLicenceHeader} = $o365PrivilegedLicence ? $o365PrivilegedLicence : 0;
		$userStruc->{$userId}->{$o365GracePeriodExtensionHeader} = $o365GracePeriodExtension;

		#we need to set correct group expiration if affiliation is for guests
		if($affiliation && $affiliation eq $O365_GUEST_AFFILIATION) {
			#if expiration is not empty, we will set it as is
			if($expirationInGroup) {
				$userStruc->{$userId}->{$groupMembershipExpirationHeader} = $expirationInGroup;
			#if expiration is empty, we will use constat instead to be able to recognize between undef and unlimited there
			} else {
				$userStruc->{$userId}->{$groupMembershipExpirationHeader} = $UNLIMITED_EXPIRATION;
			}
		#if this is not guest affiliation, we can set undef instead
		} else {
			$userStruc->{$userId}->{$groupMembershipExpirationHeader} = undef;
		}

		#affiliations need to be array, because one user can have more affiliations by resources
		my @affiliations = ();
		if($affiliation) {
			push @affiliations, $affiliation;
		}
		$userStruc->{$userId}->{$affiliationsHeader} = \@affiliations;
	}
}
