#!/usr/bin/perl

use strict;
use Getopt::Std;
use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::RSApp;
use Common::RSMath;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::ArtistContractTermReserve;
use RPS::DB::Item::ArtistRoyaltyIncomeItem;
use RPS::DB::Item::ArtistRoyaltyRun;
use RPS::DB::Item::ContractRateType;
use RPS::DB::Item::NewArtistContract;
use RPS::DB::Item::NewArtistContractTerm;
use RPS::DB::Item::Product;
use RPS::DB::Item::Track;

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

# Parse the command-line.
#
my %options;
parseCommandLine( \%options );

# Keep track of product reserves hitting a contract by period.  During an artist
# run, multiple reserve entries may be created if a contract is attached to tracks
# on a product from which reserves are being taken.  These reserves will contain
# the same reserve unit information, repeated for each attached track.  For pipeline
# report purposes, we only want to count the reserves once per product per period
# against a given artist contract.
#
# The contractProductMap hash allows to keep track of whether the pipeline report has
# already seen a product's reserves as held against each track attached to a contract.
#
# The format of the hash is $contractProductMap{$contractID}{$productID}{$reservePeriod}
#
my %contractProductMap;

createArtistReservePipelineReport( $options{clientID}, $options{runID} );

#
# --- Subroutines
#

sub createArtistReservePipelineReport {
    my ( $clientID, $runID ) = @_;

    # Instantiate the application singleton object.
    #
    my $appSingleton = Common::RSApp->new( clientID => $clientID );

    my %gPipeline;

    my $run       = RPS::DB::Item::ArtistRoyaltyRun->Lookup( artist_royalty_run_id => $runID );
    my $runStatus = $run->status();
    my $payorID   = $run->payor_id;

    _report( "---checking for reserves in run: id = " . $runID );

    _report( "---run status = " . $runStatus );

    _report( "---payor id = " . $payorID );

    # Let's grab all of the reserves from the current run first
    #
    my $currentReserves = RPS::DB::Item::ArtistContractTermReserve->GetByArtistRoyaltyRunID($runID);
    my $currentTaken    = 1;

    while ( my $runReserveItem = $currentReserves->next() ) {
        addReservesToHash( $runReserveItem, $currentTaken, $runStatus, \%gPipeline );

    }

    if ( $runStatus == 2 ) {

        # Now let's grab all of the reserves liquidated in the current run.
        # This will only work if the run has been committed.
        my $currentLiquidatedReserves = RPS::DB::Item::ArtistContractTermReserve->GetLiquidatedByArtistRoyaltyRunID($runID);
        $currentTaken = 0;

        while ( my $runReserveItem = $currentLiquidatedReserves->next() ) {
            addReservesToHash( $runReserveItem, $currentTaken, $runStatus, \%gPipeline );

        }
    }

    # Now we'll grab all of the reserves from the committed runs.
    # We're are also going to pass in the current run id, so that we can run this report
    # even if the current run has been committed.
    # However, if subsequent runs have been completed/committed, this report will not be accurate.
    #
    my $committedReserves = RPS::DB::Item::ArtistContractTermReserve->GetAllCommittedByPayorID( $payorID, $runID );
    $currentTaken = 0;

    while ( my $runReserveItem = $committedReserves->next() ) {
        addReservesToHash( $runReserveItem, $currentTaken, $runStatus, \%gPipeline );

    }

    # Write the data out to the table
    #

    foreach my $regionID ( keys %gPipeline ) {

        #_report("---processing reserves for region id = ".$regionID);
        my $artistPayeeIDHash = $gPipeline{$regionID};

        foreach my $artistPayeeID ( keys %$artistPayeeIDHash ) {

            #_report("---processing artist payee id = ".$artistPayeeID);
            my $albumIDHash = $artistPayeeIDHash->{$artistPayeeID};

            foreach my $albumID ( keys %$albumIDHash ) {
                my $data = $albumIDHash->{$albumID};

                #_report("---processing album id = ".$albumID);
                #_report("-----total revenue = ".$data->{totalRevenue});
                #_report("-----total units = ".$data->{totalUnits});

                my $insertSQL =
                    "INSERT INTO artist_reserve_pipeline SET "
                  . 'artist_royalty_run_id=?,'
                  . 'artist_payee_id=?,'
                  . 'album_id=?,'
                  . 'region_id=?,'
                  . 'total_revenue=?,'
                  . 'total_units=?,'
                  . 'revenue_0=?,'
                  . 'units_0=?,'
                  . 'revenue_1=?,'
                  . 'units_1=?,'
                  . 'revenue_2=?,'
                  . 'units_2=?,'
                  . 'revenue_3=?,'
                  . 'units_3=?,'
                  . 'revenue_4=?,'
                  . 'units_4=?,'
                  . 'revenue_5=?,'
                  . 'units_5=?,'
                  . 'revenue_6=?,'
                  . 'units_6=?,'
                  . 'revenue_7=?,'
                  . 'units_7=?,'
                  . 'revenue_8=?,'
                  . 'units_8=?,'
                  . 'revenue_9=?,'
                  . 'units_9=?';

                my $dbo = Common::RSApp::GetClientDB();
                $dbo->DoCmdWithPlaceholders(
                    $insertSQL,
                    [
                        $runID,              $artistPayeeID,      $albumID,            $regionID,           $data->{totalRevenue},
                        $data->{totalUnits}, $data->{0}{revenue}, $data->{0}{units},   $data->{1}{revenue}, $data->{1}{units},
                        $data->{2}{revenue}, $data->{2}{units},   $data->{3}{revenue}, $data->{3}{units},   $data->{4}{revenue},
                        $data->{4}{units},   $data->{5}{revenue}, $data->{5}{units},   $data->{6}{revenue}, $data->{6}{units},
                        $data->{7}{revenue}, $data->{7}{units},   $data->{8}{revenue}, $data->{8}{units},   $data->{9}{revenue},
                        $data->{9}{units}
                    ]
                );
            }
        }
    }
}

