#!/usr/bin/perl
use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use File::Copy;
use Time::Piece;
use Perun::Agent;

sub getStatus;

local $::SERVICE_NAME = "vsup_zimbra";
local $::PROTOCOL_VERSION = "3.1.0";
my $SCRIPT_VERSION = "3.0.3";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".csv";
my $data = perunServicesInit::getHashedHierarchicalData;
my $agent = Perun::Agent->new();
my $attributesAgent = $agent->getAttributesAgent;

# Constants
our $A_LOGIN; *A_LOGIN = \'urn:perun:user_facility:attribute-def:virt:login';
our $A_UCO; *A_UCO = \'urn:perun:user:attribute-def:def:ucoVsup';
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_ARTISTIC_FIRST_NAME; *A_ARTISTIC_FIRST_NAME = \'urn:perun:user:attribute-def:def:artisticFirstName';
our $A_ARTISTIC_LAST_NAME; *A_ARTISTIC_LAST_NAME = \'urn:perun:user:attribute-def:def:artisticLastName';
our $A_EMAIL_VSUP;  *A_EMAIL_VSUP = \'urn:perun:user:attribute-def:def:vsupMail';
our $A_R_RELATION_TYPE; *A_R_RELATION_TYPE = \'urn:perun:resource:attribute-def:def:relationType';
our $A_BLACKLISTED;  *A_BLACKLISTED = \'urn:perun:user_facility:attribute-def:virt:blacklisted';
our $A_EXPIRATION_KOS;  *A_EXPIRATION_KOS = \'urn:perun:user:attribute-def:def:expirationKos';
our $A_EXPIRATION_DC2;  *A_EXPIRATION_DC2 = \'urn:perun:user:attribute-def:def:expirationDc2';
our $A_EXPIRATION_MANUAL; *A_EXPIRATION_MANUAL = \'urn:perun:user:attribute-def:def:expirationManual';
our $A_ZIMBRA_DISPLAY_NAME;  *A_ZIMBRA_DISPLAY_NAME = \'urn:perun:user:attribute-def:def:zimbraDisplayName';
# following are manually retrieved
our $A_EMAIL_VSUP_ALIAS;  *A_EMAIL_VSUP_ALIAS = \'urn:perun:user:attribute-def:def:vsupMailAlias';
our $A_EMAIL_VSUP_ALIASES;  *A_EMAIL_VSUP_ALIASES = \'urn:perun:user:attribute-def:def:vsupMailAliases';

# Read which accounts are "system wide" => IGNORED by Perun.
open FILE, "<" . "/etc/perun/services/$::SERVICE_NAME/vsup_zimbra_ignored_accounts";
my @ignoredAccountsList = <FILE>;
close FILE;
chomp(@ignoredAccountsList);
my %ignoredAccounts = map { $_ => 1 } @ignoredAccountsList;

# GATHER USERS
my $users;  # $users->{$uco}->{ATTR} = $attrValue;

