#!/usr/bin/perl
#------------------------------------------------------------
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#------------------------------------------------------------
use strict;

use Getopt::Std;
use Data::Dumper;
use MIME::Lite;
use HTML::Template;
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use File::Spec;
use Encode qw(encode_utf8);

use lib '/app/tools/bookpub/lib';
use BookPub::Sale::Report::Royalty::Factory;
use BookPub::Sale::Report::Royalty::Hachette;
use BookPub::Tracker::Job::FTPRoyaltyReport;
use BookPub::Tracker::File;
use BookPub::DB::Item::File;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::Period;
use BookPub::DB::Item::InputConversionRate;
use BookPub::DB::Item::EmailNotification;

use lib '/app/tools/common/lib';
use Common::Amazon::Mail;
use Common::NumericFormat;
use Common::Log;
use Common::RSApp;
use Common::DB::Item::ClientFTP;
use Common::Email;
use Common::Client;

# Allow tuning of the verbosity of our output.
#
my $gVerbosityLevel = 1;

use constant kZipProg => '/usr/bin/zip';

Common::Log::Print(" - running with args '@ARGV'");

my %opt;
getopts( 'mp:c:', \%opt );

my $clientID = $opt{c};
my $periodID = $opt{p};

usage("missing clientID") unless ( defined $clientID );
usage("missing periodID") unless ( defined $periodID );

my $appSingleton = Common::RSApp->new( clientID => $clientID );

my ( $error, %data ) = genReport( $clientID, $periodID );

if ($error) {
    Common::Log::Print("Error in report generation, sending error email");
    if ( $opt{m} && Common::RSApp::IsProductionServer ) {
        sendErrorEmail( $periodID, %data );
    }
} else {

    # Also send the period summary email
    # But only if we're in production OR we're using a test client (DTMV Test).
    if ( $opt{m} && ( Common::RSApp::IsProductionServer || $clientID == 310 ) ) {
        Common::Log::Print("Report generated, ftping file");
        ftpReport( $clientID, $periodID );
        Common::Log::Print("Report generated, sending alert email");
        sendEmail( $periodID, %data );

        # Special behavior for Macmillan AU
        if ( $clientID == Client::Client::kMacmillanAU() ) {
            Common::Log::Print("Emailing output files to client");
            emailFilesMMAU($periodID);
        }

        # Special behavior for Macmillan AU Print
        if ( $clientID == Client::Client::kMacmillanAUPrint() ) {
            Common::Log::Print("Emailing output files to client");
            emailFilesMMAUPrint($periodID);
        }
    }
}

sub genReport {
    my $clientID = shift;
    my $periodID = shift;

    my $royaltyReport = BookPub::Sale::Report::Royalty::Factory::GetRoyaltyReport($periodID);
    my $srcFile       = $royaltyReport->filePath();
    if ( -e $srcFile ) {
        Common::Log::Print("$srcFile exists, removing it...");
        die "\nCan't remove $srcFile\n" unless ( unlink $srcFile );
    }

    my (%returnValues) = $royaltyReport->create();

    unless (%returnValues) {
        Common::Log::Print("Error: Report creation failed");
        return 1;
    }
    Common::Log::Print("Report created: $srcFile");

    my $reportUnits   = $returnValues{totalUnits};
    my $reportRevenue = $returnValues{totalRevenueUnrounded};

    # Now let's validate the unit and revenue totals.
    my ( $periodUnits, $periodRevenue, $fileCount ) = _getPeriodTotalsForValidation($periodID);
    my $reportError = 0;

    # Let's go ahead and round off the revenue totals at this point.
    $reportRevenue = Common::Util::formatFixedPoint( $reportRevenue, 2 );
    $periodRevenue = Common::Util::formatFixedPoint( $periodRevenue, 2 );

    if ( $reportUnits != $periodUnits ) {
        Common::Log::Print("Error: Totals do not match");
        Common::Log::Print("Report Units: $reportUnits different than Period Units: $periodUnits");
        $reportError = 1;
    } elsif ( $reportRevenue != $periodRevenue ) {

        # If the units match, but the revenue does not, it could just be a rounding thing.
        # Let's see if they are close.
        my $difference = abs( $reportRevenue - $periodRevenue );

        # Worst case scenario, we could be off by a penny for each file.
        #my $maxDifference = $fileCount * .01;

        # Let's just try setting it to 10 cents for now.
        my $maxDifference = .1;
        Common::Log::Print("Revenues do not match, but allowing for a difference of $maxDifference");
        Common::Log::Print("Difference is $difference");
        if ( $difference > $maxDifference ) {
            Common::Log::Print("Error: Totals do not match");
            Common::Log::Print("Report Revenue: $reportRevenue different than Period Revenue: $periodRevenue");
            $reportError = 1;
        }
    }

    my %reportData;
    $reportData{reportUnits}   = $reportUnits;
    $reportData{reportRevenue} = $reportRevenue;
    $reportData{periodUnits}   = $periodUnits;
    $reportData{periodRevenue} = $periodRevenue;
    $reportData{fileCount}     = $fileCount;

    unless ( $reportError ) {
        my $oPeriod = BookPub::DB::Item::Period->Lookup( period_id => $periodID );
        if ( $oPeriod ) {
            $oPeriod->royalty_report_created(1);
            $oPeriod->save();
        }
    }

    return ( $reportError, %reportData );
}

