package BookPub::Sale::Report::Royalty::BaseReport;

use strict;
use Template;
use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::Log;
use Common::Client;
use Common::DB::ItemCollection;
use Common::DB::Item;
use Common::Util qw(formatFixedPoint);

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::File;
use BookPub::DB::Item::Period;
use BookPub::DB::Item::Imprint;
use BookPub::DB::Item::ClientService;
use BookPub::DB::Item::BookContributor;
use BookPub::DB::Item::BookProduct;
use BookPub::DB::Item::PriceType;
use BookPub::DB::Item::ProductMarketPrice;

use BookPub::DB::Item::RoyaltyReport::Hachette;
use BookPub::DB::Item::RoyaltyReport::Disney;
use BookPub::DB::Item::RoyaltyReport::Hyperion;

use base 'BookPub::Sale::Report::Royalty';

use constant kDB         => Common::DB::Item::kClientDB;
use constant OUTDIR_BASE => '/app/data/sale_report/tmp/';

my %HEADER_MAIN = (
    _order => [
        qw(record_type sale_year sale_month sale_day isbn units unit_price revenue purchase_price list_price
          discount catalog_price product_type agency free country state postal_code
          currency title subtitle author imprint)
    ],
    record_type    => 'Record Type',
    sale_year      => 'Sale Year',
    sale_month     => 'Sale Month',
    sale_day       => 'Sale Day',
    isbn           => 'ISBN',
    units          => 'Units',
    unit_price     => 'Unit Price',
    revenue        => 'Revenue',
    purchase_price => 'Purchase Price',
    list_price     => 'List Price',
    discount       => 'Discount',
    catalog_price  => 'Catalog Price',
    product_type   => 'Product Type',
    agency         => 'Agency',
    free           => 'Free',
    country        => 'Country',
    state          => 'State',
    postal_code    => 'Postal Code',
    currency       => 'Currency',
    title          => 'Title',
    subtitle       => 'Subtitle',
    author         => 'Author',
    imprint        => 'Imprint',
);

# TODO - this header could be templatized
use constant TMPL_HEADER_SERVICE_SALES =>
  "SH\tService ID\t{service_id}\tService Name\t{service_name}\tPeriod\t{period_id}\tBatch\t{batch}\tSales\n";
use constant TMPL_HEADER_SERVICE_RETURNS =>
  "SH\tService ID\t{service_id}\tService Name\t{service_name}\tPeriod\t{period_id}\tBatch\t{batch}\tReturns\n";
use constant TMPL_TRAILER_SERVICE => "ST\tService ID\t{service_id}\tLines\t{lines}\tUnits\t{units}\tRevenue\t{revenue}\n";
use constant TMPL_TRAILER_REPORT  => "T\tLines\t{lines}\tUnits\t{units}\tRevenue\t{revenue}\n";

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;

    $self->_init(@_);

    return $self;
}

sub _init {
    my $self = shift;
    my %args = @_;

    $self->SUPER::_init(%args);

    $self->{clientID} = $args{clientID} ? $args{clientID} : Common::RSApp::GetClientID();
    $self->{tmplObj} = Template->new( { START_TAG => '{', END_TAG => '}' } );

    $self->{imprintMap}       = BookPub::DB::Item::Imprint->IDMap();
    $self->{clientServiceMap} = BookPub::DB::Item::ClientService->GetAllByRSServiceIDProdTypeCurrencyCode();

    #$self->{serviceProdTypes}   = BookPub::DB::Item::ClientService->GetProductTypes();
}

sub _getReportData { die "Must be overloaded" }

sub _clientReportName { die "Must be overloaded" }

sub _getReportHeader { die "Must be overloaded" }

