#!/usr/bin/perl
#

use strict;
use warnings;

DTMVBillingReport->start();

package DTMVBillingReport;

use Data::Dumper;

use lib '/app/tools/data_classes/lib';
use Client::Client;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::RSApp;
use Common::RSDB;
use Common::Client;
use Common::DB::Item;
use Common::DB::Item::Client;
use Common::DB::Item::ClientType;
use Common::DB::Item::Billing;
use Common::Date;

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::Billing;
use BookPub::DB::Item::Service;
use BookPub::DB::Item::Period;
use BookPub::DB::Item::File;
use BookPub::Tracker::File;
use BookPub::Tracker::Period;
use BookPub::Tracker::Service;

use base 'BookPub::Script';

use constant kNotAvailable => 'N/A';

# global hash, stores current revenue
my %metrics = ();

sub _options {
    {
        persist => {
            short       => 'p',
            required    => 0,
            description => 'Persist report info to database',
        },
        date => {
            short       => 'y',
            required    => 0,
            description => 'Date of report',
            parameter   => 's'
        },
        history => {
            short       => 's',
            required    => 0,
            description => 'Show report history',
        },
    };
}

# overloaded from Common::Script.pm, this runs once after the start() method
# runs, but before the _process method is called for each client in the host db
#
sub run_process {
    my $self = shift;

    if ( $self->param('history') ) {
        $self->_printHistoryHeader();
    } else {
        $self->_printReportHeader();
    }

    return $self->SUPER::run_process(@_);
}

sub _process {

    my ($self)    = @_;
    my $clientID  = $self->param('client_id');
    my $doPersist = $self->param('persist');

    my $client = Common::Client::Current();
    if ( !$client->isClientType(Common::Client::kBookPublishing) ) {
        return;
    }

    # Let's skip the test sites.
    if (   $client->ClientID() == Client::Client::kDTMVTest()
        || $client->ClientID() == Client::Client::kDTMVDemo() ) {
        return;
    }

    my $count = 0;
    $count = Common::DB::Item::Billing->GetPersistedTodayCount( $client->ClientID() );

    if ( $doPersist && $count > 0 ) {
        print "\n\nCan not persist twice in a given day.\n";
        print "To view report, remove \"-p\" option.\n\n";
        exit;
    }

    my $reportDate = $self->param('date');
    my $history    = $self->param('history');

    if ($reportDate) {

        $self->debug("Output existing report");
        $self->_doExistingReport($reportDate);

        return;
    }

    if ($history) {

        $self->debug("Output report history");
        $self->_doReportHistory( $client->ClientID(), $client->ClientName );

        return;
    }

    $self->_getCurrentMetrics();

    # This was originally implemented to deal with the case of a file being
    # split and therefore having revenue over multiple periods.  Since
    # we only are concerned with units, we can skip the update.  I'm
    # leaving this code in place in case it's ever needed.
    #$self->_updateCurrentMetrics();

    $self->_processDeletedFiles();
    $self->_outputReport();

    my $writeToRevenueSummary = $self->param('persist');
    if ($writeToRevenueSummary) {
        $self->_addToRevenueSummary();
    }

    #clear out this hash for the next client
    %metrics = ();

}

sub _processDeletedFiles {
    my ($self) = @_;

    my @fileIDs = Common::DB::Item::Billing->GetFileIDArray();
    foreach my $id (@fileIDs) {
        if ( BookPub::DB::Item::Billing->FileIsDeleted($id) ) {

            # need to add the deleted file info into the metrics
            # hash so it's in the report
            my $collection = Common::DB::Item::Billing->GetSingleFileData($id);

            my $count    = $collection->size();
            my $client   = Common::Client::Current();
            my $clientID = $client->ClientID();
            assert( $count == 1,
                "There are $count entries for this fileID/clientID pair ($id and $clientID) and there should be only one" );

            my $row = $collection->next();

            $metrics{$id}{file_id}          = $row->{file_id};
            $metrics{$id}{parent_file_id}   = $row->{parent_file_id};
            $metrics{$id}{file_name}        = $row->{file_name};
            $metrics{$id}{service_id}       = $row->{service_id};
            $metrics{$id}{period_id}        = $row->{period_id};
            $metrics{$id}{end_date}         = $row->{end_date};
            $metrics{$id}{date_received}    = $row->{date_received};
            $metrics{$id}{previous_units}   = $row->{current_units};
            $metrics{$id}{current_units}    = 0 - $row->{current_units};
            $metrics{$id}{billable_units}   = 0;
            $metrics{$id}{previous_revenue} = $row->{current};
            $metrics{$id}{current_revenue}  = 0 - $row->{current};
            $metrics{$id}{billable_revenue} = 0;
            $metrics{$id}{credited}         = 1;
            $metrics{$id}{report_date}      = $row->{report_date};

            my $service = BookPub::DB::Item::Service->Lookup( service_id => $row->{service_id} );
            if ($service) {
                $metrics{$id}{service_name} = $service->service_name;
            }

            my $period = BookPub::DB::Item::Period->Lookup( period_id => $row->{period_id} );
            if ($period) {
                $metrics{$id}{period_name}     = $period->name;
                $metrics{$id}{period_end_date} = $period->end_date;
            }

            Common::DB::Item::Billing->CreditDeletedFile($id);
        }

    }

}