# FTP files to client site
#
sub ftpReport {
    my $clientID = shift;
    my $periodID = shift;

    my $clientFTP = Common::DB::Item::ClientFTP->Lookup( client_id => $clientID );

    if ( $clientFTP ) {
        my $jobArgs = BookPub::Tracker::Job::FTPRoyaltyReport->new(
            clientID     => $clientID,
            periodID     => $periodID,
            report_email => $clientFTP->email,
            error_email  => $clientFTP->email,
        );

        $jobArgs->enqueue();
    }
}

sub sendEmail {
    my ( $periodID, %data ) = @_;

    # send an email about the close
    # but, if there's no email addresses in the db, don't...
    my ( $addressList, $bccAddressList ) = _getEmailAddresses();
    if ( length($addressList) eq 0 || !defined $addressList ) {
        Common::Log::Print("Skipping the email alert, no recipients.");
        return;
    }
    my $emailFrom = Common::RSApp::GetConfig( 'bookpub', 'period_close_email_from' );
    my $body;
    my $subject;
    my $client = new Common::Client( clientID => Common::RSApp::GetClientID() );
    my $urlPrefix;
    if ( !$client->WebAlias ) {
        $urlPrefix = $client->ClientNameClean;
    } else {
        $urlPrefix = $client->WebAlias;
    }

    my $periodName = BookPub::DB::Item::Period->GetNameByPeriodID($periodID);

    # if this period hasn't been named, we will set it to the period id
    if ( length($periodName) eq 0 || !defined $periodName ) {
        $periodName = "ID $periodID";
    }

    $subject = "RoyaltyShare Period Close - $periodName";

    my $serviceCollection = BookPub::DB::Item::Service->GetAllInPeriodWithRevenue($periodID);
    my $currencySymbol    = Common::Client::Current()->Locale()->currencyFormat()->symbol();

    # iterate over all services in this period and get the revenue.  Put it in a hash
    # to stuff into the HTML template
    my @loopData = ();
    while ( $serviceCollection->hasNext() ) {
        my $service = $serviceCollection->next();
        my %rowData;
        my $revenue = Common::Client::Current()->Locale()->formatNumber( sprintf( "%.2f", $service->{revenue} ) );
        $revenue          = $currencySymbol . $revenue;
        $rowData{SERVICE} = $service->{service_name};
        $rowData{REVENUE} = $revenue;
        push( @loopData, \%rowData );
    }

    my $revString = Common::Client::Current()->Locale()->formatNumber( sprintf( "%0.2f", $data{periodRevenue} ) );
    $revString = $currencySymbol . $revString;

    # get html template filename
    my $filename = Common::RSApp::GetConfig( 'bookpub', 'period_close_html_template' );

    # This module creates an HTML file from the template file.  The template file
    # contains tags with variable names (PERIODID).  Template method param populates
    # the variables in the file with the values passed to it.
    my $template = HTML::Template->new( filename => $filename );
    $template->param(
        PERIODNAME      => $periodName,
        PERIODID        => $periodID,
        CLIENTNAME      => $urlPrefix,
        PERIODREVENUE   => $revString,
        PERIODUNITS     => $data{periodUnits},
        PERIODFILECOUNT => $data{fileCount},
        REVENUELOOP     => \@loopData,
    );
    $body = $template->output();
    $body = encode_utf8($body);

    my $mail = Common::Amazon::Mail->new('html');
    $mail->to( ($addressList) );
    $mail->bcc( ($bccAddressList) );
    $mail->subject($subject);
    $mail->body($body);
    $mail->sendit;

    Common::Log::Print("Sent email with subject \'$subject\'");
    Common::Log::Print("To: $addressList");
    Common::Log::Print("Bcc: $bccAddressList");

}