sub create {
    my $self = shift;

    # Since this report treats returns as positive amounts,
    # we'll keep a separate tally of units and revenue and
    # return those at the end for validation.
    my $totalUnits;
    my $totalRevenueUnrounded;

    my $fileList =
      BookPub::DB::Item::File->GetServiceFilesByPeriodID( $self->periodID(), file_status => BookPub::DB::Item::File::STATUS_CLOSED );

    my $outFilePath = $self->filePath();

    unless ( open( OUT, '>:utf8', $outFilePath ) ) {
        die "can't open output file ($outFilePath) - $!\n";
    }

    # print the main header
    my @headers = @HEADER_MAIN{ @{ $HEADER_MAIN{_order} } };
    print OUT join( "\t", @headers ), "\n";

    # print the report header
    my $header = $self->_getReportHeader();
    print OUT $header;

    use constant kMonthRecordLimit => 8999;

    my $rptRows     = 0;
    my $rptUnits    = 0;
    my $rptRevenue  = 0;
    my %fileStats   = ();
    my %authorCache = ();
    my %svcBatch    = ();

    foreach my $svcID ( @{ $fileList->{_order} } ) {
        my @fileIDs = ();
        foreach my $fileRef ( @{ $fileList->{$svcID} } ) {
            my $fID = $fileRef->file_id;
            push( @fileIDs, $fID );
            $fileStats{$fID} = {

                #records => $fileRef->records,
                units   => $fileRef->units,
                revenue => $fileRef->revenue,

                #revenue => $fileRef->revenue || $fileRef->input_revenue,
            };
        }

        # init the service file stats hash
        # it will be used to validate against %fileStats (above) to ensure the rows printed
        # match the orig file specs
        my %svcFileStats = ();
        map { $svcFileStats{$_} = { records => 0, units => 0, revenue => 0 } } @fileIDs;

        foreach my $prodType ( keys %{ $self->{clientServiceMap}{$svcID} } ) {
            foreach my $currencyCode ( keys %{ $self->{clientServiceMap}{$svcID}{$prodType} } ) {
                foreach my $feedId ( keys %{ $self->{clientServiceMap}{$svcID}{$prodType}{$currencyCode} } ) {

                    # Sales with 0 revenue and negative units were being treated as sales, instead of returns.
                    # So now we'll pull the 0 revenue sales in both times and filter them based on the units.
                    foreach my $condition ( '>=', '<=' ) {
                        my $collection = $self->_getReportData(
                            condition    => $condition,
                            serviceID    => $svcID,
                            fileIDs      => \@fileIDs,
                            productType  => $prodType,
                            currencyCode => $currencyCode,
                            feedId       => $feedId,
                        );

                        next unless ( $collection->size );

                        # print the service header
                        my $tmplHeader = $condition eq '>=' ? TMPL_HEADER_SERVICE_SALES : TMPL_HEADER_SERVICE_RETURNS;

                        my $name = $self->_clientReportName();
                        $tmplHeader =~ s/\n/\t$name\n/;

                        my $tmplTrailer = TMPL_TRAILER_SERVICE;

                        my $svcMonthRecords = 0;
                        my $svcMonthUnits   = 0;
                        my $svcMonthRevenue = 0;
                        my $svcMonth        = 0;
                        my $cntRecords      = 0;
                        my %tmplVars;

                        my $rowCount = 0;

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

                            # We want to make sure that 0 revenue sales are counted as "sales"
                            # if they have positive units and "returns" if they have negative units.
                            if ( $condition eq '>=' && $row->units < 0 ) {
                                next;
                            }
                            if ( $condition eq '<=' && $row->units > 0 ) {
                                next;
                            }

                            $rowCount++;

                            if ( $svcMonthRecords == 0 && $cntRecords == 0 ) {
                                $svcMonth = $row->period;

                                my $clientServiceID = $self->{clientServiceMap}{$svcID}{$prodType}{$currencyCode}{$feedId}{id};
                                my $buffer          = '';
                                $self->_getServiceHeader(
                                    service_id    => $svcID,
                                    product_type  => $prodType,
                                    currency_code => $currencyCode,
                                    feedId        => $feedId,
                                    batch         => ++$svcBatch{$clientServiceID}{$condition}{$svcMonth}{$currencyCode}{batch},
                                    template      => \$tmplHeader,
                                    buffer_ref    => \$buffer,
                                );
                                print OUT $buffer;
                            } elsif ( $svcMonth != $row->period || $svcMonthRecords >= kMonthRecordLimit ) {
                                $svcMonthRecords++;
                                $svcMonthUnits   += $tmplVars{units};
                                $svcMonthRevenue += $tmplVars{revenue};

                                $tmplVars{revenue} = formatFixedPoint( $tmplVars{revenue}, 2 );

                                my @detailRow = @tmplVars{ @{ $HEADER_MAIN{_order} } };
                                print OUT join( "\t", @detailRow ), "\n";

                                $cntRecords = 0;
                                %tmplVars   = undef;

                                my $buffer = '';
                                $self->_getServiceTrailer(
                                    service_id    => $svcID,
                                    product_type  => $prodType,
                                    currency_code => $currencyCode,
                                    records       => $svcMonthRecords,
                                    units         => $svcMonthUnits,
                                    revenue       => $svcMonthRevenue,
                                    template      => \$tmplTrailer,
                                    buffer_ref    => \$buffer,
                                    feedId        => $feedId,
                                );

                                $rptRows    += $svcMonthRecords;
                                $rptUnits   += $svcMonthUnits;
                                $rptRevenue += $svcMonthRevenue;

                                $svcMonthRecords = 0;
                                $svcMonthUnits   = 0;
                                $svcMonthRevenue = 0;
                                $svcMonth        = $row->period;
                                $cntRecords      = 0;

                                my $clientServiceID = $self->{clientServiceMap}{$svcID}{$prodType}{$currencyCode}{$feedId}{id};

                                $self->_getServiceHeader(
                                    service_id    => $svcID,
                                    product_type  => $prodType,
                                    currency_code => $currencyCode,
                                    batch         => ++$svcBatch{$clientServiceID}{$condition}{$svcMonth}{$currencyCode}{batch},
                                    template      => \$tmplHeader,
                                    buffer_ref    => \$buffer,
                                    feedId        => $feedId,
                                );
                                print OUT $buffer;
                            }

                            my $agency          = $row->price_type_id == BookPub::DB::Item::PriceType::kPriceTypeAgency ? 'Y' : 'N';
                            my $free            = $row->free;
                            my $country         = $row->country;
                            my $state           = $row->state;
                            my $postal_code     = $row->postal_code;
                            my $currency        = $row->currency;
                            my $conversion_rate = $row->conversion_rate;
                            my $product_id      = $row->product_id;
                            my $unit_price      = abs( $row->unit_price );
                            $unit_price *= $conversion_rate;

                            #$unit_price         *= $conversion_rate if ($currencyCode ne $currency);
                            $unit_price = formatFixedPoint( $unit_price, 2 );
                            my $purchase_price;
                            if ( $row->purchase_price && $row->purchase_price ne '' ) {
                                $purchase_price = formatFixedPoint( abs( $row->purchase_price ), 2 );
                            }
                            my $list_price    = formatFixedPoint( abs( $row->list_price ), 2 );
                            my $discount      = $row->discount;
                            my $catalog_price = $self->_getCatalogPrice(
                                product_id    => $row->product_id,
                                date_begin    => $row->date_begin,
                                date_end      => $row->date_end,
                                currency_code => $row->currency,
                                price_type_id => $row->price_type_id,
                                list_price    => formatFixedPoint( $row->list_price, 2 ),
                            );

                            if (
                                $cntRecords
                                && (   $agency ne $tmplVars{agency}
                                    || $free ne $tmplVars{free}
                                    || $country ne $tmplVars{country}
                                    || $state ne $tmplVars{state}
                                    || $postal_code ne $tmplVars{postal_code}
                                    || $currency ne $tmplVars{currency}
                                    || $conversion_rate != $tmplVars{conversion_rate}
                                    || $unit_price != $tmplVars{unit_price}
                                    || $purchase_price != $tmplVars{purchase_price}
                                    || $list_price != $tmplVars{list_price}
                                    || $discount != $tmplVars{discount}
                                    || $product_id != $tmplVars{product_id}
                                    || $catalog_price != $tmplVars{catalog_price} )
                              ) {
                                $svcMonthRecords++;
                                $svcMonthUnits   += $tmplVars{units};
                                $svcMonthRevenue += $tmplVars{revenue};

                                $tmplVars{revenue} = formatFixedPoint( $tmplVars{revenue}, 2 );

                                my @detailRow = @tmplVars{ @{ $HEADER_MAIN{_order} } };
                                print OUT join( "\t", @detailRow ), "\n";

                                $cntRecords = 0;
                                %tmplVars   = undef;
                            }

                            if ( $cntRecords == 0 ) {

                                # grouping items
                                $tmplVars{agency}          = $agency;
                                $tmplVars{free}            = $free;
                                $tmplVars{country}         = $country;
                                $tmplVars{state}           = $state;
                                $tmplVars{postal_code}     = $postal_code;
                                $tmplVars{currency}        = $currency;
                                $tmplVars{conversion_rate} = $conversion_rate;
                                $tmplVars{product_id}      = $product_id;
                                $tmplVars{unit_price}      = $unit_price;
                                $tmplVars{purchase_price}  = $purchase_price;
                                $tmplVars{list_price}      = $list_price;
                                $tmplVars{discount}        = $discount;
                                $tmplVars{catalog_price}   = $catalog_price;

                                # the rest of what the detail lines need (that's static)
                                $tmplVars{record_type} = 'D';

                                # We only want to populate sale day if the date range is a single day.
                                # Otherwise, we don't really know the day so leave it blank.
                                my $saleDay;
                                if ( $row->date_begin eq $row->date_end ) {
                                    $saleDay = substr( $row->date_end, 8, 2 );
                                }
                                $tmplVars{sale_day}     = $saleDay;
                                $tmplVars{sale_month}   = substr( $row->period, 4, 2 );
                                $tmplVars{sale_year}    = substr( $row->period, 0, 4 );
                                $tmplVars{isbn}         = $row->isbn;
                                $tmplVars{product_type} = BookPub::DB::Item::BookProduct->GetProductString( $row->onix_prod_code );
                                $tmplVars{title}        = $row->title;
                                $tmplVars{subtitle}     = $row->subtitle;
                                my $bookID = $row->book_id;
                                $authorCache{$bookID} ||= BookPub::DB::Item::BookContributor->GetAuthorNamesByBookID($bookID);
                                $tmplVars{author}  = $authorCache{$bookID};
                                $tmplVars{imprint} = $self->{imprintMap}{ $row->imprint_id };
                            }

                            my $fID     = $row->file_id;
                            my $revenue = $row->revenue;
                            $revenue *= $row->conversion_rate;

                            #$revenue *= $row->conversion_rate if ($currencyCode ne $row->currency);
                            $svcFileStats{$fID}{records}++;
                            $svcFileStats{$fID}{units}   += $row->units;
                            $svcFileStats{$fID}{revenue} += $revenue;

                            # finally, the aggregated stuff
                            $tmplVars{units}   += abs( $row->units );
                            $tmplVars{revenue} += abs($revenue);

                            # for validation
                            $totalUnits += $row->units;
                            $totalRevenueUnrounded += $revenue;

                            $cntRecords++;
                        }

                        # Since we are filtering out some of the lines based on positive or negative units.
                        # Let's make sure some of them made it through before printing out the trailer.
                        if ( $rowCount > 0 ) {

                            # print the last service trailer (and detail line)
                            $tmplVars{revenue} = formatFixedPoint( $tmplVars{revenue}, 2 );

                            $svcMonthRecords++;
                            $svcMonthUnits   += $tmplVars{units};
                            $svcMonthRevenue += $tmplVars{revenue};

                            my @detailRow = @tmplVars{ @{ $HEADER_MAIN{_order} } };
                            print OUT join( "\t", @detailRow ), "\n";

                            my $buffer = '';
                            $self->_getServiceTrailer(
                                service_id    => $svcID,
                                product_type  => $prodType,
                                currency_code => $currencyCode,
                                records       => $svcMonthRecords,
                                units         => $svcMonthUnits,
                                revenue       => $svcMonthRevenue,
                                template      => \$tmplTrailer,
                                buffer_ref    => \$buffer,
                                feedId        => $feedId,
                            );
                            print OUT $buffer;

                            $rptRows    += $svcMonthRecords;
                            $rptUnits   += $svcMonthUnits;
                            $rptRevenue += $svcMonthRevenue;
                        }
                    }
                }
            }
        }

        # now that we've completed all files for the current service, validate the
        # accumulated totals against the orig file totals
        foreach my $fID ( keys %svcFileStats ) {
            if (    #$fileStats{$fID}{records} != $svcFileStats{$fID}{records}
                $fileStats{$fID}{units} != $svcFileStats{$fID}{units}
                or abs( $fileStats{$fID}{revenue} - $svcFileStats{$fID}{revenue} ) > .01
              ) {
                # !!! This isn't taking conversion rate into account,
                # !!! so I'm just going to comment out the messages for now.
                #Common::Log::Print("numbers don't match for file_id $fID");
                #Common::Log::Print('file: ' . Dumper($fileStats{$fID}));
                #Common::Log::Print('reported: ' . Dumper($svcFileStats{$fID}));
            }
        }
    }

    # print the report trailer
    my $tmpl = TMPL_TRAILER_REPORT;
    my $buffer;
    $self->{tmplObj}->process(
        \$tmpl,
        {
            lines   => $rptRows,
            units   => $rptUnits,
            revenue => formatFixedPoint( $rptRevenue, 2 ),
        },
        \$buffer
    );
    print OUT $buffer;

    close(OUT);

    # Going to send everything back in a hash so that's it's
    # easy to add more stuff in later.
    #
    my %returnValues;
    $returnValues{totalUnits}            = $totalUnits;
    $returnValues{totalRevenueUnrounded} = $totalRevenueUnrounded;

    return (%returnValues);
}