#
# AGGREGATE DATA
#
# FOR EACH USER
foreach my $resourceId ($data->getResourceIds()) {

	my $relationType = $data->getResourceAttributeValue(resource => $resourceId, attrName => $A_R_RELATION_TYPE);

	# Users from Resource must be in a relation
	unless ($relationType) {
		next;
	}

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

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

		# SKIP MEMBERS WHICH SUPPOSED TO BE SYSTEM WIDE ACCOUNTS => IGNORED BY PERUN
		if (exists $ignoredAccounts{$login}) {
			next;
		}

		my $uco  = $data->getUserAttributeValue(member => $memberId, attrName => $A_UCO);
		my $vsupMail  = $data->getUserAttributeValue(member => $memberId, attrName => $A_EMAIL_VSUP);
		my $userId  = $data->getUserIdForMember(member => $memberId);
		my $artisticFirstName = $data->getUserAttributeValue(member => $memberId, attrName => $A_ARTISTIC_FIRST_NAME);
		my $firstName = $data->getUserAttributeValue(member => $memberId, attrName => $A_FIRST_NAME);
		my $artisticLastName = $data->getUserAttributeValue(member => $memberId, attrName => $A_ARTISTIC_LAST_NAME);
		my $lastName = $data->getUserAttributeValue(member => $memberId, attrName => $A_LAST_NAME);
		my $displayName = $data->getUserAttributeValue(member => $memberId, attrName => $A_ZIMBRA_DISPLAY_NAME);
		my $blacklisted = $data->getUserFacilityAttributeValue(member => $memberId, attrName => $A_BLACKLISTED);
		my $expirationKOS = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_KOS);
		my $expirationDC2 = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_DC2);
		my $expirationManual = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_MANUAL);

		$users->{$uco}->{$A_LOGIN} = $login;
		$users->{$uco}->{'EMAIL'} = ($vsupMail || $login . '@vsup.cz');

		$users->{$uco}->{'USER_ID'} = $userId;

		# determine user account type
		if ($relationType eq "ZAM") {
			# prefer ZAM over anything
			$users->{$uco}->{'TYPE'} = $relationType;
			$users->{$uco}->{'COS'} = '0603cf86-f917-4448-bb34-57dd11a2c381';
		} elsif ($relationType eq "PED") {
			# prefer active PED before STU or EXPIRED or not yet set
			if ((!defined $users->{$uco}->{'TYPE'}) || $users->{$uco}->{'TYPE'} ne 'ZAM') {
				$users->{$uco}->{'TYPE'} = $relationType;
				$users->{$uco}->{'COS'} = 'e3c33612-98c2-4c53-8b67-80ac1b133546';
			}
		} elsif ($relationType eq "STU") {
			# prefer active STU if is also EXPIRED or not yet set
			if ((!defined $users->{$uco}->{'TYPE'}) || (($users->{$uco}->{'TYPE'} ne 'ZAM') && ($users->{$uco}->{'TYPE'} ne 'PED'))) {
				$users->{$uco}->{'TYPE'} = $relationType;
				$users->{$uco}->{'COS'} = 'e00428a1-0c00-11d9-836a-000d93afea2a';
			}
		} elsif ($relationType eq "EXPIRED") {
			# mark user expired if has no other relation (STU/ZAM)
			unless ($users->{$uco}->{'TYPE'}) {
				$users->{$uco}->{'TYPE'} = $relationType;
				$users->{$uco}->{'COS'} = 'e00428a1-0c00-11d9-836a-000d93afea2a';
			}
		} elsif ($relationType eq "GEN") {
			# CAN'T OVERLAP WITH ANOTHER
			unless ($users->{$uco}->{'TYPE'}) {
				$users->{$uco}->{'TYPE'} = $relationType;
				$users->{$uco}->{'COS'} = 'e69e98ba-e16b-4bdd-bc2c-0ae5e697337d';
			} else {
				die "User: '$login' was both GENERIC type and $users->{$uco}->{'TYPE'} type.";
			}
		} elsif ($relationType eq "MML") {
			# CAN'T OVERLAP WITH ANOTHER
			unless ($users->{$uco}->{'TYPE'}) {
				$users->{$uco}->{'TYPE'} = $relationType;
				$users->{$uco}->{'COS'} = '867a2875-136f-40be-aab8-751658db0300';
			} else {
				die "User: '$login' was both MAILMAN-LIST type and $users->{$uco}->{'TYPE'} type.";
			}
		}

		# get users name
		$firstName = ($artisticFirstName || ($firstName || ''));
		$lastName = ($artisticLastName || ($lastName || ''));
		# use custom zimbra displayName
		$displayName = $displayName || '';
		# if custom name not present, create it from givenName and sn
		unless(defined $displayName and length $displayName) {
			if (defined $firstName and length $firstName and defined $lastName and length $lastName) {
				$displayName = $firstName . " " . $lastName;
			} elsif (defined $firstName and length $firstName and !(defined $lastName and length $lastName)) {
				$displayName = $firstName;
			} elsif (!(defined $firstName and length $firstName) and defined $lastName and length $lastName) {
				$displayName = $lastName;
			}
		}

		$users->{$uco}->{"FIRST_NAME"} = ($firstName || '');
		$users->{$uco}->{"LAST_NAME"} = ($lastName || '');
		$users->{$uco}->{"DISPLAY_NAME"} = ($displayName || '');

		if (defined $blacklisted and ($blacklisted == 1)) {
			# blacklisted users !security ban! are locked
			$users->{$uco}->{"STATUS"} = 'locked';
		} else {
			$users->{$uco}->{"STATUS"} = getStatus($expirationKOS, $expirationDC2, $expirationManual);
		}

	}
}

