package App::EdwardNG;

use 5.014000;
use strict;
use warnings;
use parent qw/Exporter/;
our $VERSION = '0.001';
our @EXPORT = qw/process_message/;

use Email::Sender::Simple qw/sendmail/;
use File::Slurp qw/read_file/;
use Getopt::Long;
use MIME::Entity;
use MIME::Parser;
use Mail::GnuPG;
use PerlX::Maybe;
use Template;
use Try::Tiny;

sub debug { say STDERR @_ if $ENV{EDWARDNG_DEBUG} }
sub stringify ($) { join '', map {; '>', $_ } @{$_[0]} }
sub mg {
	Mail::GnuPG->new(
		key                => $ENV{EDWARDNG_KEY},
		maybe always_trust => $ENV{EDWARDNG_ALWAYS_TRUST},
		maybe keydir       => $ENV{EDWARDNG_KEYDIR},
		maybe passphrase   => $ENV{EDWARDNG_PASSPHRASE},
		maybe use_agent    => $ENV{EDWARDNG_USE_AGENT},
		@_);
}

sub first_part{
	my ($ent) = @_;
	return first_part $ent->parts(0) if $ent->parts;
	$ent->bodyhandle->as_string
}

sub process_message {
	my ($msg) = @_;
	my $parser = MIME::Parser->new;
	$parser->decode_bodies(0);
	$parser->output_to_core(1);

	if (ref $msg eq 'MIME::Entity') {
		debug 'Got MIME::Entity';
	} elsif (ref $msg eq 'IO') {
		debug 'Parsing from filehandle';
		$msg = $parser->parse($msg)
	} elsif (ref $msg eq 'SCALAR') {
		debug 'Parsing from string';
		$msg = $parser->parse_data($$msg)
	} elsif (!ref $msg) {
		debug "Parsing from file $msg";
		$msg = $parser->parse_open($msg)
	} else {
		die "Don't know how to parse $msg"
	}

	if ($msg->mime_type ne 'multipart/signed' && $msg->mime_type ne 'multipart/encrypted') {
		# PGP/Inline requires decoding
		$parser->decode_bodies(1);
		$msg = $parser->parse_data($msg->stringify)
	}

	my $gpg = mg;
	if ($gpg->is_signed($msg)) {
		debug 'This mail looks signed';
		my ($code, $keyid, $email) = $gpg->verify($msg);
		return sign_error => (
			message => stringify $gpg->{last_message}) if $code;
		return sign => (
			keyid   => $keyid,
			email   => $email,
			message => stringify $gpg->{last_message});
	}

	if ($gpg->is_encrypted($msg)) {
		debug 'This mail looks encrypted';
		my ($code, $keyid, $email) = $gpg->decrypt($msg);
		return encrypt_error => (
			message   => stringify $gpg->{last_message}) if $code;
		return encrypt => (
			plaintext => stringify $gpg->{plaintext},
			decrypted => $gpg->{decrypted},
			message   => stringify $gpg->{last_message}) unless defined $keyid;
		return signencrypt => (
			keyid     => $keyid,
			email     => $email,
			plaintext => stringify $gpg->{plaintext},
			decrypted => $gpg->{decrypted},
			message   => stringify $gpg->{last_message});
	}

	debug 'This mail doesn\'t seem to be signed or encrypted';
	return 'plain'
}

