#!/usr/bin/perl

=head1 NAME

add_constituency - add or update constitiuensies

=head1 SYNOPSIS

    add_constituency --help
    add_constituency --name <constituency> [options]
    add_constituency --name EDUNET
    add_constituency --name EDUNET --correspond 'edunet@example.com' --quiet

    add_constituency --name <constituency> --rename <new value>

=head1 OPTIONS

=over 4

=item B<--name>

Constituency name. Mandatory argument.

=item --correspond

Set/update correspond address.

=item --comment

Set/update comment address.

=item --rename

Rename constituency, use this option to set new value when --name defines old.

=item --quiet

Disable output.

=back

=head1 DESCRIPTION

This command line utitlity is a helper for activating advanced access control
on tickets using the current value of the constituency field.

Read more in F<lib/RT/IR/Constituencies.pod> using perldoc tool.

=cut

use warnings;
use strict;

### after: use lib qw(@RT_LIB_PATH@);
use lib qw(/usr/share/request-tracker/local/lib /usr/lib/perl5/vendor_perl/5.18.2);

use RT;
RT::LoadConfig;
RT::Init;

my %opt;

sub debug(@) {
    return if $opt{'quiet'};
    print @_;
    print "\n" if $_[-1] && $_[-1] !~ /\n$/s;
};

use Getopt::Long;
GetOptions( \%opt, "name=s", "rename=s", "correspond=s", "comment=s", "help", "quiet" );

if ( $opt{'help'} ) {
    require Pod::Usage;
    import Pod::Usage;
    pod2usage(
        -msg => "RTIR Constituencies management tool\n",
        -verbose => 2,
        -exitval => 0,
    );
}

my $constituency = sanitize_value( $opt{'name'} );
unless ( defined $constituency && length $constituency ) {
    require Pod::Usage;
    import Pod::Usage;
    pod2usage(
        -msg => "Name of constituency is a mandatory argument\n",
        -exitval => 1,
    );
}

my @queue_prefixes = ('Incidents', 'Incident Reports', 'Investigations', 'Blocks');

if ( exists $opt{'rename'} ) {
    exit rename_value(
        $constituency => sanitize_value( $opt{'rename'} )
    );
}


$RT::Handle->BeginTransaction;

my $exist = cf_value_exists( $constituency );

print "\nIf you continue then we'll...\n";
if ( $exist ) {
    print "* WARNING: skip adding constituency value, already exists.\n";
} else {
    print "* add new constituency value: '$constituency'.\n";
}
foreach (@queue_prefixes) {
    print "* create queue '$_ - $constituency' if it doesn't exist\n";
}
print <<END;
* create group 'DutyTeam $constituency' if it don't exist
* create group 'ReadOnly $constituency' if it don't exist
* grant basic set of rights to the groups on the queues

END

if ( $exist ) {
    exit 0 unless prompt_yN( "Do you want to proceed with update?" );
} else {
    exit 0 unless prompt_yN( "Do you want to proceed creating new incident?" );
}

debug "Adding constituency $constituency";
add_cf_value( $constituency );

my %metaqueues;
my %mainqueues;
# Create our four new queues
foreach my $prefix (@queue_prefixes) {
    $mainqueues{$prefix} = load_queue($prefix); # will always load
    $metaqueues{$prefix} = create_or_load_queue($prefix . " - " .$constituency);
}

# Create a DutyTeam $constituency
my $dutyteam = create_or_load_group('DutyTeam '.$constituency);
my $ro = create_or_load_group('ReadOnly '.$constituency);

my @DUTYTEAM_METAQUEUE_RIGHTS = (

       'OwnTicket', 
       'CommentOnTicket', 
       'ShowTicket', 
       'ShowTicketComments', 
       'StealTicket', 
       'TakeTicket', 
       'Watch', 
); 

my @DUTYTEAM_PRIMARY_QUEUE_RIGHTS = (
       'CreateTicket', 
       'SeeQueue', 
       'ShowTemplate', 
);


my @RO_METAQUEUE_RIGHTS = (
       'ShowTicket', 
       'ShowTicketComments', 
       'Watch', 
);
 

my @RO_PRIMARY_QUEUE_RIGHTS = (
       'SeeQueue', 
       'ShowTemplate', 

);
   
    
my @DUTYTEAM_CF_RIGHTS = ( 'SeeCustomField', 'ModifyCustomField');
my @RO_CF_RIGHTS = ( 'SeeCustomField');

