#---------------------------------------------------------------
# ____                   _ _         ____  _
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/
#
# Copyright (C) 2010 RoyaltyShare, Inc.   All Rights Reserved
#---------------------------------------------------------------
package RPS::Portal::SendInvite;
use strict;
use warnings;
use Data::Dumper;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::ArtistPayee;
use RPS::DB::Item::ArtistPayeeStatementRecipient;
use RPS::DB::Item::Publisher;
use RPS::DB::Item::PublisherStatementRecipient;
use RPS::DB::Item::CAPublisher;
use RPS::DB::Item::PortalEmailCustomText;
use RPS::Portal::Job::SendInvite;
use RPS::Portal::Token;

use lib '/app/tools/api/lib';
use API::DB::Item::PortalInvite;

use lib '/app/tools/common/lib';
use Common::DB::Item::Client;
use Common::DB::Item::PayeeType;
use Common::RSApp;
use Common::Util;
use Common::Email;
use Common::Assert;

my $PAYEE_TYPE_MAP = {
    Common::DB::Item::PayeeType::kPayeeTypeArtist => {
        type      => 'artist_payee',
        payee     => 'RPS::DB::Item::ArtistPayee',
        recipient => 'RPS::DB::Item::ArtistPayeeStatementRecipient',
    },
    Common::DB::Item::PayeeType::kPayeeTypePublisherUS => {
        type      => 'publisher',
        payee     => 'RPS::DB::Item::Publisher',
        recipient => 'RPS::DB::Item::PublisherStatementRecipient',
    },
    Common::DB::Item::PayeeType::kPayeeTypePublisherCA => {
        type      => 'ca_publisher',
        payee     => 'RPS::DB::Item::CAPublisher',
        recipient => 'RPS::DB::Item::CAPublisherStatementRecipient',
    },
};

sub new {
    my $class = shift;
    my $self = bless {}, $class;

    $self->_init(@_);

    return $self;
}

sub _init {
    my ( $self, %args ) = @_;

    $self->{_payeeRecipientList} = $args{payeeRecipientList};

    return $self;
}

sub run {
    my $self = shift;

    if ( exists $self->{_payeeRecipientList} && $self->{_payeeRecipientList} ) {
        my @payeeRecipientList = split /,/, $self->{_payeeRecipientList};
        foreach my $item ( @payeeRecipientList ) {
            my %params;
            my ( $payeeTypeID, $payeeID, $statementRecipientID ) = split /:/, $item;
            $self->_sendInvite(
                payee_id => $payeeID,
                payee_type => $payeeTypeID,
                statement_recipient_id => $statementRecipientID // 0
            );
        }
    }

    return 1;
}

sub _sendInvite {
    my ($self, %params) = @_;

    # Client section
    my $clientID     = Common::RSApp::GetClientID();
    my $oClient      = Common::DB::Item::Client->Lookup( client_id => $clientID );
    my $labelName    = $oClient->display_name;
    my $labelContact = $oClient->email;

    my $isValidClient = $self->_validateClient($labelName, $labelContact);
    return 0 unless $isValidClient;

    # Payee section
    my $payeeID              = $params{payee_id};                 # artist_payee_id or publisher_id or ca_publisher_id
    my $payeeType            = $params{payee_type};               # see types in Common::DB::Item::PayeeType
    my $statementRecipientID = $params{statement_recipient_id};   # additional statement recipient is optional or 0 for primary payee
    my ($oPayee, $mainPayeeName) = $self->_getPayeeByType($payeeID, $payeeType, $statementRecipientID);
    return 0 unless $oPayee;

    my $payeeEmail = $oPayee->email;
    my $payeeName  = $oPayee->{name} || $oPayee->{publisher_name} || $payeeEmail;

    # Get the encoded token
    my $tokenObj   = RPS::Portal::Token->new( clientID => $clientID, payeeID => $payeeID, payeeType => $payeeType, email => $payeeEmail );
    my $tokenParam = $tokenObj->GetTimedToken();
    my $inviteUrl  = "https://portal.royaltyshare.com/invite/$tokenParam";

    # Prepare and send the email
    my $to      = $payeeEmail;
    my $from    = 'do-not-reply@royaltyshare.com';
    my $subject = "Invite for Online Statement Distribution from $labelName - $mainPayeeName";
    my $body    = $self->_getEmailBody($payeeName, $labelName, $labelContact, $inviteUrl);
    Common::Email->SendAWS(
        to      => $to,
        from    => $from,
        subject => $subject,
        body    => $body
    );

    $oPayee->date_invited( Common::Util::today_and_now() );
    $oPayee->save();

    # Check to see if we already have a copy of this invite stored. If not, save it.
    unless ( my $oInvite = API::DB::Item::PortalInvite->Lookup( invite_key => $tokenParam ) ) {
        my %args = (
            'invite_key'             => $tokenParam,
            'client_id'              => $clientID,
            'payee_id'               => $payeeID,
            'statement_recipient_id' => ($statementRecipientID ? $oPayee->statement_recipient_id : 0),
            'payee_type'             => $payeeType,
            'invitee'                => $payeeEmail,
            'date_invited'           => Common::Util::today_and_now(),
        );

        $oInvite = API::DB::Item::PortalInvite->Create(%args);
        $oInvite->save();
    }

    my $recipientInfo = $statementRecipientID ? "PayeeStatementRecipient: '$statementRecipientID'" : "";
    Common::Log::Print("Payee: '$payeeID' $recipientInfo with PayeeType: '$payeeType' - invite sent");

    return 1;
}