sub _getCurrentMetrics {

    my ($self) = @_;

    my $collection = BookPub::DB::Item::Billing->GetAllMetrics();

    if ( $collection->size() == 0 ) { return; }

  OUTER: while ( my $row = $collection->next() ) {

        my $fileID       = $row->{file_id};
        my $parentFileID = $row->{parent_file_id};

        # skip this insert if the file is already in the summary table
        next
          if ( Common::DB::Item::Billing->CheckFilePresence($fileID) > 0
            || Common::DB::Item::Billing->CheckFilePresence($parentFileID) > 0 );

        # Looking further up the family tree and skipping if we find anything.
        while ($parentFileID) {
            my $file = BookPub::DB::Item::File->Lookup( file_id => $parentFileID );
            my $grandParentFileID = $file->parent_file_id;

            if ( !$grandParentFileID ) {
                last;
            }

            next OUTER if ( Common::DB::Item::Billing->CheckFilePresence($grandParentFileID) > 0 );

            $parentFileID = $grandParentFileID;
        }

        $metrics{$fileID}{file_id}          = $row->{file_id};
        $metrics{$fileID}{parent_file_id}   = $row->{parent_file_id};
        $metrics{$fileID}{file_name}        = $row->{file_name};
        $metrics{$fileID}{service_name}     = $row->{service_name};
        $metrics{$fileID}{service_id}       = $row->{service_id};
        $metrics{$fileID}{period_id}        = $row->{period_id};
        $metrics{$fileID}{end_date}         = $row->{end_date};
        $metrics{$fileID}{date_received}    = $row->{date_received};
        $metrics{$fileID}{period_name}      = $row->{period_name};
        $metrics{$fileID}{period_end_date}  = $row->{period_end_date};
        $metrics{$fileID}{previous_units}   = 0;
        $metrics{$fileID}{current_units}    = $row->{current_units};
        $metrics{$fileID}{billable_units}   = $row->{current_units};
        $metrics{$fileID}{previous_revenue} = 0;
        $metrics{$fileID}{current_revenue}  = $row->{current};
        $metrics{$fileID}{billable_revenue} = $row->{current};
        $metrics{$fileID}{credited}         = 0;
        $metrics{$fileID}{today}            = $row->{today};
    }

    $self->_processChildFiles();

}

sub _processChildFiles {

    my ($self) = @_;

    my @children = ();

    # We need to sort these so that we process the most recent files first.
    # Otherwise, there can be gaps when we try to look for the oldest parent.
    #
    foreach my $fileID ( sort { $b <=> $a } keys %metrics ) {

        # get parent file id
        my $parent = $metrics{$fileID}{parent_file_id};
        my $top;

        # get most senior parent file id
        while ( $parent && exists( $metrics{$parent} ) ) {
            $top    = $parent;
            $parent = $metrics{$parent}{parent_file_id};
        }

        # add units and revenue to its top parent id
        # then mark the child to be deleted later
        if ($top) {
            if ( defined $metrics{$fileID}{current_units} ) {
                $metrics{$top}{current_units} +=
                  $metrics{$fileID}{current_units};
            }

            if ( defined $metrics{$fileID}{billable_units} ) {
                $metrics{$top}{billable_units} +=
                  $metrics{$fileID}{billable_units};
            }

            if ( defined $metrics{$fileID}{current_revenue} ) {
                $metrics{$top}{current_revenue} +=
                  $metrics{$fileID}{current_revenue};
            }

            if ( defined $metrics{$fileID}{billablerevenue} ) {
                $metrics{$top}{billable_revenue} +=
                  $metrics{$fileID}{billable_revenue};
            }

            push( @children, $fileID );
        }

        # delete files marked to be deleted.
        for ( my $i = 0 ; $i < @children ; $i++ ) {
            delete $metrics{ $children[$i] };
        }
    }

}

