use 5.010;
use utf8;
use strict;
use warnings;

# application / package version
our $VERSION = 2.8;
# API Version, only needs to be incremented when the REST API changes
our $MAJOR_VERSION = 2;
our $MINOR_VERSION = 0;

use Mojolicious::Lite;
use noris::SMS;
use Data::Dumper;
use Config::INI;
use Config::INI::Reader;
use Mojo::JSON ();
use noris::Mojo::Log;
use DBI;
use Date::Simple qw(today);
use Email::MIME;

use Mojolicious::Plugin::BasicAuth;

my @required_attr = qw/receiver text/;
my $Config = Config::INI::Reader->read_file(
    $ENV{SMS_REST_CONFIG} || 'sms-rest.ini'
);

# preprocessing:
# turn { 'users.somewhere' => { a => '42' } }
# into { 'users' => { a => { value => '42', password => 'somewhere' } } }
{
    my %split_by_customer = (
        users           => 1,
        'stats-users'   => 1,
    );
    for my $k (sort keys %$Config) {
        my @chunks = split /\./, $k, 2;
        next if @chunks < 2;
        my ($section, $customer) = @chunks;
        if ($split_by_customer{ $section } ) {
            my $subhash = delete $Config->{$k};
            for my $sk (sort keys %$subhash) {
                if (exists $Config->{$section}{$sk}) {
                    die qq[Duplicate $section "$sk" (via section $k)\n];
                }
                $Config->{$section}{$sk} = {
                    password    => $subhash->{$sk},
                    customer    => $customer,
                };
            }
        }
    }
    for my $k (sort keys %$Config) {
        if ($k =~ /^soft_quota\.(.*)/) {
            $Config->{soft_quota}{$1} = delete $Config->{$k};
        }
    }
}


plugin 'basic_auth';
app()->config( hypnotoad => {
    listen  => [ 'http://127.0.0.1:' . ($Config->{server}{port} || 8080) ],
});

app()->log->path($Config->{server}{log} || 'sms-rest.log');

helper 'err', sub {
    my ($c, $status, $message, @rest) = @_;
    $c->render(
        status => $status,
        json   => {
            success => Mojo::JSON->false,
            message => $message,
            @rest,
        },
    );
};

helper 'dbhandle', sub {
    state $dbh;
    return $dbh if $dbh && $dbh->ping;
    my $dbconfig = $Config->{db};
    die "No db config!\n" unless $dbconfig;
    $dbh = DBI->connect(
        sprintf('DBI:mysql:database=%s;host=%s', $dbconfig->{database}, $dbconfig->{host}),
        $dbconfig->{user},
        $dbconfig->{password},
        {RaiseError => 1, AutoCommit => 1},
    );
    return $dbh;
};

helper 'log_count', sub {
    my ($c, $count) = @_;
    my $user = $c->stash('user')
        or die "User nicht bekannt?!";
    my $customer = $c->stash('customer')
        or die "Kunde (zu user $user) nicht bekannt?!";
    my $sth = $c->dbhandle->prepare_cached('CALL log_sms(?, ?, ?)');
    $sth->execute($customer, $user, $count);
    $sth->finish;
};

helper 'acct_logger', sub {
    state $logger;
    return $logger if $logger;
    $logger = noris::Mojo::Log->new(
            path    => ($Config->{env}{acct} || 'sms-rest-acct.log'),
            level   => 'info',
    );
    return $logger;
};

