package Stanford::WebApps::SharedEmail::Util;

## no critic (CodeLayout::ProhibitParensWithBuiltins);

use strict;
use warnings FATAL => 'all';

use Carp;
use Net::Remctl;
use Readonly;
use Template;
use Try::Tiny;

# Stanford packages:
use Stanford::Orange::Util qw(kerberos_ticket_cache_no_afs);
use Stanford::Orange::Sendmail;
use Stanford::Directory;
use Stanford::WebApps::SharedEmail::Config qw(%CONFIG);

require Exporter;
use base qw(Exporter);

our @EXPORT_OK = qw(
  announcement_enabled
  lookup_orgid
  read_password
  refresh_krb5_cache
  link_workgroup_enabled
  remctl_link_workgroup
  send_email
  split_entries
  white_space_cleanup
);

# Create a directory connection for use by lookup_orgid
# Looking up OrgID via LDAP is MUCH faster than via remctl to MaIS
# We'll also let the first query open the connection, in case it isn't needed.
#
# Note: In the old code we used an anonymous bind. This worked because
# anonymous ON-CAMPUS searches are granted complete read access to the org
# tree. But now we are likely to be running the application from an
# OFF-CAMPUS address so we need to use a regular authenticated SASL search
# and have the LDAP team grant our principal read access to the org tree.
my $DIRECTORY = Stanford::Directory->new();
$DIRECTORY->set(
    ldap_server => $CONFIG{ldap_server},
    mechanism   => 'GSSAPI',
    ticket_file => $CONFIG{ccache},
    basedn      => 'cn=organizations,dc=stanford,dc=edu',
    net_ldapapi => 1,
);
#
# Old anonymous search:
#$DIRECTORY->set(
#    ldap_server => $CONFIG{ldap_server},
#    mechanism   => undef,
#    basedn      => 'cn=organizations,dc=stanford,dc=edu',
#);

END { $DIRECTORY->close_directory; }

# lookup_orgid: Given a four-character Org ID, return the name of the
# organization.  If the Org ID doesn't exist, returns false (0).
# Finally, if there are problems, croak.
sub lookup_orgid {
    my ($orgid) = @_;

    # Make sure our OrgID is the right form
    if ($orgid =~ m/\A([a-z]{4})\z/xmsi) {
        $orgid = $1;
    } else {
        return 0;
    }

    # Query LDAP for the OrgID
    my @entries = $DIRECTORY->ldap_query("suAdminID=$orgid", 'displayName');

    # Check for errors
    if ($DIRECTORY->error()) {
        croak("Error looking up OrgID $orgid: " . $DIRECTORY->error());
    }
    if (scalar(@entries) == 0) {
        return 0;
    }
    if (scalar(@entries) > 1) {
        croak("OrgID $orgid found multiple entries");
    }

    # We've got a name, so send it through!
    return $entries[0]->displayName;
}

# split_entries: Given a list of "one X per line", return a list of X
# Takes a string, and returns a list or a list ref.
sub split_entries {
    my ($text) = @_;

    if (!defined($text)) {
        return wantarray ? () : [];
    }
    my @entries = split /^/xms, $text;

    # Remove leading/trailing whitespace and empty lines
    my @output;
    foreach my $entry (@entries) {
        $entry =~ s/\s//xmsg;
        if (!length($entry)) {
            next;
        }
        push @output, lc($entry);
    }

    return wantarray ? @output : \@output;
}

