#!/usr/bin/env perl
#===============================================================================
#
#         FILE:  rt-tickets-report.pl
#       AUTHOR:  Mátyás Selmeci <matyas@cs.wisc.edu>
#       $Id: rt-tickets-report.pl,v 1.1 2013/03/19 22:09:36 matyas Exp $
#
#===============================================================================

use warnings;
use warnings FATAL => qw(uninitialized);
use strict;
BEGIN { $::DEBUG = 0 }
BEGIN { if ($::DEBUG) { use diagnostics; $Carp::Verbose = 1 } }
#use lib '/s/perl-5.8.8/lib/5.8.8/x86_64-linux';
#use lib '/s/perl-5.8.8/lib/5.8.8';
#use lib '/s/perl-5.8.8/lib/site_perl/5.8.8/x86_64-linux';
#use lib '/s/perl-5.8.8/lib/site_perl/5.8.8';
#use lib '/s/perl-5.8.8/lib/site_perl';
use lib '/p/condor/software/RT-Client-REST-0.41/lib';
use RTTicketSearch;
use Config::General;
use Mail::Internet;
use MIME::Entity;
use Date::Manip;





our $DEFAULT_CONFIG_FILE = '/p/condor/home/rust/scripts/rt-tickets-report.conf';
#our $DEFAULT_CONFIG_FILE = 'rt-tickets-report-test.conf';
our $RT_SERVER   = 'https://crt.cs.wisc.edu';
our $RT_USER     = 'mailsearch';
our $RT_PASSWORD = 'teifanUc2';
our $FROM        = 'RT Tickets Report <matyas@cs.wisc.edu>';
our $MAINTAINER  = 'matyas@cs.wisc.edu'; # user to send to if debugging

our $LINE_WIDTH = 78;
use constant { ID          => 0,
               STATUS      => 1,
               OWNER       => 2,
               CREATOR     => 3,
               SUBJECT     => 4,
               LASTUPDATED => 5, };
our @COLUMN_WIDTHS = ( 5, 6, 8, 15, 28, 12 );
our @COLUMN_FIELDS = qw(id Status Owner Creator Subject LastUpdated);

our @DEFAULT_SORTING = ('id');

our %Config;
our %Reports;
our $Rt;