my %soft_quota_notified;
helper check_soft_quota => sub {
    my $c = shift;
    my $customer = $c->stash('customer');
    my $quota = $Config->{soft_quota}{$customer}{limit};
    return unless $quota;
    return if $soft_quota_notified{$customer};
    my $today = today();
    my $this_month = sprintf '%04d-%02d-00', $today->year, $today->month;
    my ($used)     = $c->dbhandle->selectrow_array(
            'SELECT `count` FROM sms_per_month WHERE month = ? AND customer = ?',
            undef,
            $this_month,
            $customer
    );
    if ($used && $used > $quota) {
        my ($notified, $customer_id) = $c->dbhandle->selectrow_array( <<SQL, undef, $this_month, $customer);
            SELECT COUNT(*), customer.id
              FROM soft_quota_report_sent
              JOIN customer ON customer.id = soft_quota_report_sent.customer
              WHERE soft_quota_report_sent.month = ? AND customer.name = ?
SQL
        if ($notified) {
            $soft_quota_notified{$customer} = 1;
            return;
        }

        my $recipient = $Config->{soft_quota}{$customer}{recipient};
        unless ($recipient) {
            warn "Cannot send notification mail: no recipient configured!";
            return $recipient;
        }
        $c->send_soft_quota_email(
            recipient   => $recipient,
            used        => $used,
            quota       => $quota,
        );

        $c->dbhandle->do(
            'INSERT INTO soft_quota_report_sent (month, customer, sent_to) VALUES (?, ?, ?)',
            undef,
            $this_month,
            $customer_id,
            $recipient,
        );
        $soft_quota_notified{$customer} = 1;
    }
};

helper send_soft_quota_email => sub {
    my ($c, %params) = @_;
    my $fqdn = $Config->{server}{fqdn} || qx/hostname -f/;
    chomp $fqdn;
    my $mail = Email::MIME->create(
        header  => [
            From    => '"noris networkg AG" <support@noris.de>',
            To      => $params{recipient},
            Subject => "Das SMS-Gateway hat die Warnschwelle erreicht",
            'Content-Type'  => 'text/plain; charset=UTF-8',
        ],
        attributes => {
            content_type    => 'text/plain',
            charset         => 'UTF-8',
            encoding        => '8bit',
        },
        body_str => <<MAIL
Sehr geehrte Damen und Herren,

die Warngrenze von $params{quota} versandten SMS durch die HTTP-Schnittstelle
auf $fqdn wurde erreicht.

Mit freundlichen Grüssen,
ihr noris network Support Team.

-- 
noris network AG - Thomas-Mann-Straße 16-20 - D-90471 Nürnberg -
Tel +49-911-9352-0 - Fax +49-911-9352-100
http://www.noris.de - The IT-Outsourcing Company

Vorstand: Ingo Kraupa (Vorsitzender), Joachim Astel, Hansjochen Klenk -
Vorsitzender des Aufsichtsrats: Stefan Schnabel - AG Nürnberg HRB 17689
MAIL
    );
    open my $PIPE, '|-', '/usr/sbin/sendmail', '-t', '-i', '-f', 'support@noris.de'
        or die "Cannot open pipe to sendmail: $!";
    print $PIPE $mail->as_string
        or die "Error while piping to sendmail: $!";
    close $PIPE
        or die "Error while closing pipe to sendmail: $! ($?)";

};

get '/version', sub {
    shift->render(
        json => {
            application   => 'noris SMS REST interface',
            major_version => $MAJOR_VERSION,
            minor_version => $MINOR_VERSION,
            application_version => "$VERSION",
        },
    );
};

get '/ping', sub {
    my $c = shift;
    my $res = eval {
        my ($x) = $c->dbhandle->selectrow_array(qq[SELECT 'OK']);
        $x;
    };
    my $status   = $res ? 200 : 500;
    my $response = $res || "Error: $@";
    $c->render(text => "$response\n", status => $status);
};