sub _updateCurrentMetrics {

    my ($self) = @_;

    my $client = Common::Client::Current();

    my $collection = Common::DB::Item::Billing->GetMetricsUpdate( $client->ClientID() );

    if ( $collection->size() == 0 ) { return; }

    while ( my $row = $collection->next() ) {

        my $fileID = $row->{file_id};
        my $service;
        my $serviceName;
        my $periodName;
        my $periodEndDate;

        # if this fileID has already been processed, previous is set to current
        # and billable is current minus previous

        if ( exists( $metrics{$fileID} ) ) {

            $metrics{$fileID}{previous_revenue} = $row->{previous};
            $metrics{$fileID}{billable_revenue} =
              $metrics{$fileID}{current_revenue} - $metrics{$fileID}{previous_revenue};

            $metrics{$fileID}{previous_units} = $row->{previous_units};
            $metrics{$fileID}{billable_units} =
              $metrics{$fileID}{current_units} - $metrics{$fileID}{previous_units};

        }

        # this file has been deleted, but its revenue is still in the
        # bp_revenue_summary table so we need to populate metrics
        # for that file as best we can.
        else {

            # get service name from service id
            if ( defined( $row->{service_id} ) ) {
                $service = new BookPub::Tracker::Service( serviceID => $row->{service_id} );
                $serviceName = $service->ServiceName();
            }

            # if service name is unavailable, use service ID instead
            if ( !defined($serviceName) || ( length $serviceName == 0 ) ) {
                $serviceName = $row->{service_id};
            }

            # get period name and end date from period id
            if ( defined( $row->{period_id} ) ) {
                my $period = new BookPub::Tracker::Period( periodID => $row->{period_id} );
                $periodName    = $period->Name();
                $periodEndDate = $period->EndDate();
            }

            # if period is not unavailable, use ID instead
            if ( !defined($periodName) || ( length $periodName == 0 ) ) {
                $periodName = kNotAvailable;
            }
            if ( !defined($periodEndDate) || ( length $periodEndDate == 0 ) ) {
                $periodEndDate = kNotAvailable;
            }

            $metrics{$fileID}{file_id}          = $row->{file_id};
            $metrics{$fileID}{parent_file_id}   = 0;
            $metrics{$fileID}{file_name}        = $row->{file_name};
            $metrics{$fileID}{service_name}     = $serviceName;
            $metrics{$fileID}{service_id}       = $row->{service_id};
            $metrics{$fileID}{period_id}        = $row->{period_id};
            $metrics{$fileID}{end_date}         = $periodEndDate;
            $metrics{$fileID}{date_received}    = kNotAvailable;
            $metrics{$fileID}{period_name}      = $periodName;
            $metrics{$fileID}{period_end_date}  = $periodEndDate;
            $metrics{$fileID}{previous_units}   = $row->{previous_units};
            $metrics{$fileID}{current_units}    = 0;
            $metrics{$fileID}{billable_units}   = 0 - $row->{previous_units};
            $metrics{$fileID}{previous_revenue} = $row->{previous};
            $metrics{$fileID}{current_revenue}  = 0;
            $metrics{$fileID}{billable_revenue} = 0 - $row->{previous};
            $metrics{$fileID}{credited}         = 1;

        }
    }
}

sub _addToRevenueSummary {

    my ($self) = @_;

    my $client = Common::Client::Current();

    my $response = ();

    foreach my $fileID ( keys %metrics ) {
        $response = BookPub::DB::Item::Billing->PutSummaryUpdate(
            $client->ClientID(),                $metrics{$fileID}{file_id},          $metrics{$fileID}{period_id},
            $metrics{$fileID}{service_id},      $metrics{$fileID}{file_name},        $metrics{$fileID}{previous_units},
            $metrics{$fileID}{current_units},   $metrics{$fileID}{billable_units},   $metrics{$fileID}{previous_revenue},
            $metrics{$fileID}{current_revenue}, $metrics{$fileID}{billable_revenue}, $metrics{$fileID}{credited},
        );

    }
}