# resendAll is an all or nothing kind of action.
# If any of the payees/additional recipients cannot be resent to, then no invites will be sent.
sub resendAll {
    my ($self, $payeeTypeID, $entity) = @_;
    # entity: 'payee' or 'recipient'

    my $okToResendAll = $self->okToResendAll($payeeTypeID, $entity);
    return 0 unless $okToResendAll;

    my $class = $PAYEE_TYPE_MAP->{$payeeTypeID}{$entity};
    my $payeeKeyField = $PAYEE_TYPE_MAP->{$payeeTypeID}{type} . '_id';
    my $oEntities = $class->GetAllOnline();

    my @t;
    while ( $oEntities->hasNext() ) {
        my $oEntity = $oEntities->next();

        $entity eq 'recipient'
            ? push @t, join ':', ($payeeTypeID, $oEntity->$payeeKeyField, $oEntity->statement_recipient_id)
            : push @t, join ':', ($payeeTypeID, $oEntity->$payeeKeyField, 0);
    }

    my $entityList = join ',', @t;

    my $oJob = RPS::Portal::Job::SendInvite->new( payeeRecipientList => $entityList );
    $oJob->enqueue();

    return 1;
}

# We're going to loop over all payees/additional recipients set to online distribution that have not confirmed yet.
# If it's too soon to invite any of them again, we'll return false.
sub okToResendAll {
    my ($self, $payeeTypeID, $entity) = @_;

	if ( !exists $PAYEE_TYPE_MAP->{$payeeTypeID} ) {
		Common::Log::Print("Unable to determine payee type from ID '$payeeTypeID'.");
		return 0;
	}

	if ( !exists $PAYEE_TYPE_MAP->{$payeeTypeID}{$entity} ) {
		Common::Log::Print("Unable to determine payee statement recipient entity '$entity'.");
		return 0;
	}

    my $class = $PAYEE_TYPE_MAP->{$payeeTypeID}{$entity};
    my $oEntities = $class->GetAllOnline();

    while ( $oEntities->hasNext() ) {
        my $oEntity = $oEntities->next();
        next if $oEntity->date_confirmed;
        my $okToInviteAgain = _checkDateInvited( $oEntity->date_invited );
        return 0 unless $okToInviteAgain;
    }

    return 1;
}

# We don't want to send an invite to a payee that has recieved one in the last 24 hours.
sub _checkDateInvited {
    my $dateInvited = shift;

    if ($dateInvited) {

        my $invited = Common::Util::dateTimeToSeconds($dateInvited);
        my $now     = Common::Util::dateTimeToSeconds( Common::Util::today_and_now() );

        my $diff = $now - $invited;

        my $diffInHours = ( $diff / 60 ) / 60;

        if ( $diffInHours < 24 ) {
            return;
        }
    }

    return 1;
}