post '/sms' => sub {
    my $c = shift;

    my $auth = $c->basic_auth(
            realm => sub {
                my ($user, $password) = @_;
                if (exists $Config->{users}{$user}
                            && $Config->{users}{$user}{password} eq $password) {
                    $c->stash(user     => $user);
                    $c->stash(customer => $Config->{users}{$user}{customer});
                    return 1;
                }

                return 0;
            }
    );
    unless ($auth) {
        return $c->err(401, 'Authentication required');
    }
    my $body = $c->req->json;
    unless ($body) {
        return $c->err(500, 'Request body is not valid JSON (or wrong content type)');
    }

    my @missing = grep !exists $body->{$_}, @required_attr;

    if (@missing) {
        return $c->err(
            500,
            "Required attributes missing\n",
            missing => \@missing,
        );
    }

    my $orig    = $body->{sender} || $Config->{sms}{sender} || 'noris';
    my $max_sms = $body->{max_count} || $Config->{sms}{max} || 10;

    my $sms = noris::SMS->new(
            orig    => $orig,
            dest    => $body->{receiver},
            data    => $body->{text},
            max_sms => $max_sms,
    );

    my $sent = $sms->send();
    my $count_sent = $sent->sent;

    if (defined $sms->errstr) {
        return $c->err(404, $sms->errstr);
    }
    $c->acct_logger->info(
        customer    => scalar $c->stash('customer'),
        receiver    => $body->{receiver},
        count       => $count_sent,
    );

    $c->render(json => {success => Mojo::JSON->true, sms_sent => $count_sent });
    $c->log_count($count_sent);
    $c->check_soft_quota();
};

get '/' => sub { shift->redirect_to('/stats') };

get '/stats' => sub {
    my $c = shift;
    my $customer;
    my $auth = $c->basic_auth(
            realm => sub {
                my ($user, $password) = @_;
                return 0 unless defined $user;
                if (exists $Config->{'stats-users'}{$user}
                            && $Config->{'stats-users'}{$user}{password} eq $password) {
                    $customer = $Config->{'stats-users'}{$user}{customer};
                    return 1;
                }

                return 0;
            }
    );
    unless ($auth) {
        return $c->err(401, 'Authentication required');
    }

    my $sth = $c->dbhandle->prepare_cached(
        'SELECT username, month, count FROM sms_per_month_and_user WHERE customer = ? ORDER BY month, username'
    );
    $sth->execute($customer);

    my %months;
    my %month_total;
    my %all_users;

    $sth->bind_columns(\(my ($username, $month, $count)));
    while ($sth->fetchrow) {
        $month                     = substr $month, 0, 7;
        $months{$month}{$username} = $count;
        $all_users{$username}      = 1;
        $month_total{$month}      += $count;
    }
    my @months =         sort keys %months;
    my @users  = reverse sort keys %all_users;

    $sth->finish;

    $c->render(
        months  => \@months,
        users   => \@users,
        stats   => \%months,
    );
};

app->start;

__DATA__

@@ stats.html.ep
<html>
<head>
    <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
    <title>Benutzungstatistiken der SMS REST API</title>
    <style type="text/css">
        body {
            font-family: verdana,arial,sans-serif;
            background-color: white;
            font-size: 120%;
        }
        table {
            color:#333333;
            border-width: 1px;
            border-color: #666666;
            border-collapse: collapse;
        }
        table th {
            border-width: 1px;
            padding: 8px;
            border-style: solid;
            border-color: #666666;
            background-color: #dedede;
        }
        table td {
            border-width: 1px;
            padding: 8px;
            border-style: solid;
            border-color: #666666;
            background-color: #ffffff;
        }
    </style>
</head>
<body>
    <h1>Benutzungstatistiken der SMS REST API</h1>
    <p>Pro Monat und Userkennung</p>
    <table>
        <thead>
            <tr>
                <th>Monat</th>
                % for my $u (@$users) {
                    <th><%= $u %></th>
                %}
            </tr>
        </thead>
        <tbody>
            % for my $month (@$months) {
                <tr>
                    <th><%= $month %></th>
                    % for my $u (@$users) {
                        <td><%= $stats->{$month}{$u} // 0 %></td>
                    % }
                </tr>
            % }
        </tbody>
    </table>

    <p>Bereitgestellt durch die noris network AG, <a href="mailto://support@noris.de">support@noris.de</a>.</p>
