#!/usr/bin/perl

## no critic (CodeLayout::ProhibitParensWithBuiltins) ;
## no critic (Modules::RequireNoMatchVarsWithUseEnglish) ;
## no critic (ErrorHandling::RequireCheckingReturnValueOfEval) ;

# List shared-email records or show information for a specific
# shared-email record.

use strict ;
use warnings ;
use autodie ;

use Carp ;
use Data::Dumper ;
use DBI ;
use English ;
use File::Temp ;
use Getopt::Long::Descriptive;
use Pod::Usage;

use Stanford::Schema::WebApps::SharedEmail ;
use Stanford::WebApps::SharedEmail::Config qw(%CONFIG) ;
use Stanford::WebApps::SharedEmail::Util qw( remctl_link_workgroup refresh_krb5_cache ) ;

my $SCHEMA ;
my $VERBOSE = 0 ;

# ### ## ### ## ### ## ### ## ### ## ### ## ### ## ### ## ### ## ### #
sub log_error {
    my ($msg) = @_ ;
    print {*STDERR} $msg . "\n" ;
    return ;
}

sub progress {
    my ($msg) = @_ ;

    if ($VERBOSE) {
        print "progress: $msg\n" ;
    }
    return ;
}

sub exit_with_error {
    my ($msg) = @_ ;
    print "ERROR: $msg\n" ;
    exit(1) ;
}

sub db_connect {
    my $msg ;
    progress('connecting to database') ;

    if ($SCHEMA) {
        return ;
    }

    eval {
       $SCHEMA = Stanford::Schema::WebApps::SharedEmail->connect(\%CONFIG);
    };
    if ($EVAL_ERROR) {
        $msg = 'could not connect to database' ;
        exit_with_error($msg) ;
    }

    return ;
}

sub get_status {
    my ($shared_email) = @_ ;

    my $is_approved   = $shared_email->is_approved() ;
    my $reject_reason = $shared_email->reject_reason() ;
    my $when_created  = $shared_email->when_created() ;

    # REJECTED?
    if ($reject_reason) {
        return 'rejected' ;
    }

    # PROVISIONED?
    if ($when_created) {
        return 'provisioned' ;
    }

    # APPROVED?
    if ($is_approved) {
        return 'approved' ;
    }

    # EVERYTHING ELSE
    return 'pending' ;
}

sub all_accounts_string {
    progress('getting all accounts') ;
    db_connect();
    my @shared_emails = $SCHEMA->resultset('Account')->all();

    foreach my $shared_email (@shared_emails) {
        my %col_to_value = $shared_email->get_inflated_columns() ;
        my $account = $col_to_value{'account'} ;
        my $status = get_status($shared_email) ;
        print "$account,$status\n" ;
    }

    return ;
}

sub account_string {
    my ($account) = @_ ;

    my $msg ;

    progress("searching for record '$account'") ;
    db_connect();

    my $shared_email = $SCHEMA->resultset('Account')->search({
        account => $account,
    });

    if ($shared_email->count == 0) {
        $msg = "no record for account '$account' found in database" ;
        exit_with_error($msg) ;
    } elsif ($shared_email->count > 1) {
        my $count = $shared_email->count ;
        $msg = "found $count records for '$account' in database (should be only one)" ;
        exit_with_error($msg) ;
    }

    progress("found record '$account'") ;
    my $shared_email1 = $shared_email->first;
    my %col_to_value = $shared_email1->get_inflated_columns() ;

    my @fields = qw (
      account
      description
      requestor
      org
      sponsor
      additional
      when_requested
      when_reviewed
      reject_reason
      is_approved
      when_created
      when_moved
        ) ;

    my $result = q{} ;
    foreach my $field (@fields) {
        my $value = 'NULL' ;
        if (exists($col_to_value{$field}) && defined($col_to_value{$field})) {
            $value = $col_to_value{$field} ;
        }
        $result .= sprintf("%15s: %s\n", $field, $value);
    }

    return $result ;
}

sub forwards_or_owners_string {
    my ($account, $table) = @_ ;

    my %table_to_field = (
        'AccountForward' => 'forward',
        'AccountOwner'   => 'owner',
        ) ;
    my $field = $table_to_field{$table} ;
    my $msg ;

    progress("searching for record '$account' in $table") ;
    db_connect();
    my @records = $SCHEMA->resultset($table)->search({
        account => $account,
    })->all();

    my $count = scalar(@records) ;
    if ($count == 0) {
        $msg = "no record for account '$account' found in $table table" ;
        exit_with_error($msg) ;
    }

    progress("found $count record(s) for '$account' in table $table") ;
    my $result = q{} ;
    foreach my $forward (@records) {
        my %col_to_value = $forward->get_inflated_columns() ;

        my $forward_name = $col_to_value{$field} ;
        $result .= sprintf("  %s\n", $forward_name);
    }

    return $result ;
}