sub _getPayeeByType {
	my ($self, $payeeID, $payeeTypeID, $statementRecipientID) = @_;

	# We can send an invitation to either the main payee or an additional statement recipient.
	# If we received $statementRecipientID and its value is greater than zero,
	# then we will work with an additional statement recipient.
	# In any case we must first verify the main payee, determine its type (artist_payee, publisher, ca_publisher) and validate it.
	if ( !exists $PAYEE_TYPE_MAP->{$payeeTypeID} ) {
		Common::Log::Print("Unable to determine payee type from ID '$payeeTypeID'.");
		return 0;
	}

	my $payeeTypeText = $PAYEE_TYPE_MAP->{$payeeTypeID}{type};

	# Payee main
	my $payeeClass    = $PAYEE_TYPE_MAP->{$payeeTypeID}{payee};
	my $oPayee = $payeeClass->Lookup( $payeeTypeText . '_id' => $payeeID );
	my $isValidPayee = $self->_validateMainPayee($payeeID, $payeeTypeText, $statementRecipientID, $oPayee);
	return 0 unless $isValidPayee;

	# We work with the main payee!!!
	return $oPayee, ($oPayee->{name} || $oPayee->{publisher_name}) unless $statementRecipientID;

	# Otherwise, we work with the additional payee statement recipient!
	my $recipientClass = $PAYEE_TYPE_MAP->{$payeeTypeID}{recipient};
	my $oPayeeRecipient = $recipientClass->Lookup(
        $payeeTypeText . '_id' => $payeeID,
        statement_recipient_id => $statementRecipientID
	);
	my $isValidRecipient = $self->_validatePayeeStatementRecipient($payeeID, $payeeTypeText, $statementRecipientID, $oPayeeRecipient);
    return 0 unless $isValidRecipient;

	return $oPayeeRecipient, ($oPayee->{name} || $oPayee->{publisher_name});
}

sub _validateMainPayee {
	my ($self, $payeeID, $payeeTypeText, $statementRecipientID, $oPayee) = @_;

    if ( !$oPayee ) {
        Common::Log::Print("Payee: '$payeeID' with PayeeType: '$payeeTypeText' was not found in the system.");
        return 0;
    }

    if ( $oPayee->distribution != RPS::DB::Item::ArtistPayee::kStatementDistributionOnline ) {
        Common::Log::Print("Payee '$payeeID' with PayeeType: '$payeeTypeText' - not set to online distribution");
        return 0;
    }

    if ( !$oPayee->email ) {
        Common::Log::Print("Payee $payeeID with PayeeType: '$payeeTypeText' - unable to invite without email");
        return 0;
    }

    if ( !$statementRecipientID && $oPayee->date_confirmed ) {
        Common::Log::Print("Payee '$payeeID' with PayeeType: '$payeeTypeText' - already confirmed");
        return 0;
    }

    # Checking date this payee was last invited. If < 24 hours, we won't send them email now.
    my $okToInviteAgain = _checkDateInvited( $oPayee->date_invited );
    if ( !$statementRecipientID && !$okToInviteAgain ) {
        Common::Log::Print("Payee $payeeID with PayeeType: '$payeeTypeText' - too soon to invite again");
        return 0;
    }

    return 1;
}

sub _validatePayeeStatementRecipient {
	my ($self, $payeeID, $payeeTypeText, $statementRecipientID, $oPayeeRecipient) = @_;

    if ( !$oPayeeRecipient ) {
        Common::Log::Print("PayeeStatementRecipient '$statementRecipientID' with PayeeType: '$payeeTypeText' was not found in the system.");
        return 0;
    }

    if ( !$oPayeeRecipient->email ) {
        Common::Log::Print("PayeeStatementRecipient $statementRecipientID with PayeeType: '$payeeTypeText' - unable to invite without email");
        return 0;
    }

    if ( $oPayeeRecipient->date_confirmed ) {
        Common::Log::Print("PayeeStatementRecipient '$statementRecipientID' with PayeeType: '$payeeTypeText' - already confirmed");
        return 0;
    }

    # Checking date this payee was last invited. If < 24 hours, we won't send them email now.
    my $okToInviteAgain = _checkDateInvited( $oPayeeRecipient->date_invited );
    if ( !$okToInviteAgain ) {
        Common::Log::Print("PayeeStatementRecipient '$statementRecipientID' with PayeeType: '$payeeTypeText' - too soon to invite again");
        return 0;
    }

	return 1;
}

sub _validateClient {
    my ($self, $labelName, $labelContact) = @_;

    unless ( $labelName && $labelContact ) {
        Common::Log::Print("Label info missing - unable to invite");
        return 0;
    }

    return 1;
}

sub _getEmailBody {
    my ($self, $payeeName, $labelName, $labelContact, $inviteUrl) = @_;

    my $body = "Dear $payeeName,

You've been invited to set up an account for RoyaltyShare's Payee Portal by $labelName so you can receive royalty statements online.

Go here to create your account now:
$inviteUrl
This invitation link is valid for 30 days.

Once you've set up your account, you'll receive notifications when new statements are available.

If you have questions about why you're receiving this email, please contact $labelContact.

Thanks!
The RoyaltyShare Support Team";

    if ( my $customInviteText = RPS::DB::Item::PortalEmailCustomText->GetCustomInviteText() ) {
        $body .= <<"END";


================================================================================================================================
Below is a direct message sent on behalf of $labelName

$customInviteText

================================================================================================================================

END
    }

    return $body;
}


1;