# Attempts to get the "best" catalog price for a given sale item.
# Since this accepts a date range, multiple prices can be returned.
# Preference is given to the price that matches the list price
# of the item requested. If none of the prices match, we'll just
# run with the last effective price for the specified date range.
#
# An alternative would be to grab the closest price. This might be
# preferable for all items except something with no price. In that
# case, you'd just end up with the lowest price (so there, most
# recent seems better)
sub _getCatalogPrice {
    my ( $self, %args ) = @_;

    my @catalogPrice;

    my $collection = BookPub::DB::Item::ProductMarketPrice->GetAllPrices(
        product_id    => $args{product_id},
        start_date    => $args{date_begin},
        end_date      => $args{date_end},
        currency_code => $args{currency_code},
        price_type_id => $args{price_type_id},
    );

    while ( my $dbItem = $collection->next() ) {
        push( @catalogPrice, $dbItem->price );
        if ( $dbItem->price == $args{list_price} ) {
            return $args{list_price};
        }
    }

    return $catalogPrice[0];
}

sub _getServiceHeader {
    my ( $self, %args ) = @_;
    my $localServiceId = $self->{clientServiceMap}{ $args{service_id} }{ $args{product_type} }{ $args{currency_code} }{ $args{feedId} }{id};

    $self->{tmplObj}->process(
        $args{template},
        {
            service_id => $localServiceId,
            period_id  => $self->periodID(),
            batch      => $args{batch},
            service_name =>
              $self->{clientServiceMap}{ $args{service_id} }{ $args{product_type} }{ $args{currency_code} }{ $args{feedId} }{name},
        },
        $args{buffer_ref}
    );

}

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

    $self->{tmplObj}->process(
        $args{template},
        {
            service_id =>
              $self->{clientServiceMap}{ $args{service_id} }{ $args{product_type} }{ $args{currency_code} }{ $args{feedId} }{id},
            lines   => $args{records},
            units   => abs( $args{units} ),
            revenue => formatFixedPoint( abs( $args{revenue} ), 2 ),
        },
        $args{buffer_ref}
    );
}

1;