sub accountforward_string {
    my ($account) = @_ ;
    return forwards_or_owners_string($account, 'AccountForward') ;
}

sub accountowners_string {
    my ($account) = @_ ;
    return forwards_or_owners_string($account, 'AccountOwner') ;
}

sub account_info_string {
    my ($account) = @_ ;

    my $rv = q{} ;

    my $account_str  = account_string($account) ;
    my $forwards_str = accountforward_string($account) ;
    my $owners_str   = accountowners_string($account) ;

    $rv .= "Account information:\n" ;
    $rv .= $account_str ;
    $rv .= "Forwarding Addresses:\n" ;
    $rv .= $forwards_str ;
    $rv .= "Owners:\n" ;
    $rv .= $owners_str ;

    return $rv ;
}

sub delete_account {
    my ($account) = @_ ;

    db_connect();

    my $msg ;

    my $account_str = account_string($account) ;
    if (!$account_str) {
        $msg = "no record with account name '$account'" ;
        exit_with_error($msg) ;
    }

    # Start a transaction
    my $TRANSACTION ;
    progress('start transaction for record delete') ;
    eval {
        $TRANSACTION = $SCHEMA->txn_scope_guard;
    };
    if ($EVAL_ERROR) {
        $msg = "Database transaction-start error: $EVAL_ERROR" ;
        exit_with_error($msg) ;
    }

    # Delete the various records
    eval {
        $SCHEMA->resultset('AccountOwner')->search(
          {
            account => $account,
          }
        )->delete;

        $SCHEMA->resultset('AccountForward')->search(
          {
            account => $account,
          }
        )->delete;

        $SCHEMA->resultset('Account')->search(
          {
            account => $account,
          }
            )->delete;
    } ;
    my $at_error = $EVAL_ERROR ;
    if ($at_error) {
        $msg = "error deleting record $account: $at_error" ;
        die $msg ;
    }

    $TRANSACTION->commit;

    progress("acount '$account' deleted");

    return ;
}

sub unapprove_account {
    my ($account) = @_ ;

    db_connect();

    my $msg ;

    my $account_str = account_string($account) ;
    if (!$account_str) {
        $msg = "no record with account name '$account'" ;
        exit_with_error($msg) ;
    }

    # Start a transaction
    my $TRANSACTION ;
    progress('start transaction for record unapprove') ;
    eval {
        $TRANSACTION = $SCHEMA->txn_scope_guard;
    };
    if ($EVAL_ERROR) {
        $msg = "Database transaction-start error: $EVAL_ERROR" ;
        exit_with_error($msg) ;
    }

    my $shared_email = $SCHEMA->resultset('Account')->search({
        account => $account,
    });

    eval {
        # Unapprove the record: set is_approved to 0 and
        # when_reviewed to NULL.
        $shared_email->update({ is_approved   => 0 });
        $shared_email->update({ when_reviewed => undef });
    } ;
    my $at_error = $EVAL_ERROR ;
    if ($at_error) {
        $msg = "error unapproving record $account: $at_error" ;
        die $msg ;
    }

    $TRANSACTION->commit;

    progress("acount '$account' unapproved");

    return 1;
}

# ### ## ### ## ### ## ### ## ### ## ### ## ### ## ### ## ### ## ### #

my $msg1 ;

my $prog_name = 'shared-email-manage' ;

my @action_helps = (
    "$prog_name manual (show manual)",
    "$prog_name help (show help)",
    "$prog_name list (list all shared-email accounts)",
    "$prog_name show <account> (show <account> details)",
    "$prog_name delete --delete <account> (see manual for details)",
    "$prog_name unapprove <account> (see manual for details)",
    );

my ($opt, $usage) = describe_options(
     join("\n", @action_helps),
    [ 'delete',            'needed when deleting a record'  ],
    [ 'verbose|v',         'print extra stuff'              ],
    );
#>>>



if ($opt->verbose()) {
    $VERBOSE = 1 ;
}

my $action ;
if ($ARGV[0]) {
    $action = $ARGV[0] ;
} else {
    print $usage->text;
    exit;
}

