#!/usr/bin/env perl

use 5.014;
use utf8;
use warnings;
use Data::Dumper;
use File::Basename;
use Email::MIME;
use Encode qw/decode_utf8 encode_utf8/;

use Mojolicious::Lite;
use Mojo::JSON ();

# Config
# $git_dir must be an absolute path!
my $git_dir                  = '/var/spool/gitwebhook/';
#$git_dir                  = '/home/kfranken/projects/gitwebhook/tmp/'; # DEBUG
my $DEFAULT_mail_to          = 'appdev-d@noris.net';

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

get '/' => sub {
    app()->log->debug("GET /");
    my $self   = shift;
    my $status = 200;
    my $response =
      "OK - Dieser Service soll nur als Webhook von gitlab aufgerufen werden.";
    $self->render( text => "$response\n", status => $status );
};

post '/' => sub {
    app()->log->debug("POST /");
    my $self             = shift;
    my $whatchanged_only = $self->param('changed');
    my $mail_to          = $self->param('mail_to') || $DEFAULT_mail_to;
    my $mail_to_url      = $self->param('mail_to');

    my $body = $self->req->json;
    unless ($body) {
        return $self->err( 500,
            'Request body is not valid JSON (or wrong content type)' );
    }
    app()->log->debug( Dumper($body) );

    if ( $body->{object_kind} eq 'push' ) {
        my $git_ssh_url = $body->{repository}->{git_ssh_url};
        my $ref         = $body->{ref};
        my $repo        = $body->{repository}->{name};
        my $alt         = $body->{before};
        my $neu         = $body->{after};
        my $branch      = basename($ref);
        my $commits     = $body->{commits};
        my $commit_urls = '';

        for my $commit (@$commits) {
            $commit_urls .= "\n" . $commit->{url} if $commit->{url} =~ m/\/$neu/;
        }

        app()->log->debug("PUSH");
        app()->log->debug("Ref: $ref");
        app()->log->debug("Repo: $repo ($git_ssh_url)");
        app()->log->debug("Alt: $alt");
        app()->log->debug("Neu: $neu");
        app()->log->debug("Branch: $branch");
        app()->log->debug("Commit-URLs: $commit_urls");
        app()->log->info("post / : $repo, branch: $branch, $alt..$neu");

        # 0. chdir und git fetch
        git_chdir_fetch( $repo, $git_ssh_url );    # wechselt in den Ordner!

        # 1. Hook: Änderungen an POP-Datenbank
        if ( $ref eq 'refs/heads/master' or $ref eq 'refs/heads/bugfix' ) {
            my $changes = git_changes( $repo, $alt, $neu, $whatchanged_only );
            app()->log->debug( "Changes: " . Dumper($changes) );
            if ($changes) {
                my $mail_from   = 'git@noris.net';
                my $mail_header = << "EOT";
From: $mail_from
To: <$mail_to>
Subject: $whatchanged_only geaendert im $branch-Zweig
X-noris-Ticket-Autoreply: no
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8

EOT
                app()->log->info("Sending Changes-E-Mail to $mail_to");
                $mail_header =~ s/BRANCH/$branch/g;
                open my $MAIL, '|-', '/usr/sbin/sendmail', '-f', $mail_to,
                  $mail_to
                  or die app()->log->error("can't open sendmail-pipe: $!");
                print $MAIL $mail_header;
                print $MAIL $changes;
                print $MAIL $commit_urls;
                close $MAIL;
            }
        }

        # 2. Hook: Ticket-Nummern in Commit -> Mail an Ticket
        my %commits;
        my %order;
        my $cnt = 0;

        if ( $ref eq 'refs/heads/master' ) {
            open my $PIPE, '-|', 'git', 'log', '--oneline', "$alt..$neu", '--'
              or app()->log->error("can't open git log Pipe: $!");
            while (<$PIPE>) {
                chomp;
                app()->log->debug("git log: $_");
                my ( $sha1, $msg ) = split ' ', $_, 2;
                $order{$sha1} //= ++$cnt;
                if ( $msg =~ /^#(\d{6,13})/ ) {
                    my $ticket = $1;
                    $commits{$ticket}{$sha1} = 1;
                    app()->log->debug("commits{$ticket}{$sha1}");
                } elsif ( $msg =~ /^(NNIS-\d{4,6})/ ) {
                    my $ticket = $1;
                    $commits{$ticket}{$sha1} = 1;
                    app()->log->debug("commits{$ticket}{$sha1}");
                }
            }
            close $PIPE
              or app()->log->error("can't read from git log Pipe: $!");
            commit_mail2tickets( $repo, \%commits, \%order, $commit_urls,
                $mail_to_url );
        }
        app()->log->info("post / : $repo, branch: $branch, $alt..$neu OK");
    }

    $self->render( text => "OK\n", status => 200 );
};