sub addReservesToHash {
    my ( $runReserveItem, $currentTaken, $runStatus, $pipeline ) = @_;

    my $reserveID = $runReserveItem->artist_contract_term_reserve_id;

    my $reserve       = RPS::DB::Item::ArtistContractTermReserve->Lookup( artist_contract_term_reserve_id => $reserveID );
    my $channelID     = $reserve->channel_id;
    my $price         = $reserve->price;
    my $priceLevelID  = $reserve->price_level_id;
    my $productID     = $reserve->product_id;
    my $regionID      = $reserve->region_id;

    my $product = RPS::DB::Item::Product->Lookup( product_id => $productID );
    my $albumID = _getAlbumIDFromProduct($product);

    my $termID           = $reserve->artist_contract_term_id;
    my $term             = RPS::DB::Item::NewArtistContractTerm->Lookup( artist_contract_term_id => $termID );
    my $artistContractID = $term->artist_contract_id;
    my $artistContract   = RPS::DB::Item::NewArtistContract->Lookup( artist_contract_id => $artistContractID );
    my $artistPayeeID    = $artistContract->artist_payee_id;

    my $reservePeriod = $reserve->periods_remaining;

    my $reserveRevenue = $reserve->revenue;
    my $reserveUnits   = $reserve->units;
    my $liquidated     = $reserve->liquidated_run_id;

    if ( $runStatus != 2 && $reservePeriod == 1 ) {
        $liquidated = 1;
    }

    # Make sure we don't overcount artist_contract_term_reserve period entries
    # for a product against an artist contract.
    #
    if ( exists $contractProductMap{$artistContractID}{$productID}{$regionID}{$channelID}{$priceLevelID}{$price}{$reservePeriod} ) {
        return;
    } else {
        # Flag the reserves for this contract/product/region/channel/price level/period as 'seen'.
        #
        $contractProductMap{$artistContractID}{$productID}{$regionID}{$channelID}{$priceLevelID}{$price}{$reservePeriod} = 1;
    }

    if ( $runStatus == 2 ) {

        # I'm going to increment this by one to make room for the current
        # liquidations if we are dealing with a committed run.
        #
        $reservePeriod++;
    }

    my $effectiveRate = _calculateEffectiveRate( $reserve, $term, $artistContract );

    my $incomeItemID = $reserve->original_statement_item_id;
    my $incomeItem = RPS::DB::Item::ArtistRoyaltyIncomeItem->Lookup( artist_royalty_income_item_id => $incomeItemID );
    my $units;
    my $grossSales;
    my $revenueReserved;
    my $reserveRate;
    my $totalUnitsReserved;
    my $liquidationPercentage;
    my $liquidationUnits;

    if ($incomeItem) {
        $units           = $incomeItem->net_units;
        $grossSales      = $incomeItem->revenue;
        $revenueReserved = $incomeItem->revenue_reserved;
        if ( $revenueReserved != 0 && $grossSales != 0 ) {
            $reserveRate        = $revenueReserved / $grossSales;
            $totalUnitsReserved = Common::RSMath::round( $units * $reserveRate );

            $liquidationPercentage = $reserveRevenue / $revenueReserved;
            $liquidationUnits      = $totalUnitsReserved * $liquidationPercentage;

        }
    }

    if ( !$liquidated ) {

        if ( $reserve->revenue_based ) {
            if ($incomeItem) {
                $pipeline->{$regionID}{$artistPayeeID}{$albumID}{totalUnits} += $liquidationUnits;
            }
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{totalRevenue} += $reserveRevenue;

        } else {
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{totalUnits}   += $reserveUnits;
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{totalRevenue} += ( $reserveUnits * $effectiveRate );
        }
    } else {

        #_report("-------found liquidations for this run: reserve id = $reserveID");
        # If these reserves are being liquidated, we'll set the reserve period to 1
        # so that they show up in the proper place in the hash.
        #
        $reservePeriod = 1;
    }

    if ( $reserve->revenue_based ) {

        #_report("-------reserve period = ".$reservePeriod." -- revenue = ".$reserveRevenue);
        $pipeline->{$regionID}{$artistPayeeID}{$albumID}{$reservePeriod}{revenue} += $reserveRevenue;

        if ($incomeItem) {
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{$reservePeriod}{units} += $liquidationUnits;
        }

    } else {

        #_report("-------reserve period = ".$reservePeriod." -- units = ".$reserveUnits);
        $pipeline->{$regionID}{$artistPayeeID}{$albumID}{$reservePeriod}{units}   += $reserveUnits;
        $pipeline->{$regionID}{$artistPayeeID}{$albumID}{$reservePeriod}{revenue} += ( $reserveUnits * $effectiveRate );

    }

    # We want to display a total for reserves taken in the current period.
    #
    if ( $currentTaken == 1 ) {
        if ( $reserve->revenue_based ) {

            #_report("-------current period = ".$reservePeriod." -- revenue = ".$reserveRevenue);
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{0}{revenue} += $reserveRevenue;

            if ($incomeItem) {
                $pipeline->{$regionID}{$artistPayeeID}{$albumID}{0}{units} += $liquidationUnits;
            }
        } else {

            #_report("-------current period = ".$reservePeriod." -- units = ".$reserveUnits);
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{0}{units}   += $reserveUnits;
            $pipeline->{$regionID}{$artistPayeeID}{$albumID}{0}{revenue} += ( $reserveUnits * $effectiveRate );

        }
    }

}