# Grant that new dutyteam rights to see and update the CFs
    grant_group_cf_rights($dutyteam, @DUTYTEAM_CF_RIGHTS);
# Grant that new dutyteam rights to do queue-level things on the "main" RTIR queues
    grant_group_queue_rights($dutyteam, \%mainqueues, @DUTYTEAM_PRIMARY_QUEUE_RIGHTS);
# Grant that new dutyteam all the regular dutyteam rights for the new constituency queues
    grant_group_queue_rights($dutyteam, \%metaqueues, @DUTYTEAM_METAQUEUE_RIGHTS);




# Create or load the group "ReadOnly $constituency"
    grant_group_cf_rights($ro, @RO_CF_RIGHTS);
# Grant the new readonly group the rights to see the RTIR CFs
    grant_group_queue_rights($ro, \%mainqueues, @RO_PRIMARY_QUEUE_RIGHTS);
# Grant the new readonly group the rights to see the "main" RTIR queues
    grant_group_queue_rights($ro, \%metaqueues, @RO_METAQUEUE_RIGHTS);


$RT::Handle->Commit;

debug "Done. Enjoy your new constituency.\n"; 

sub prompt_yN {
    my $text = shift;
    print "$text [y/N] ";
    return 0 unless <STDIN> =~ /^(?:y|yes)$/i;
    return 1;
}

sub get_cf {
    my $cf = RT::CustomField->new( $RT::SystemUser );
    $cf->Load( "_RTIR_Constituency" );
    unless ( $cf->id ) {
        die "Couldn't load the constituency custom field";
    }
    return $cf;
}

sub cf_value_exists {
    my $value = shift;

    debug "Check that the constituency exists";

    my $cf = get_cf();
    my $values = $cf->Values;
    $values->Limit( FIELD => 'Name', VALUE => $value );
    my $value_obj = $values->First;
    return $value_obj && $value_obj->id? $value_obj : undef;
}

sub add_cf_value {
    my $value = shift;

    debug "Adding the value to the constituency CF";

    my $cf = get_cf();
    if ( cf_value_exists( $value ) ) {
        debug "Value '$value' already exists";
    } else {
        my ($val,$msg) = $cf->AddValue( Name => $value );
        die $msg unless $val;
        debug "Added '$value' to the constituency field";
    }
}

sub grant_group_queue_rights {
    my $group = shift;
    my $queues = shift;
    my @rights = (@_);
    
    foreach my $queue    (values %$queues) {
        debug "Granting rights for queue " .$queue->Name. " to group ". $group->Name;
        foreach my $right  (@rights) {
            my ($val,$msg) =  $group->PrincipalObj->GrantRight(Right => $right, Object=>$queue);
            debug "\t$right\n";
            if ( $val ) {
                debug "\t...done.";
            }
            elsif ( $msg =~ /That principal already has that right/i ) {
                debug "\t...skipped, already granted.\n";
            } else {
                die "Failed to grant $right to ". $group->Name ." for Queue ". $queue->Name;
            }
        }
    } 
}


sub grant_group_cf_rights {
    my $group = shift;
    my @rights = (@_);

    my $cfs = RT::CustomFields->new($RT::SystemUser);
    $cfs->Limit( FIELD => 'Name', OPERATOR => 'STARTSWITH', VALUE => '_RTIR' );
    while ( my $cf = $cfs->Next ) {
        debug "Granting rights for custom field ". $cf->Name . " to group ". $group->Name;

        foreach my $right  (@rights) {
            my ($val,$msg) =  $group->PrincipalObj->GrantRight(Right => $right, Object=>$cf);
            debug "\t$right";
            if ( $val ) {
                debug "\t...done.";
            }
            elsif ( $msg =~ /That principal already has that right/i ) {
                debug "\t...skipped, already granted.\n";
            } else {
                die "Failed to grant $right to ".$group->Name. " for Custom Field ". $cf->Name
                    .".\nError: $msg";
            }
        }
    } 
}

sub group_exists {
    my $name = shift;
    my $group = RT::Group->new( $RT::SystemUser );
    $group->LoadByCols( Name => $name );
    return $group && $group->id ? $group : undef;
}