sub sendErrorEmail {
    my ( $periodID, %data ) = @_;

    # send an email about the error
    my $emailFrom = 'do-not-reply@royaltyshare.com';
    my $reportEmail = Common::RSApp::GetConfig( 'bookpub', 'error_report_email' );

    my $body;
    my $subject;

    if (%data) {

        # If we have data, the report generated but the totals are off.
        $subject = "Error - Output file incorrect";
        $body    = "Output file totals are different from period summary totals\n\n";
        $body .=
"Note that these figures are only used for validation purposes and may differ from the amounts shown on the tracker page and in the output file due to rounding.\n\n";
        $body .= "Output File Totals\n";
        $body .= "Units: " . $data{reportUnits} . "\n";
        $body .= "Revenue: " . $data{reportRevenue} . "\n\n";
        $body .= "Period Summary Totals\n";
        $body .= "Units: " . $data{periodUnits} . "\n";
        $body .= "Revenue: " . $data{periodRevenue} . "\n\n";
    } else {

        # If we don't have data, the report did not finish generating.
        $subject = "Error - Output file failed";
        $body    = "Output file failed to generate\n\n";
    }

    my $client = new Common::Client( clientID => Common::RSApp::GetClientID() );
    my $name = $client->ClientName || "Client ID: " . Common::RSApp::GetClientID();
    $subject .= ' for Period ID ' . $periodID . ' (' . $name . ')';

    Common::Email->SendAWS(
        from    => $emailFrom,
        to      => $reportEmail,
        subject => $subject,
        body    => $body
    );
}

# Pull the email recipients of the period close email from the database.
sub _getEmailAddresses {
    my $emailCollection = BookPub::DB::Item::EmailNotification->GetAllPeriodClose();
    my ( @toArray, @bccArray );

    while ( $emailCollection->hasNext() ) {
        my $address = $emailCollection->next();
        if ( $address->{period_close_bcc} eq 'N' ) {
            push( @toArray, $address->{email_address} );
        } else {
            push( @bccArray, $address->{email_address} );
        }
    }

    return ( join( ',', @toArray ), join( ',', @bccArray ) );
}

sub _getPeriodTotalsForValidation {
    my $periodID = shift;

    my $currencyCode  = Common::Client::Current()->Locale()->currencyFormat()->currencyCode();
    my $periodSummary = BookPub::DB::Item::File->GetSummaryByPeriodID( $periodID, $currencyCode );
    my $revenue       = $periodSummary->{revenue};
    my $units         = $periodSummary->{units};
    my $fileCount     = $periodSummary->{count};

    # Now we have to add in the converted amounts for the files in a foreign currency.
    my $foreignFiles = BookPub::DB::Item::File->GetAllForeignByPeriodID( $periodID, $currencyCode );

    while ( $foreignFiles->hasNext() ) {
        my $foreignFile = $foreignFiles->next();
        my $fileRevenue = 0;

        my $inputRates = BookPub::DB::Item::InputConversionRate->GetAllByFileID( $foreignFile->{file_id} );
        if ( $inputRates->size ) {
            while ( my $item = $inputRates->next() ) {
                $fileRevenue += $item->revenue * $item->conversion_rate if ( !$item->is_alternate );
            }
        }

        $fileCount++;
        $units   += $foreignFile->{units};
        $revenue += $fileRevenue;

        #$revenue += Common::Util::formatFixedPoint($fileRevenue, 2);
    }

    return ( $units, $revenue, $fileCount );
}