# manually get mail aliases
my @uco_keys = sort keys %{$users};
for my $uco (@uco_keys) {

	my $uid = $users->{$uco}->{'USER_ID'};
	my @attrNames = ($A_EMAIL_VSUP_ALIAS, $A_EMAIL_VSUP_ALIASES);

	my @attributes = $attributesAgent->getAttributes( user => $uid , attrNames => \@attrNames );
	foreach (@attributes) {
		if ($_->getName() eq $A_EMAIL_VSUP_ALIAS) {

			$users->{$uco}->{$A_EMAIL_VSUP_ALIAS} = $_->getValue || '';

		} elsif ($_->getName() eq $A_EMAIL_VSUP_ALIASES) {

			my $aliases = $_->getValue;
			my @aliases = ();
			if ($aliases) {
				@aliases = @$aliases;
			}
			$users->{$uco}->{$A_EMAIL_VSUP_ALIASES} = join(",",@aliases) || '';

		}
	}

}

#
# PRINT user data
#
open FILE,">$fileName" or die "Cannot open $fileName: $! \n";
binmode FILE, ":utf8";

# print personal info
my @keys = sort keys %{$users};
for my $uco (@keys) {

	# print attributes, which are never empty
	print FILE $uco . "\t" . $users->{$uco}->{$A_LOGIN} . "\t" . $users->{$uco}->{'TYPE'} . "\t" .
		$users->{$uco}->{'EMAIL'} . "\t" . $users->{$uco}->{"FIRST_NAME"} . "\t" . $users->{$uco}->{"LAST_NAME"} .
		"\t" . $users->{$uco}->{"DISPLAY_NAME"} . "\t" . $users->{$uco}->{"STATUS"} . "\t" . $users->{$uco}->{"COS"} .
		"\t" . $users->{$uco}->{$A_EMAIL_VSUP_ALIAS} . "\t" . $users->{$uco}->{$A_EMAIL_VSUP_ALIASES} . "\n";

}

close(FILE);

#
# Copy ignored accounts
#
copy("/etc/perun/services/$::SERVICE_NAME/vsup_zimbra_ignored_accounts", "$DIRECTORY/vsup_zimbra_ignored_accounts") or die "Couldn't copy file of ignored Zimbra accounts.";

perunServicesInit::finalize;

#
# active - výchozí stav, netřeba nastavovat při vytváření schránky, pouze pokud byl účet předtím v jiném stavu
# locked - uzamčen pro přihlašování, maily jsou doručovány
# closed - nelze se přihlásit, maily nejsou doručovány - soft-delete
#
sub getStatus() {

	# read input
	my $expirationKos = shift;
	my $expirationDc2 = shift;
	my $expirationMan = shift;
	# parse to time or undef
	my $expirationKosTime = ($expirationKos) ? Time::Piece->strptime($expirationKos,"%Y-%m-%d") : undef;
	my $expirationDc2Time = ($expirationDc2) ? Time::Piece->strptime($expirationDc2,"%Y-%m-%d") : undef;
	my $expirationManTime = ($expirationMan) ? Time::Piece->strptime($expirationMan,"%Y-%m-%d") : undef;

	my @expirations = ();
	if (defined $expirationKosTime) { push(@expirations, $expirationKosTime->epoch); }
	if (defined $expirationDc2Time) { push(@expirations, $expirationDc2Time->epoch); }
	if (defined $expirationManTime) { push(@expirations, $expirationManTime->epoch); }

	# sort all expirations
	my @sorted_expirations = sort { $a <=> $b } @expirations;

	my $latest_expiration = $sorted_expirations[$#sorted_expirations];
	my $currentDate = Time::Piece->strptime(localtime->ymd,"%Y-%m-%d");

	if (!defined $expirationKos and !defined $expirationDc2 and !defined $expirationMan) {
		# if no expiration set in source data - take as "never"
		return 'active';
	}

	# Add time 23:59:59 to the date, since we want accounts to be active on the last day
	$latest_expiration = $latest_expiration + 86399;

	if ($latest_expiration > $currentDate->epoch) {
		return 'active';
	}

	# (will) expire by studies - add 21 days grace period
	if ($expirationKosTime and ($latest_expiration == $expirationKosTime->epoch + 86399)) {

		# within 14 days grace period - still active
		if (($latest_expiration + (21*24*60*60)) > $currentDate->epoch) {
			return 'active';
		} else {
			# is within 30 days period
			if (($latest_expiration + (30*24*60*60)) > $currentDate->epoch) {
				return 'locked';
			}
			return 'closed'
		}

	} else {
		# Expired by employment or manual
		if (($latest_expiration + (30*24*60*60)) > $currentDate->epoch) {
			return 'locked';
		}
		return 'closed';
	}

}
