#!/usr/bin/perl

use strict;
use warnings;

use English;
use Getopt::Long;
use Pod::Usage;
use NGCP::API::Client;
use Readonly;
use XML::Simple;
use Template;
use Email::Sender::Simple qw();
use Email::Simple;
use Email::Simple::Creator;
use Email::Sender::Transport::Sendmail qw();

Readonly my @required => qw();
Readonly my $config_file => '/etc/ngcp-panel/provisioning.conf';

my $page_size = 10;

my $opts = {
    verbose => 0,
};

my $config;

GetOptions($opts,
    'help|h' => sub { usage() },
    'verbose',
) or usage();

sub check_params {
    my @missing;

    foreach my $param (@required) {
        push @missing, $param unless $opts->{$param};
    }
    usage(join(' ', @missing)) if scalar @missing;

    return;
}

sub usage {
    my $missing = shift;

    pod2usage(
        -exitval => $missing ? 1 : 0,
        -verbose => 1,
        -message => $missing ? "Missing parameters: $missing" : '',
    );

    return;
}

sub load_config {
    $config = XML::Simple->new()->XMLin(
        $config_file,
        ForceArray => [ 'credit_warnings' ],
    ) or die "Cannot load config: $config_file: $ERRNO\n";

    return;
}

sub send_email {
    my ($cwarning, $contracts) = @_;

    my $template = get_email_template() || return;

    my $vars = {
        domain    => $cwarning->{domain},
        threshold => $cwarning->{threshold},
        adminmail => $config->{adminmail},
    };

    foreach my $data (@{$contracts}) {
        $vars->{contracts} .= sprintf ('contract_id: %s cash_balance: %s', @{$data}{qw(id cash_balance)});
        $vars->{contracts} .= ' external_id: '. $data->{external_id} if $data->{external_id};
        $vars->{contracts} .= ' threshold: '. $data->{threshold} if $data->{threshold};
        $vars->{contracts} .= "\n";
    }

    my $tt = Template->new();
    foreach my $field (keys %{$template}) {
        my $out;
        $tt->process(\$template->{$field}, $vars, \$out);
        $template->{$field} = $out if $out;
    }

    my $transport = Email::Sender::Transport::Sendmail->new;
    my $email = Email::Simple->create(
        header => [
            To      => ref $cwarning->{recipients} eq 'ARRAY'
                       ? join(', ', @{$cwarning->{recipients}})
                       : $cwarning->{recipients},
            From    => $template->{from_email},
            Subject => $template->{subject},
        ],
        body => $template->{body},
    );

    Email::Sender::Simple->send($email, { transport => $transport })
        or die sprintf "Cannot send credit warning notification to %s: $ERRNO\n",
                       $email->header('To');
    return;
}

sub get_data {
    my ($uri, $link, $process_code) = @_;

    $process_code = sub { return 0 } if 'CODE' ne ref $process_code;

    my $client = NGCP::API::Client->new(
        verbose => $opts->{verbose},
        page_rows => $page_size,
    );

    my @result = ();
    while (my $res = $client->next_page($uri)) {
        return [] unless check_api_error($res);
        my $res_hash = $res->as_hash;
        my $data = $res_hash->{_embedded}{'ngcp:' . $link};
        if ('ARRAY' eq ref $data) {
            unless ($process_code->($data)) {
                push @result, @{$data};
            }
        } elsif ($data) {
            unless ($process_code->([ $data ])) {
                push @result, $data;
            }
        }
    }
    return \@result;
}

sub check_api_error {
    my $res = shift;
    if ($res->is_success) {
        return 1;
    } else {
        die($res->result . "\n") if (
            $res->is_client_error
            or ($res->is_server_error and not scalar grep { $_ == $res->code; } qw(502 503 504))
        );
        return 0;
    }
}

sub get_email_template {
    my $templates_data = get_data('/api/emailtemplates/', 'emailtemplates');
    foreach my $template (@{$templates_data}) {
        next unless $template->{name} eq 'credit_warning_default_email';
        return $template;
    }
    return;
}

sub main {
    check_params();
    load_config();

    my $cwarnings = ref $config->{credit_warnings} eq 'ARRAY'
                    ? $config->{credit_warnings}
                    : [ $config->{credit_warnings} ];
    foreach my $cwarning (@{$cwarnings}) {
        unless ($cwarning->{recipients}) {
            die "No recipients defined for domain: $cwarning->{domain}\n";
        }
        unless ($cwarning->{domain}) {
            die "Missing domain in a credit warning check\n";
        }
        my @contracts;
        get_data(sprintf('/api/customerbalances/?no_count=true&domain=%s&prepaid=1',
            $cwarning->{domain}),
            'customerbalances',
            sub {
                my $balances = shift;
                foreach my $balance (@{$balances}) {
                    my $ratio = $balance->{ratio} // 1.0;
                    my $threshold = $cwarning->{threshold} * $ratio;
                    next if $balance->{cash_balance} >= $threshold;
                    my $data = {
                        map { $_ => $balance->{$_} } qw(id cash_balance external_id)
                    };
                    $data->{threshold} = $threshold if $ratio < 1.0;
                    push @contracts, $data;
                }
                return 1;
            }
        );
        if (@contracts) {
            eval {
                send_email($cwarning, \@contracts);
            };
            print $EVAL_ERROR if $EVAL_ERROR;
        }
    }

    return 0;
}

exit main();

__END__

=head1 NAME

ngcp-credit-warning - checks for contract balances above credit warning thresholds

=head1 SYNOPSIS

B<ngcp-credit-warning> [I<options>...]

=head1 DESCRIPTION

B<This program> checks for contract balances above credit warning thresholds
and sends email notifications about the incidents.

=head1 OPTIONS

=over 8

=item B<--verbose>

Show additional debug information. Default false.

=item B<--help>

Print a brief help message.

=back

=head1 EXIT STATUS

=over

=item B<0>

Command completed successfully.

=item B<1>

Command failed with errors.

=back

=head1 SEE ALSO

NGCP::API::Client

=head1 BUGS AND LIMITATIONS

Please report problems you notice to the Sipwise
Development Team <support@sipwise.com>.

=head1 AUTHOR

Kirill Solomko <ksolomko@sipwise.com>

=head1 LICENSE

Copyright (C) 2016 Sipwise GmbH, Austria

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