# as_array( $scalar_or_arrayref )
# Utility function: if argument is an array/arrayref, do nothing. If argument
# is a scalar, turn it into an array of 1 element. If argument is null, return
# ()/undef.  Returns an array in array context, arrayref in scalar context.
# Used because Config::General turns duplicate entries into arrayrefs and single
# entries into scalars.
sub as_array {
    if ($#_ > 0)            { return wantarray ? (@_) : \@_; }
    elsif (! defined $_[0]) { return; }
    for (ref($_[0])) {
        my @unpacked;

        if      ($_ eq 'HASH')      { @unpacked = (%{$_[0]}); }
        elsif   ($_ eq 'ARRAY')     { @unpacked = (@{$_[0]}); }
        else                        { @unpacked = ($_[0]);  }

        return wantarray ? @unpacked : [@unpacked];
    }
}

# as_array_with_split( $split_pat, $scalar_or_arrayref )
# See as_array. Splits each argument according to $split_pat
sub as_array_with_split {
    my $split_pat = shift;

    my @arr = map { split( $split_pat, $_ ) } as_array(@_);

    return wantarray ? @arr : \@arr;
}

sub as_array_split_comma { as_array_with_split qr/\s*,\s*/, @_ }



sub text_column_format_string { join(' ', map { "%-${_}.${_}s" } @COLUMN_WIDTHS) }


sub text_table_header {
    return (sprintf text_column_format_string . "\n", @COLUMN_FIELDS) .
           "-" x $LINE_WIDTH . "\n";
}





sub text_table_row {
    my ($ticketref) = @_;
    die "Not enough arguments" if @_ < 1;
    die 'hashref expected for $ticketref' if ref($ticketref) ne 'HASH';

    my @values = map { $ticketref->{$_} } @COLUMN_FIELDS;
    $values[ID] =~ s{ticket/}{};
    $values[LASTUPDATED] = UnixDate(ParseDate($values[LASTUPDATED]),"%v %D");
    return sprintf text_column_format_string . "\n", @values;
}


sub text_table_footer {
    return "\n";
}



sub html_table_header {
    return '<table><tr><th>' . join('</th><th>', @COLUMN_FIELDS) . '</th></tr>' . "\n";
}




sub html_table_row {
    my ($ticketref) = @_;

    die "Not enough arguments" if @_ < 1;
    die 'hashref expected for $ticketref' if ref($ticketref) ne 'HASH';

    my @values = map { $ticketref->{$_} } @COLUMN_FIELDS;
    $values[ID] =~ s{ticket/}{};
    $values[LASTUPDATED] = UnixDate(ParseDate($values[LASTUPDATED]),"%a %m/%d/%Y");
    @values = map { escape_html($_) } @values;
    $values[ID] = qq{<a href="${RT_SERVER}/Ticket/Display.html?id=$values[ID]">$values[ID]</a>};
    return '<tr><td>' . join('</td><td>', @values) . '</td></tr>' . "\n";
}


sub html_table_footer {
    return "</table>\n";
}



sub escape_html {
    my $str = shift || '';
    for ($str) {
        s{\&}{&amp;}g;
        s{\<}{&lt;}g;
        s{\>}{&gt;}g;
        s{\%}{&#37;}g;
        s{\n}{<br/>\n}g;
    }
    return $str;
}




sub load_config {
    my ($config_file) = @_;
    our %Config = Config::General::ParseConfig(-ApacheCompatible=>1, -ConfigFile=>$config_file)
        or die "Couldn't load config file";
    our %Reports = %{$Config{report}};
}




sub verify_reports {
    my @checkqueues;
    my @report_errors;

    our %Reports;

    foreach my $krep (keys %Reports) { ### verifying reports in config file... done
        my %rep = %{$Reports{$krep}};
        if (!$rep{queues}) {
            push @report_errors, "Report $krep: 'queues' field missing or empty";
        }
        else {
            push @checkqueues, as_array_split_comma($rep{queues});
        }
        if (!$rep{notify}) {
            push @report_errors, "Report $krep: 'notify' field missing or empty";
        }

        if ($rep{description}) {
            for (ref($rep{description})) {
                push @report_errors, "Report $krep: multiple descriptions found" if /ARRAY/;
                push @report_errors, "Report $krep: description used as a section" if /HASH/;
            }
        }

        if ($rep{subject}) {
            for (ref($rep{subject})) {
                push @report_errors, "Report $krep: multiple subjects found" if /ARRAY/;
                push @report_errors, "Report $krep: subject used as a section" if /HASH/;
            }
        }
    }
    die join("\n", @report_errors) if @report_errors;
}


sub queues_in_reports {
    our %Reports;
    my @queues;
    for my $rep (values(%Reports)) {
        push @queues, as_array_split_comma($rep->{queues});
    }
    return @queues;
}


sub join_with_or { "(" . join(") OR (", @_) . ")" }
sub join_with_and { "(" . join(") AND (", @_) . ")" }


sub tickets_query {
    my ($queuesref, $conditionsref) = @_;

    my @and_clauses;
    push @and_clauses, join_with_or(map { "Queue = '$_'" } @{$queuesref}) if $queuesref;
    push @and_clauses, join_with_or(@{$conditionsref}) if $conditionsref;

    return join_with_and(@and_clauses);
}



sub html_section {
    my ($description, @tickets) = @_;
    my $html_section;
    $html_section = '<p>' . escape_html($description) . "</p>\n";
    $html_section .= html_table_header;
    $html_section .= html_table_row($_) for @tickets;
    $html_section .= html_table_footer;
    return $html_section;
}

sub text_section {
    my ($description, @tickets) = @_;
    my $text_section;
    $text_section .= $description."\n\n";
    $text_section .= text_table_header;
    $text_section .= text_table_row($_) for @tickets;
    $text_section .= text_table_footer;
    return $text_section;
}

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# MAIN EXECUTION BEGINS HERE
print "Running rt-tickets-report in test mode\n" if $::DEBUG;
load_config($ARGV[0] or $DEFAULT_CONFIG_FILE);
verify_reports();
my $ts = RTTicketSearch->new($RT_SERVER, $RT_USER)->login($RT_PASSWORD);
$ts->verify_queues(queues_in_reports());


foreach my $krep (keys %Reports) {
    my %rep = %{$Reports{$krep}};
    my $query = tickets_query([ as_array_split_comma($rep{queues}) ],
                              exists($rep{condition}) ? [ as_array($rep{condition}) ] : []);

    my @sorting;
    if (exists($rep{sortby})) {
        @sorting = as_array_split_comma($rep{sortby});
    }
    else {
        @sorting = @DEFAULT_SORTING;
    }

    my $description;
    if ($rep{description}) {
        $description = $rep{description};
        $description =~ s/\%QUERY\%/$query/g;
    }
    else {
        $description = "Report matching $query\n";
    }

    my $subject;
    if ($rep{subject}) {
        chomp($subject = $rep{subject});
    }
    else {
        $subject = "Report $krep";
    }

    my @tickets = $ts->get_tickets_sorted($query, \@sorting);
    my $numtickets = scalar @tickets;

    if (! $numtickets) {
        #print "skipping $krep because there are no tickets to report on\n";
        next;
    }

    $description =~ s/\%NUMTICKETS\%/$numtickets/g;
    $subject =~ s/\%NUMTICKETS\%/$numtickets/g;


    my $html_message = "<body>" . html_section($description, @tickets) . "</body>\n";

    my $text_message = text_section($description, @tickets) . "\n";

    my @notify;
    if ($::DEBUG) {
        @notify = ($MAINTAINER);
    }
    else {
        @notify = as_array_split_comma($rep{notify});
    }
    my $top = MIME::Entity->build(Type    => "multipart/mixed",
                                  To      => join(',', @notify),
                                  From    => $FROM,
                                  Subject => $subject);
    $top->attach(Data=>$html_message,Type=>'text/html');
    $top->attach(Data=>$text_message,Type=>'text/plain');
    my $success = $top->smtpsend;

    die 'Unable to send mail!' if !$success;
}