sub run {
	GetOptions(
		'always-trust!' => \$ENV{EDWARDNG_ALWAYS_TRUST},
		'debug!'        => \$ENV{EDWARDNG_DEBUG},
		'from=s'        => \$ENV{EDWARDNG_FROM},
		'key=s'         => \$ENV{EDWARDNG_KEY},
		'keydir=s'      => \$ENV{EDWARDNG_KEYDIR},
		'passphrase=s'  => \$ENV{EDWARDNG_PASSPHRASE},
		'use-agent!'    => \$ENV{EDWARDNG_USE_AGENT},
	);

	my $parser = MIME::Parser->new;
	$parser->decode_bodies(0);
	$parser->output_to_core(1);
	my $in = $parser->parse(\*STDIN);

	my ($tmpl, %params);
	try {
		($tmpl, %params) = process_message $in
	} catch {
		($tmpl, %params) = (error => message => $_)
	};

	$params{plaintext} = first_part $params{decrypted} if $params{decrypted};

	my $tt = Template->new(INCLUDE_PATH => 'tmpl/en');
	my $data;
	$tt->process($tmpl, \%params, \$data);
	my $email = MIME::Entity->build(
		From    => $ENV{EDWARDNG_FROM},
		To      => $in->get('From'),
		Subject => 'Re: ' . $in->get('Subject'),
		Data    => $data);

	my $mg = mg always_trust => 1;
	$mg->mime_signencrypt($email, $in->get('From') =~ /<(.*)>/) and debug 'Could not encrypt message. GnuPG said ', stringify $mg->{last_message};
	sendmail $email
}

1;
__END__

=encoding utf-8

=head1 NAME

App::EdwardNG - GnuPG email sign/encrypt testing bot

=head1 SYNOPSIS

  use App::EdwardNG;
  my ($status, %params) = process_message '/path/to/message';
  if ($status eq 'signencrypt') {
    say 'This message is encrypted and signed with key ', $params{keyid}, ' from ', $params{email};
    say 'Its contents are: ', $params{plaintext};
  } elsif ($status eq 'encrypt') {
    say 'This message is encrypted but not signed';
    say 'Its contents are: ', $params{plaintext};
  } elsif ($status eq 'encrypt_error') {
    say 'This message is encrypted but I was unable to decrypt it. GnuPG output: ', $params{message};
  } elsif ($status eq 'sign') {
    say 'This message is signed with key ', $params{keyid}, ' from ', $params{email};
  } elsif ($status eq 'sign_error') {
    say 'This message is signed but I was unable to verify the signature. GnuPG output: ', $params{message};
  } elsif ($status eq 'plain') {
    say 'This message is neither signed nor encrypted';
  } elsif ($status eq 'error') {
    say 'There was an error processing the message: ', $params{message};
  }

=head1 DESCRIPTION

EdwardNG is a reimplementation of the Edward reply bot referenced in L<https://emailselfdefense.fsf.org/>.

It takes mail messages, checks them for PGP signatures and encryption, then replies appropriately.

This module exports a single function, B<process_message>, which takes a single parameter representing the message. This parameter can be:

=over

=item A filehandle reference, e.g. C<\*STDIN>.

=item A reference to a scalar which holds the message contents.

=item A scalar which represents a path to a message.

=item A L<MIME::Entity> object

=back

The function returns a status followed by a hash. Possible results:

=over

=item plain

The message is neither signed nor encrypted.

=item sign_error, message => $message

The message is signed but the signature could not be verified. GnuPG output is $message.

=item sign, keyid => $keyid, email => $email, message => $message

The message is signed with key $keyid from $email. GnuPG output is $message.

=item encrypt_error, message => $message

The message is encrypted and unable to be decrypted. GnuPG output is $message.

=item encrypt, plaintext => $plaintext, decrypted => $decrypted, message => $message

The message is encrypted and unsigned. $plaintext is the decrypted message as plain text, while $decrypted is a MIME::Entity representing the decrypted message. GnuPG output is $message.

=item signencrypt, plaintext => $plaintext, decrypted => $decrypted, keyid => $keyid, email => $email, message => $message

The message is encrypted and signed with key $keyid from $email. $plaintext is the decrypted message as plain text, while $decrypted is a MIME::Entity representing the decrypted message. GnuPG output is $message.

=item error, message => $message

There was an error while processing the message. The error can be found in $message.

=back

=head1 ENVIRONMENT

This module is configured via the %ENV hash. See the L<edwardng(1)> manpage for more information.

=head1 SEE ALSO

L<edwardng(1)>

=head1 AUTHOR

Marius Gavrilescu, E<lt>marius@ieval.roE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2014 by Fundația Ceata

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.18.2 or,
at your option, any later version of Perl 5 you may have available.


=cut
