#!/usr/bin/perl
#
# DW::EmailPost::Comment
#
# Reply to a comment via email
#
# Authors:
#      Afuna <coder.dw@afunamatata.com>
#
# Copyright (c) 2013 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.

package DW::EmailPost::Comment;

use base qw(DW::EmailPost::Base);
use strict;

use Digest::SHA;

use LJ::Utils qw(rand_chars);
use LJ::Protocol;
use DW::CleanEmail;
use LJ::Comment;

=head1 NAME

DW::EmailPost::Comment - Handle comment replies via email

=HEAD1 SYNOPSIS

    # when generating the email:
    DW::EmailPost::Comment->replyto_address( $poster_u, $journal_u, $ditemid, $dtalkid );
=cut

sub _find_destination {
    my ( $class, @to_addresses ) = @_;

    foreach my $dest (@to_addresses) {
        next unless $dest =~ /^
                                (\S+?)          # any prefix
                                \@\Q$LJ::EMAIL_REPLY_DOMAIN\E
                                $
                                /ix;
        return $1;
    }
    return;
}

# Checks the validity of the replyto (left side of the email address).
# String is in the format of: userid-journalid-ditemid-dtalkid-auth
# Return 1 if success
sub _parse_destination {
    my ( $self, $replyto_token ) = @_;

    my ( $remote_userid, $journalid, $ditemid, $dtalkid, $message_auth ) = split "-",
        $replyto_token;

    my $u = LJ::load_userid($remote_userid);
    return unless $u;

    my $ju = LJ::load_userid($journalid);
    return unless $ju;

    $self->{u}       = $u;
    $self->{ju}      = $ju;
    $self->{journal} = $ju->user;

    # shouldn't happen but just in case
    return unless $u->emailpost_auth;

    my $class     = ref $self;
    my $real_auth = $class->_hash( $u, $ju, $ditemid, $dtalkid );
    return $self->err("Invalid secret address. Unable to post as $u->{user}.")
        unless $message_auth eq $real_auth;

    $self->{ditemid} = $ditemid;
    $self->{parent}  = $dtalkid;

    return 1;
}

sub _process {
    my $self = $_[0];

    $self->_check_sender or return $self->send_error;

    $self->_set_props( $self->{u}, %{ $self->{post_headers} || {} } );

    # remove reply cruft
    $self->{body} = DW::CleanEmail->nonquoted_text( $self->{body} );
    $self->{subject} =
        DW::EmailPost::Comment->determine_subject( $self->{subject}, $self->{ju}, $self->{ditemid},
        $self->{parent} );

    $self->cleanup_body_final;

    # build the comment
    my $req = {
        ver => 1,

        username => $self->{u}->username,
        journal  => $self->{ju}->username,
        ditemid  => $self->{ditemid},
        parent   => $self->{parent},

        body                 => $self->{body},
        subject              => $self->{subject},
        prop_picture_keyword => $self->{props}->{picture_keyword},
        prop_editor          => $self->{props}->{editor},

        useragent => "emailpost",
    };

    # post!
    my $post_error;
    LJ::Protocol::do_request( "addcomment", $req, \$post_error, { noauth => 1, nocheckcap => 1 } );
    return $self->send_error( LJ::Protocol::error_message($post_error) ) if $post_error;

    return ( 1, "Comment success" );

}

# check that this either from their raw address, or one of their allowed senders
# we use raw address so that they don't need to do additional setup to reply as mobile post
sub _check_sender {
    my $self = $_[0];

    my $addrlist = LJ::Emailpost::Web::get_allowed_senders( $self->{u}, 1 );

    # well this shouldn't happen because we include their raw email in the allowed senders
    unless ( ref $addrlist && keys %$addrlist ) {
        return $self->err( "No allowed senders have been saved for your account.",
            { nomail => 1 } );
    }

    my $from = $self->{from};
    return $self->err("Unauthorized sender address: $from")
        unless grep { lc $from eq lc $_ } keys %$addrlist;

    return 1;
}

sub _set_props {
    my ( $self, $u, %post_headers ) = @_;

    my $props = {};
    $props->{picture_keyword} = $post_headers{userpic}
        || $post_headers{icon};
    $props->{editor} = $self->_choose_editor( $post_headers{format} );

    $self->{props} = $props;

    return 1;
}

sub dblog_opts {
    return ( t => "reply" );
}

=head1 Class Methods

=cut

# Generates the hash to be used in the reply to address
sub _hash {
    my ( $class, $u, $ju, $ditemid, $dtalkid ) = @_;
    return Digest::SHA::sha256_hex( $ju->id . $ditemid . $dtalkid . $u->emailpost_auth );
}

=head2 C<< $class->replyto_address( $u, $journal, $ditemid, $dtalkid ) >>

Get the reply-to address for this user + journal + entry + comment combination

=cut

sub replyto_address {
    my ( $class, $u, $journalu, $ditemid, $dtalkid ) = @_;
    return join( "-",
        $u->userid, $journalu->userid, $ditemid, $dtalkid,
        $class->_hash( $u, $journalu, $ditemid, $dtalkid ) )
        . "\@$LJ::EMAIL_REPLY_DOMAIN";
}

=head2 C<< $class->replyto_address_header( $u, $journal, $ditemid, $dtalkid ) >>

Returns the reply-to address with a pretty name, suitable for use in the reply-to-address header

=cut

sub replyto_address_header {
    my ( $class, $u, $journal, $ditemid, $dtalkid ) = @_;

    my $reply_as = LJ::Lang::get_default_text(
        "emailpost.reply.address",
        {
            user => $u->display_username,
        }
    );
    my $email = $class->replyto_address( $u, $journal, $ditemid, $dtalkid );
    return qq{"$reply_as" <$email>};
}

=head2 C<< $class->determine_subject( $email_subject, $ju, $ditemid, $parent ) >>

Decide what the subject should be (either from the email or the parent comments)

=cut

sub determine_subject {
    my ( $class, $email_subject, $ju, $ditemid, $parent ) = @_;

    my $generated_subject_id = ' [ ' . $ju->display_name . ' - ' . $ditemid . ' ]';

    # use subject from email first
    my $subject = $email_subject;

    # does the email subject look like we generated it?
    # if so, then we assume we want to use the parent comment's subject (if any)
    # otherwise we assume they've set their own comment subject (no need to clean then)
    if ( $subject =~ /\Q$generated_subject_id\E$/ ) {

        # we always have a parent comment, because that's the only way we can get an auth hash
        # if that changes, we'll have to add checking here
        my $parent_obj = LJ::Comment->new( $ju, dtalkid => $parent );
        $subject = DW::CleanEmail->reply_subject( $parent_obj->subject_text );
    }

    return $subject;
}

=head1 User Methods

=head2 C<< $u->generate_emailpost_auth() >>

Generates an auth to be used when replying to a comment via email

=cut

sub generate_auth {
    my ($u) = $_[0];

    my $auth = LJ::rand_chars(32);
    $u->set_prop( emailpost_auth => $auth );

    # just log that the emailpost_auth has changed
    # automatically records remote / uniq / ip if available
    $u->log_event('emailpost_auth');

    return $auth;
}

=head2 C<< $u->emailpost_auth >>

Gets the auth to be used when replying to a comment via email.

If you don't have one yet, generate one automatically.

=cut

sub emailpost_auth {
    my ($u) = $_[0];

    return $u->prop("emailpost_auth") || $u->generate_emailpost_auth;
}

*LJ::User::generate_emailpost_auth = \&generate_auth;
*LJ::User::emailpost_auth          = \&emailpost_auth;