sub _outputReport {

    my ($self) = @_;

    my $client = Common::Client::Current();
    my $dateReceived;
    my $billingDate;

    foreach my $fileID ( sort sortOnService ( keys %metrics ) ) {

        if (   $metrics{$fileID}{date_received}
            && $metrics{$fileID}{date_received} ne kNotAvailable ) {
            $dateReceived =
              new Common::Date( date => $metrics{$fileID}{date_received} );
        }

        if ( $metrics{$fileID}{today} ) {
            $billingDate =
              new Common::Date( date => $metrics{$fileID}{today} );
        }

        print $client->ClientName() . "\t";
        print $client->ClientID() . "\t";
        if ( $metrics{$fileID}{service_name} ) {
            print $metrics{$fileID}{service_name} . "\t";
        } else {
            print kNotAvailable . "\t";
        }
        print $metrics{$fileID}{file_name} . "\t";

        if ( defined($dateReceived) ) {
            print $dateReceived->asString("%m/%d/%Y") . "\t";
        } else {
            print kNotAvailable . "\t";
        }

        if ( $metrics{$fileID}{end_date} ) {
            print $metrics{$fileID}{end_date} . "\t";
        } else {
            print kNotAvailable . "\t";
        }

        if ( $metrics{$fileID}{period_name} ) {
            print $metrics{$fileID}{period_name} . "\t";
        } else {
            print "current" . "\t";
        }

        if ( $metrics{$fileID}{current_units} ) {
            print $metrics{$fileID}{current_units} . "\t";
        } else {
            print "0" . "\t";
        }

        if ( $metrics{$fileID}{current_revenue} ) {
            print $metrics{$fileID}{current_revenue} . "\t";
        } else {
            print "0" . "\t";
        }

        if ( defined($billingDate) ) {
            print $billingDate->asString("%m/%d/%Y") . "\t";
        } else {
            print kNotAvailable . "\t";
        }

        print "\n";
    }
}

sub _doExistingReport {

    my ( $self, $reportDate ) = @_;

    assert( defined $reportDate );

    my $client = Common::Client::Current();

    my $collection = Common::DB::Item::Billing->GetExistingReportData( $reportDate, $client->ClientID() );

    if ( $collection->size() == 0 ) { return; }

    while ( my $row = $collection->next() ) {

        my $fileID = $row->{file_id};

        $metrics{$fileID}{file_id}       = $row->{file_id};
        $metrics{$fileID}{file_name}     = $row->{file_name};
        $metrics{$fileID}{service_id}    = $row->{service_id};
        $metrics{$fileID}{period_id}     = $row->{period_id};
        $metrics{$fileID}{current}       = $row->{current};
        $metrics{$fileID}{current_units} = $row->{current_units};

    }

    foreach my $fileID ( keys %metrics ) {

        # get date received from file.date_created
        my $file = new BookPub::Tracker::File( file_id => $metrics{$fileID}{file_id} );
        $metrics{$fileID}{date_received} = $file->DateCreated();
        if ( !defined $metrics{$fileID}{date_received} ) {
            $metrics{$fileID}{date_received} = kNotAvailable;
        }

        # get service name from service id
        my $service = new BookPub::Tracker::Service( serviceID => $metrics{$fileID}{service_id} );
        $metrics{$fileID}{service_name} = $service->ServiceName();
        if ( !defined $metrics{$fileID}{service_name} ) {
            $metrics{$fileID}{service_name} = kNotAvailable;
        }

        # get period name and end date from period id
        my $period = new BookPub::Tracker::Period( periodID => $metrics{$fileID}{period_id} );
        $metrics{$fileID}{period_name} = $period->Name();
        if ( !defined $metrics{$fileID}{period_name} ) {
            $metrics{$fileID}{period_name} = kNotAvailable;
        }
        $metrics{$fileID}{period_end_date} = $period->EndDate();
        if ( !defined $metrics{$fileID}{period_end_date} ) {
            $metrics{$fileID}{period_end_date} = kNotAvailable;
        }

    }

    foreach my $fileID ( sort sortOnService ( keys %metrics ) ) {

        print $client->ClientName() . "\t"
          . $client->ClientID() . "\t"
          . $metrics{$fileID}{service_name} . "\t"
          . $metrics{$fileID}{file_name} . "\t"
          . $metrics{$fileID}{date_received} . "\t"
          . $metrics{$fileID}{period_end_date} . "\t"
          . $metrics{$fileID}{period_name} . "\t"
          . $metrics{$fileID}{current_units} . "\t"
          . $metrics{$fileID}{current} . "\t"
          . $reportDate . "\n";
    }
}

sub _doReportHistory {

    my ( $self, $clientID, $clientName ) = @_;

    my $collection = Common::DB::Item::Billing->GetReportHistory($clientID);

    if ( $collection->size() == 0 ) {
        print "$clientName\tNo report history\n";
    }

    while ( my $row = $collection->next() ) {
        print $clientName . "\t";
        print $row->{report_date} . "\n";
    }
}

sub sortOnService {

    return lc $metrics{$a}{service_name} cmp lc $metrics{$b}{service_name};

}

sub _printReportHeader {

    print "Customer Name\tCustomer ID\tService\tFile Name\tReceived\tReporting Period\tOriginal Period\tUnits\tRevenue\tBilling Date\n";

}

sub _printHistoryHeader {
    print "Customer Name\tReport Date\n";
}

# A Perl module must end with a true value or else it is considered not to
# have loaded.  By convention this value is usually 1 though it can be
# any true value.  A module can end with false to indicate failure but
# this is rarely used and it would instead die() (exit with an error).
1;