sub _calculateEffectiveRate {
    my ( $reserve, $term, $contract ) = @_;

    my $effectiveRate;

    my $baseRate           = $reserve->effective_rate;
    my $price              = $reserve->price;
    my $rateReduction      = $term->rate_reduction;
    my $percentageOfSales  = $term->percentage_of_sales;
    my $packagingDeduction = $term->packaging_deduction;
    my $freeGoods          = $term->free_goods_deduction;

    #if ($reserve->revenue_based)
    #{
    #
    #    $effectiveRate = $baseRate;
    #    $effectiveRate = ($effectiveRate * ($percentageOfSales / 100)) if $percentageOfSales;
    #    $effectiveRate = ($effectiveRate * ($rateReduction / 100)) if $rateReduction;
    #    $effectiveRate = ($effectiveRate * ((100 - $packagingDeduction) / 100)) if $packagingDeduction;
    #
    #}
    #else
    #{

    $effectiveRate = $price;
    my $rateType = $term->contract_rate_type_id;
    if ( RPS::DB::Item::ContractRateType::kRateTypeFixed == $rateType ) {
        $effectiveRate = ( $effectiveRate * $baseRate );
    } else {
        $effectiveRate = ( $effectiveRate * ( $baseRate / 100 ) );
    }
    $effectiveRate = ( $effectiveRate * ( $rateReduction / 100 ) ) if $rateReduction;
    $effectiveRate = ( $effectiveRate * ( ( 100 - $packagingDeduction ) / 100 ) ) if $packagingDeduction;

    #}

    return $effectiveRate;

}

sub _getAlbumIDFromProduct {
    my ($product) = @_;

    if ( RPS::DB::Item::Product::kProductTypeDigitalTrack == $product->product_type_id ) {
        my $trackID = $product->asset_id;
        my $track = RPS::DB::Item::Track->Lookup( track_id => $trackID );
        return $track->album_id;
    } else {
        return $product->asset_id;
    }
}

#
# Boring script stuff below...
#

sub parseCommandLine {
    my ($settings) = @_;

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

    if ( !$opt{c} || !$opt{r} ) {
        usage();
        exit(1);
    }
    $settings->{clientID} = $opt{c};
    $settings->{runID}    = $opt{r};

}

sub usage {
    print STDERR "\nusage: $0 -c <client_id> -r <run_id>\n";
    print STDERR "\n";
    print STDERR "Arguments:\n";
    print STDERR "\t-c <client_id>\t\t\tThe client_id of the client to process\n";
    print STDERR "\t-r <run_id>\tThe id of the artist royalty run to process\n";
}

sub _report {
    my ( $string, $verbosity ) = @_;
    $verbosity = 1 unless defined $verbosity;

    if ( $gVerbosityLevel >= $verbosity ) {
        print STDERR $string . "\n";
    }
}