sub create_or_load_group {
    my $name = shift;
    my $group = RT::Group->new($RT::SystemUser);
    $group->LoadUserDefinedGroup($name);
    unless ( $group->id ) {
        my ($val,$msg) = $group->CreateUserDefinedGroup( Name => $name);
        debug "Creating new group $name: $msg";
    } else {
        debug "Group '$name' already exists";
    }

    die "Failed to create group $name." unless $group->id;

    return $group;
}

sub queue_exists {
    my $name = shift;
    my $queue = RT::Queue->new( $RT::SystemUser );
    $queue->LoadByCols( Name => $name );
    return $queue && $queue->id ? $queue : undef;
}

sub load_queue {
    my $name = shift;
    my $queue = queue_exists( $name );
    die "Couldn't load queue '$name'" unless $queue;
    return $queue;
}

sub create_or_load_queue {
    my $name = shift;
    my $queue = RT::Queue->new( $RT::SystemUser );
    $queue->LoadByCols( Name => $name );
    unless ( $queue->id ) {
        my ($val, $msg) = $queue->Create(
            Name => $name,
            CommentAddress => $opt{'comment'},
            CorrespondAddress => $opt{'correspond'},
        );
        debug "Creating new queue '$name': $msg";
        debug "\tcomment address:\t$opt{'comment'}" if $opt{'comment'};
        debug "\tcorrespond address:\t$opt{'correspond'}" if $opt{'correspond'};
    } else {
        debug "Queue '$name' already exists";
        foreach my $type ( qw(Comment Correspond) ) {
            next unless $opt{ lc $type };
            my $method = $type .'Address';
            my $current = $queue->$method();
            next if $current eq $opt{ lc $type };

            $method = 'Set'. $method;
            my ($status, $msg) = $queue->$method( $opt{ lc $type } );
            unless ( $status ) {
                print STDERR "Couldn't set $type address of '$name' queue\n";
            } else {
                debug "\tnew $type address: ". $opt{ lc $type };
            }
        }
    }

    die "Failed to create queue $name." unless $queue->id;

    return $queue;
}

sub rename_value {
    my $old = shift;
    my $new = shift;

    my $value_obj;
    unless ( $value_obj = cf_value_exists( $old ) ) {
        die "Couldn't rename. Constituency '$old' doesn't exist.";
    }
    if ( cf_value_exists( $new ) ) {
        die "Couldn't rename. Constituency '$new' already exists.";
    }

    $RT::Handle->BeginTransaction;

    print "\nIf you continue then we'll...\n";
    print "* rename constituency value '$old' into '$new'.\n";
    foreach (@queue_prefixes) {
        print "* rename queue '$_ - $old' if it exists into '$_ - $new'.\n";
    }
    print <<END;
* rename group 'DutyTeam $old' if it exists into 'DutyTeam $new'
* rename group 'ReadOnly $old' if it exists into 'ReadOnly $new'

END
    return 0 unless prompt_yN( "Do you want to proceed?" );

    {
        my ($status, $msg) = $value_obj->SetName( $new );
        die $msg unless $status;
        debug "Renamed constituency value '$old' -> '$new'.";
    }

    foreach (@queue_prefixes) {
        my $queue = queue_exists( "$_ - $old" );
        unless ( $queue ) {
            debug "Queue '$_ - $old' doesn't exist. Skiping...";
            next;
        }
        if ( queue_exists( "$_ - $new" ) ) {
            die "Couldn't rename queue, target '$_ - $new' already exist.";
        }

        my ($status, $msg) = $queue->SetName( "$_ - $new" );
        die $msg unless $status;

        debug "Renamed queue '$_ - $old' -> '$_ - $new'";
    }

    foreach (qw(DutyTeam Readonly)) {
        my $group = group_exists( "$_ $old" );
        unless ( $group ) {
            debug "Group '$_ $old' doesn't exist. Skiping...";
            next;
        }
        if ( group_exists( "$_ $new" ) ) {
            die "Couldn't rename group, target '$_ $new' already exist.";
        }

        my ($status, $msg) = $group->SetName( "$_ $new" );
        die $msg unless $status;

        debug "Renamed group '$_ $old' -> '$_ $new'";
    }

    $RT::Handle->Commit;

    return 0;
}

sub sanitize_value {
    my $value = shift;
    # cleanup value
    $value = '' unless defined $value;
    $value =~ s/^\s+//;
    $value =~ s/\s+$//;
    $value =~ s/\s+/ /gs;
    return $value;
}