</body>
</html>

@@ favicon.ico (base64)
AAABAAEAICAAAAEAGACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGAAAAAAAAAAAACwBAAAsAQAAAAAA
AAAAAAD////+/v7m5ubExMS4uLi4uLi4uLi4uLi4uLi4uLi3t7e1tbW0tLSzs7OysrKxsbGxsbGy
srKzs7O1tbW2tra3t7e4uLi4uLi4uLi4uLi4uLi4uLi1tbXJycn6+vr////8/Pyvr6+enp7c3Nz7
+/v////////////////////4+Pjs7Ozf39/c3Nzb29vY2Nja2trb29vc3Nzm5ubz8/P/////////
///////////8/Pzy8vLk5OSTk5NgYGD4+PjX19e9vb3///////+ysrJ7e3t2dnZ1dXV3d3eAgIBy
cnL////////////////+/v7///////////////+UlJSioqKnp6enp6empqaYmJjDw8P/////////
//+Ghoaurq7ExMT///////////8lJSWnp6eqqqqmpqaoqKi+vr4UFBTh4eH////5+fn4+Pj39/f3
9/f4+Pj5+fn///8AAAAAAAAAAAAAAAAAAAAAAAA6Ojr////////////Y2NiEhITJycn/////////
//8YGBj///////////////////8+Pj7n5+f////29vb19fX19fX19fX39/f29vb///8AAAABAQEI
CAgHBwcGBgYAAABRUVH////////////y8vJ9fX3Jycn///////////8VFRX////////8/Pz5+fn/
//83Nzfl5eX////29vb09PT09PT09PT09PT19fX///8AAAAFBQUKCgoJCQkICAgAAABSUlL/////
///////x8fF9fX3Jycn///////////8XFxf////////4+Pj39/f///84ODjh4eH////09PT19fXz
8/Pz8/Pz8/P09PT///8AAAAICAgNDQ0NDQ0KCgoAAABSUlL////////////x8fF9fX3Jycn/////
//////8YGBj////////29vb19fX///86Ojrg4OD////19fX19fX19fX19fX19fX09PT///8AAAAP
Dw8REREPDw8ODg4AAABRUVH////////////v7+99fX3Jycn///////////8aGhr////+/v719fX0
9PT///88PDzg4OD+/v7z8/P19fX09PT09PT19fXy8vL///8AAAATExMXFxcUFBQPDw8AAABRUVH/
///////////u7u59fX3IyMj///////////8cHBz////9/f329vb09PT///8+Pj7f39/////09PT0
9PT19fX19fX09PT09PT///8AAAAZGRkbGxsXFxcREREAAABTU1P////////////p6el9fX3Hx8f/
//////////8fHx/////7+/v09PT19fX///8/Pz/g4OD////19fX29vb4+Pj4+Pj29vb19fX///8A
AAAdHR0eHh4YGBgVFRUAAABSUlL////////////o6Oh8fHzFxcX///////////8hISH////8/Pz0
9PT19fX///9AQEDj4+P////39/f4+Pj6+vr6+vr5+fn39/f///8CAgIiIiIfHx8aGhoXFxcAAABU
VFT////8/Pz////l5eV7e3vExMT///////////8lJSX////6+vr19fXz8/P///9CQkLk5OT////5
+fn6+vr9/f37+/v6+vr5+fn///8FBQUlJSUiIiIeHh4YGBgAAABTU1P////7+/v////j4+N6enrD
w8P////+/v7///8nJyf////6+vrz8/P19fX///9DQ0Pj4+P////9/f3+/v7////////+/v78/Pz/
//8JCQkoKCglJSUfHx8aGhoAAABUVFT////6+vr////i4uJ5eXnBwcH////9/f3///8pKSn////6
+vr19fX19fX///9ERETk5OT////+/v7////////////////+/v7///8MDAwtLS0oKCgiIiIcHBwA
AABUVFT////5+fn////f3995eXnAwMD////8/Pz///8rKyv////6+vr19fX09PT///9GRkbj4+P/
//////////////////////////////8PDw8xMTEsLCwmJiYgICACAgJVVVX////29vb////e3t54
eHjAwMD////8/Pz///8rKyv////4+Pj19fX09PT///9GRkbj4+P/////////////////////////
//////8QEBA0NDQtLS0mJiYhISEEBARVVVX////29vb////c3Nx4eHjBwcH////9/f3///8pKSn/
///4+Pjz8/P09PT///9GRkbj4+P////////////////////////+/v7///8NDQ0uLi4pKSkkJCQd
HR0BAQFUVFT////4+Pj////g4OB4eHjCwsL////+/v7///8pKSn////6+vr19fX19fX///9HR0fj
4+P////9/f3////////////+/v7+/v7///8MDAwsLCwmJiYgICAbGxsAAABUVFT////5+fn////h
4eF5eXnDw8P///////////8pKSn9/f38/Pzz8/P19fX///9oaGi6urr////6+vr7+/v////9/f38
/Pz///////8AAAApKSkjIyMdHR0aGhoAAABTU1P////5+fn////h4eF6enrFxcX///////////9D
Q0Pj4+P////19fXz8/P////i4uIjIyP////////5+fn6+vr5+fn9/f3///+0tLQPDw8nJycgICAb
GxsWFhYAAABpaWn////8/Pz////k5OR7e3vGxsb///////////97e3unp6f////09PTz8/P4+Pj/
//9LS0t0dHT////////////////////g4OACAgIgICAkJCQfHx8aGhoVFRUAAACqqqr////9/f3/
///m5uZ8fHzIyMj////////////n5+cyMjL////19fX19fXz8/P///////90dHQAAAB6enr/////
//+Pj48AAAAbGxshISEeHh4cHBwWFhYNDQ0AAAD////////+/v7////p6el9fX3Jycn/////////
//////8AAAD////////09PT19fXz8/P+/v7///////8DAwP///////+JiYkBAQEeHh4cHBwZGRkY
GBgVFRUAAAAeHh7////////////////s7Ox9fX3Jycn///////////////+xsbFNTU3////7+/v0
9PT09PT19fXz8/P///8AAAD///////+FhYUAAAAZGRkXFxcWFhYUFBQGBgYAAADp6en////+/v7/
///////v7+99fX3Jycn///////////////////8jIyO8vLz////9/f319fX09PT19fX///8AAAD/
//////+EhIQAAAAWFhYUFBQSEhIGBgYAAABnZ2f////////////////////x8fF+fn7Jycn/////
//////////////////8CAgK/v7/////////4+Pj29vb///8AAAD///////+CgoIAAAASEhINDQ0C
AgIAAAA7Ozv////////9/f3////////////x8fF9fX3Kysr///////////////////////////8c
HBxYWFj///////////////8AAAD///////9+fn4AAAAGBgYAAAAAAAB0dHT/////////////////
///////////v7+9+fn7Z2dn///////////////////////////////+ioqIAAABFRUWzs7P///8D
AwP///////9oaGgAAAAAAAAwMDD5+fn////////////////////////////////t7e2Pj4/u7u7/
///////////////////////////////////////R0dFnZ2crKyttbW3///////+qqqqQkJD/////
///////////////////////////////////////AwMC4uLj+/v74+Pj/////////////////////
////////////////////////////////////////////////////////////////////////////
///////////s7OyXl5f6+vr////+/v79/f38/Pz7+/v7+/v7+/v7+/v7+/v6+vr6+vr5+fn4+Pj4
+Pj39/f29vb39/f39/f4+Pj5+fn5+fn6+vr6+vr7+/v7+/v7+/v7+/v7+/v29vbo6Oj8/Pz///8A
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAA==

<!--  vim: ft=perl expandtab sw=4 ts=4
-->