# send_email: This sends an email to someone, using a TT2 template.
# We take the tempate file name, a variables hashref, recipient, and subject
# line.  Will croak if there are any problems.
sub send_email {
    my ($template_file, $template_vars, $recipient, $subject) = @_;

    # $template_vars can be undefined, if the user doesn't have anything
    $template_vars ||= {};

    # Add the platform to the template vars
    $template_vars->{platform} = $CONFIG{platform};

    # Create our template object, and generate the email body
    my $template = Template->new({
        INCLUDE_PATH => [qw(
            /usr/share/shared-email/templates
        )],
    });
    my $content;
    $template->process($template_file, $template_vars, \$content)
        or croak ("Error processing $template_file: " . $template->error());

    # Prepare and send the email!
    my %mail_params = (
        'smtp_host' => $CONFIG{'send_email_smtp_host'},
        'smtp_port' => $CONFIG{'send_email_smtp_port'},
        'from'      => $CONFIG{'send_email_from'},
        'to'        => $recipient,
        'subject'   => $subject,
        'body'      => $content,
        'debug'     => 0,
        ) ;

    # Adjust %mail_params if we are doing an authenticated send.
    if ($CONFIG{'send_email_use_auth_enabled'} =~ m{^yes.*$}ixsm) {
        $mail_params{'authenticate'} = 1;
        $mail_params{'username'} = $CONFIG{'send_email_use_auth_username'};

        my $pw_file = $CONFIG{'send_email_use_auth_password_file'} ;
        $mail_params{'password'} = read_password($pw_file);
    } else {
        $mail_params{'authenticate'} = 0;
    }

    try {
        my $email = Stanford::Orange::Sendmail->new(%mail_params) ;
        $email->deliver() ;
    } catch {
        my $exception = $_;
        croak(  "Problem sending email '$template_file' to $recipient: "
              . $exception
        );
    };

    return 1;
}

sub refresh_krb5_cache {
    my ($keytab_file, $cache_file) = @_ ;

    if (!$keytab_file) {
        croak 'no keytab_file parameter passed' ;
    }

    if (!$cache_file) {
        croak 'no cache_file parameter passed' ;
    }

    return kerberos_ticket_cache_no_afs($keytab_file, $cache_file);
}

# Returns 1 if the announcement banner is enabled in the config AND the
# announcement HTML file exists and is readable; 0 otherwise.
sub announcement_enabled {
    return ($CONFIG{'announcement_enabled'}
            && -r '/etc/shared-email-web/announcement.html') ? 1 : 0;
}

# Returns 1 if workgroup linkage enabled, 0 otherwise.
sub link_workgroup_enabled {
    # We only do the Workgroup linkage if the configuration directive
    # $CONFIG{workgroup_linkage_enabled} is set to "yes";
    if ((exists $CONFIG{'workgroup_linkage_enabled'})
        && ($CONFIG{'workgroup_linkage_enabled'} =~ m{yes}ixsm)) {
        return 1;
    } else {
        return 0;
    }
}

sub remctl_link_workgroup {
    my ($server, $server_principal, $uid, $contact, $env) = @_ ;

    my @cmd = (
        'remctl',
        '-s', $server_principal,
        $server,
        'wg-int-api',
        'link_group',
        'WIN Active Directory',
        "office365-$uid",
        $contact,
        "office365:$uid",
        );

    # If the environment is passed via $env and is _not_
    # "prod" add it as an additional command-line options.
    if ($env && ($env !~ m{prod}ixsm)) {
        push(@cmd, "-d$env") ;
    }

    my ($stdout, $stderr, $rc) = run_command(@cmd);

    chomp $stdout;
    chomp $stderr;

    return ($stdout, $stderr, $rc);
}

sub run_command {
    my (@command) = @_ ;

    # (From Russ)
    my ($out, $err) ;
    IPC::Run::run(\@command, q{>}, \$out, q{2>}, \$err) ;

    ## no critic (Variables::ProhibitPunctuationVars);
    ## no critic (ValuesAndExpressions::ProhibitMagicNumbers);
    return ($out, $err, $? >> 8) ;
}

sub read_password {
    my ($file) = @_ ;

    my $password;
    open(my $passfh, '<', $file)
        or croak("Error reading $file: $!");
    $password = <$passfh>;
    chomp $password;
    close($passfh);

    return $password;
}

sub white_space_cleanup {
    # Replace all non-space whitespace with spaces, and compress all multiple
    # white spaces to a single whitespace.
    my ($str) = @_;

    # Step 1. Replace any whitespace with the space character.
    $str =~ s/[^\S ]/ /g;

    # Step 2. Compress two or more space characters into a single space character.
    $str =~ s/ +/ /g;

    # Step 3. Strip any leading or trailing space characters.
    $str =~ s/^\s+//g;
    $str =~ s/\s+$//g;

    return $str;
}

1;