sub commit_mail2tickets {
    my ( $program, $r_commits, $r_order, $commit_urls, $mail_to_default ) = @_;
    my %commits = %$r_commits;
    my %order   = %$r_order;
    $program = uc $program if $program eq 'otrs';

    for my $ticket ( keys %commits ) {
        my $h = $commits{$ticket};
        my @hashes = reverse sort { $order{$a} <=> $order{$b} } keys %$h;
        my $subject =
          @hashes == 1
          ? "Commit $hashes[0]"
          : 'Commits ' . join( ', ', map substr( $_, 0, 7 ), @hashes );
        my $otrs_domain = 'ticket.noris.de';
        my $jira_domain = 'jira.noris.de';
        my $mail_to;
        my $format_for_jira = 0;

        if ($ticket =~ m/^\w+-\d/) {
            $format_for_jira = 1;
        }
        if ($mail_to_default) {
            $mail_to = $mail_to_default
        } else {
            if ($ticket =~ m/^\w+-\d/) {
                $mail_to = "$ticket\@$jira_domain";
                $format_for_jira = 1;
            } else {
                $mail_to = "$ticket\@$otrs_domain";
            }
        }

        my $body = decode_utf8( join "\n\n",
            map { scalar qx/ git show --stat $_/ } @hashes );
        if ($format_for_jira) {
            $body = "{noformat}\n${body}{noformat}\n";
        }
        $body .= $commit_urls;
        app()->log->debug( "body: " . $body );

        my $cmd = @hashes == 1 ? $hashes[0] : join( ' ', @hashes[ 0, -1 ] );

        my $diff = qx{git diff-tree -p $cmd -- debian/changelog};
        my $version;
        if ( $diff && $diff =~ /^\@\@ -\d+,\d+ \+\d+,(\d+) \@\@/m ) {
            my $line_no = $1;
            my $file    = qx{git show $hashes[0]:debian/changelog};
            if ( $file =~ /^\S[^()]* \(([^()]+)\)/gc ) {
                $version = $1;
                if ( $file =~ /^\S/mgc ) {
                    $file = substr( $file, 0, pos($file) );
                }
            }
            my $lines = $file =~ tr/\n//;

            if ( $line_no > $lines ) {
                $version = undef;
            }
        }
        my @extra_args;
        if ( $version && ( $program eq 'kunde' || $program eq 'OTRS' ) ) {
            @extra_args = (
                'X-noris-Ticket-AppendInfo' =>
                  "Test in QSU ($program $version)",
                'X-noris-Ticket-Status' => 'parking',
            );
        }

        my $from        = 'entwicklung@noris.de';
        my $mail        = Email::MIME->create(
            header => [
                From                    => $from,
                To                      => $mail_to,
                'X-noris-Ticket-Number' => $ticket,
                Subject                 => $subject,
                @extra_args,
            ],
            attributes => {
                content_type => 'text/plain',
                charset      => 'UTF-8',
                encoding     => '8bit',
            },
            body_str => $body,
        );
        app()->log->info("Sending Commit-E-Mail to $mail_to");
        app()->log->debug( "E-Mail-Body: " . $mail->as_string );
        eval {
            #print "sende Mail von $from an $mail_to\n";
            open my $PIPE, '|-', '/usr/sbin/sendmail', '-i', '-f', $from,
              $mail_to
              or app()->log->error("Cannot open pipe to sendmail: $!");
            print $PIPE $mail->as_string
              or app()->log->error("Error while piping to sendmail: $!");
            close $PIPE
              or app()
              ->log->error("Error while closing pipe to sendmail: $! ($?)");
        };

        app()->log->error("Error sending E-Mail: $@") if $@;
    }
}

sub git_chdir_fetch {
    my ( $name, $git_ssh_url ) = @_;

    if ( not -d $git_dir ) {
        app()->log->error("git_dir=$git_dir ist kein Directory");
        return;
    }
    chdir($git_dir)
      or app()->log->error("can't cd $git_dir: $!");
    if ( not -d $name ) {

        # git clone
        system("git clone -q $git_ssh_url") == 0
          or app()->log->error("can't 'git clone $git_ssh_url': $?");
    }
    else {
        # git fetch
        chdir("$git_dir/$name")
          or app()->log->error("can't cd $git_dir/$name: $!");
        system("git fetch -q") == 0
          or app()->log->error("can't 'git fetch: $?");
    }
}

sub git_changes {
    my ( $name, $old, $new, $whatchanged_only ) = @_;
    return unless defined $whatchanged_only;

    chdir("$git_dir/$name")
      or app()->log->error("can't cd $git_dir/$name: $!");

    my $whatchanged_cmd = "git whatchanged -U30 -p $old..$new";
    $whatchanged_cmd .= " -- $whatchanged_only" if $whatchanged_only;

    my $changes = qx/$whatchanged_cmd/;
    return ($changes);
}

app()->log->path( $git_dir . '/gitwebhook.log' );
app()->log->level('info');

#app()->log->level('debug'); # sonst alles nur bis info # DEBUG

app->config(
    hypnotoad => {
        user     => 'gitwebhook',
        group    => 'gitwebhook',
        listen   => ['http://*:8080'],
        pid_file => '/var/run/gitwebhook.pid',
    }
);
app->start;

__DATA__

__END__

=encoding utf8

=head1 NAME

gitwebhook

=head1 SYNOPSE

- /etc/init.d/gitwebhook start|stop|status

- /usr/bin/hypnotoad /usr/bin/gitwebhook

- morbo -l http://*:8080 /usr/bin/gitwebhook

=head1 OPTIONEN

=over 4

=back

=head1 AUTOR

 Klaus Franken <klaus.franken@noris.de>
 für die noris network AG
 Ticket 21001811, https://gitlab.noris.net/cda-ad/gitwebhook

=head1 TODO

=cut

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