if ($action eq 'list') {
    if ($ARGV[1]) {
        $msg1 = 'the list action takes no arguments' ;
        exit_with_error($msg1) ;
    } else {
        print all_accounts_string() ;
    }
} elsif ($action eq 'unapprove') {
    if (!$ARGV[1]) {
        $msg1 = q{the 'unapprove' action needs the name of the account as an additional argument} ;
        exit_with_error($msg1) ;
    } else {
        my $account = $ARGV[1] ;
        if ($ARGV[2]) {
            $msg1 = 'the unapprove action takes only one argument' ;
            exit_with_error($msg1) ;
        } else {
            my $rv = unapprove_account($account) ;
            if ($rv) {
                print "account record '$account' unapproved\n" ;
                print account_info_string($account) ;
            } else {
                $msg1 = "error unapproving account record '$account'" ;
                exit_with_error($msg1) ;
            }
        }
    }
} elsif ($action eq 'show') {
    if (!$ARGV[1]) {
        $msg1 = q{the 'show' action needs the name of the account as an additional argument} ;
        exit_with_error($msg1) ;
    } else {
        my $account = $ARGV[1] ;
        if ($ARGV[2]) {
            $msg1 = 'the show action takes only one argument' ;
            exit_with_error($msg1) ;
        } else {
            print account_info_string($account) ;
        }
    }
} elsif ($action eq 'delete') {

    if (!$opt->delete()) {
        $msg1 = q{you must use the --delete option when deleting (for safety's sake)} ;
        exit_with_error($msg1) ;
    }

    if (!$ARGV[1]) {
        $msg1 = q{the 'delete' action needs the name of the account as an additional argument} ;
        exit_with_error($msg1) ;
    } else {
        my $account = $ARGV[1] ;
        if ($ARGV[2]) {
            $msg1 = 'the delete action takes only one argument' ;
            exit_with_error($msg1) ;
        } else {
            my $rv = delete_account($account) ;
            if ($rv) {
                print "account record '$account' deleted\n" ;
            } else {
                $msg1 = "error deleting account record '$account'" ;
                exit_with_error($msg1) ;
            }
        }
    }
} elsif ($action eq 'manual') {
    pod2usage(-verbose => 2);
} elsif ($action eq 'help') {
    print $usage->text;
} else {
    $msg1 = "unknown action '$action'" ;
    exit_with_error($msg1) ;
}

exit 0 ;

#####################################################################

__END__

=head1 NAME

shared-email-manage - CLI manager for shared-email database

=head1 SYNOPSIS

shared-email-manage manual

shared-email-manage help

shared-email-manage list

shared-email-manage show I<account>

shared-email-manage delete --delete I<account>

shared-email-manage unapprove I<account>

=head1 DESCRIPTION

B<shared-email-manage> is a command-line script to help manage the
shared-email database. It can list, show, delete, and unapprove shared-email
records. This script must be run in a context containing the shared-email
configuration file.

=head1 ACTIONS

There are six "actions": list, show, delete, unapprove, manual, and help.

The list action lists all the records in the database with their
status. The output looks like this:

    aaalabaws,provisioned
    aabs2018conf,provisioned
    aafacultysearch,provisioned
    aarcturoring,pending
    ...

The string following the comma is the status. There are four statuses:
"provisioned", "pending", "approved", and "rejected".

    pending --> approved --> provisioned
       |
       `------> rejected

provisioned: the shared-email account has been approved and provisioned in
Office365. There is nothing further to be done for this record.

rejected: the shared-email account request has been rejected by a
shared-email admin.  There is nothing further to be done for this record.

approved: the shared-email account request has been approved by a
shared-email admin but not yet been provisioned

pending: the shared-email account request has not yet been approved by a
shared-email admin.

The "show" action shows detailed information about a single shared-email account.

The "delete" action deletes a single shared-email account record out of
the database. Note that this action does NOT delete any other shared-email
artifiacts, e.g., it is does not delete the office365: Stanford Workgroup
or the Office365 account itself. All those deletions must be done
manually.

The "unapprove" action alters the given shared-email account record
setting the C<is_approved> field to "0" and setting the C<when_reviewed>
field to NULL. If the shared-email is not yet approved this action will
have not effect.

The "manual" action displays the man page.

The "help" action displays a short help text.

=head1 OPTIONS

=over 4

=item B<--delete>

Use this option when deleting a record. It serves no other purpose than
making the user typing the command extra mindful that he is deleting a
record.

=back

=head1 EXIT STATUS

Returns C<0> on success, C<1> on any failure.

=head1 LICENSE AND COPYRIGHT

Copyright 2021 The Board of Trustees of the Leland Stanford Junior
University.  All rights reserved.

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, provided
that the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation, and that the name of Stanford University not be used in
advertising or publicity pertaining to distribution of the software
without specific, written prior permission.  Stanford University makes no
representations about the suitability of this software for any purpose.
It is provided "as is" without express or implied warranty.

THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

=cut

#####################################################################