sub emailFilesMMAU {
    my ($periodID) = @_;

    # send an email about the error
    my $emailFrom = Common::RSApp::GetConfig( 'bookpub', 'default_email_from' );
    my $emailTo   = Common::RSApp::GetConfig( 'bookpub', 'mmau_output_file_email' );

    my $periodName = BookPub::DB::Item::Period->GetNameByPeriodID($periodID);

    # if this period hasn't been named, we will set it to the period id
    if ( length($periodName) eq 0 || !defined $periodName ) {
        $periodName = "ID $periodID";
    }

    my $subject = "RoyaltyShare Output File - $periodName";
    my $body    = "The period $periodName output file is attached, and can be accessed at\n\n"
      . "https://macmillanau.royaltyshare.com/bookpub/tracker?period=$periodID\n\n";

    my $msg = MIME::Lite->new(
        From    => $emailFrom,
        To      => $emailTo,
        Cc      => 'digitalfinance@macmillan.com.au',
        Subject => $subject,
        Type    => 'multipart/mixed',
    );
    $msg->attach(
        Type => 'TEXT',
        Data => $body
    );

    my $report = BookPub::Sale::Report::Royalty::Factory::GetRoyaltyReport($periodID);

    my $baseDir = $report->_baseDir() . "/";

    my $zipFileName = $report->zipFileName();
    my $zipFile     = $baseDir . $zipFileName;

    if ( -e $zipFile ) {
        Common::Log::Print("$zipFile exists, removing it...");
        die "\nCan't remove $zipFile\n" unless ( unlink $zipFile );
    }

    # Change to that directory, so the compressed file won't have a bunch of extraneous
    # directories in it.
    #
    chdir($baseDir) || die "ERROR - unable to change directories to $baseDir: $!";

    my $zipCmd = kZipProg . ' -r';

    system( $zipCmd . " " . $zipFileName . " ." );

    Common::Log::Print("Created zip file $zipFile");

    $msg->attach(
        Type        => 'application/zip',
        Path        => $zipFile,
        Filename    => $zipFileName,
        Disposition => 'attachment',
    );

    $msg->send;
    Common::Log::Print("Output files emailed to $emailTo");
}

sub emailFilesMMAUPrint {
    my ($periodID) = @_;

    # send an email about the error
    my $emailFrom = Common::RSApp::GetConfig( 'bookpub', 'default_email_from' );
    my $emailTo   = Common::RSApp::GetConfig( 'bookpub', 'mmau_output_file_email' );

    my $periodName = BookPub::DB::Item::Period->GetNameByPeriodID($periodID);

    # if this period hasn't been named, we will set it to the period id
    if ( length($periodName) eq 0 || !defined $periodName ) {
        $periodName = "ID $periodID";
    }

    my $subject = "RoyaltyShare Output File - $periodName";
    my $body    = "The period $periodName output file is attached, and can be accessed at\n\n"
      . "https://macmillanauprint.royaltyshare.com/bookpub/tracker?period=$periodID\n\n";

    my $msg = MIME::Lite->new(
        From    => $emailFrom,
        To      => $emailTo,
        Cc      => 'digitalfinance@macmillan.com.au',
        Subject => $subject,
        Type    => 'multipart/mixed',
    );
    $msg->attach(
        Type => 'TEXT',
        Data => $body
    );

    my $report = BookPub::Sale::Report::Royalty::Factory::GetRoyaltyReport($periodID);

    my $baseDir = $report->_baseDir() . "/";

    my $zipFileName = $report->zipFileName();
    my $zipFile     = $baseDir . $zipFileName;

    if ( -e $zipFile ) {
        Common::Log::Print("$zipFile exists, removing it...");
        die "\nCan't remove $zipFile\n" unless ( unlink $zipFile );
    }

    # Change to that directory, so the compressed file won't have a bunch of extraneous
    # directories in it.
    #
    chdir($baseDir) || die "ERROR - unable to change directories to $baseDir: $!";

    my $zipCmd = kZipProg . ' -r';

    system( $zipCmd . " " . $zipFileName . " ." );

    Common::Log::Print("Created zip file $zipFile");

    $msg->attach(
        Type        => 'application/zip',
        Path        => $zipFile,
        Filename    => $zipFileName,
        Disposition => 'attachment',
    );

    $msg->send;
    Common::Log::Print("Output files emailed to $emailTo");
}

sub usage {
    my $err = shift;

    print "$err\n\n" if ($err);

    print <<EOF;
usage: $0 [-m] -c CLIENT_ID -p PERIOD_ID

-m           : send email alert (optional)
-c CLIENT_ID : is the client ID
-p PERIOD_ID : is the period ID
EOF

    exit;
}

