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

use Data::Dumper;
use Getopt::Std;
use POSIX qw(ceil);
use Carp;

use lib '/app/tools/common/lib';
use Common::Assert;

use lib '/app/tools/rps/lib';
use RPS::RoyaltyRun::Status;
use RPS::Statement::Artist::PDF;
use RPS::DB::Item::ArtistPayee;
use RPS::DB::Item::ArtistPayeeAccount;
use RPS::DB::Item::ArtistRoyaltyAlbum;
use RPS::DB::Item::ArtistRoyaltyTransaction;
use RPS::DB::Item::PendingTransaction;
use RPS::DB::Item::ArtistRoyaltyAlbumBalanceAccount;
use RPS::DB::Item::ContractLevelLicenseIncomeBalanceAccount;
use RPS::DB::Item::ArtistRoyaltyExpenseItem;
use RPS::DB::Item::ArtistRoyaltyIncomeItem;
use RPS::DB::Item::ArtistRoyaltyRun;
use RPS::DB::Item::ArtistRoyaltyStatement;
use RPS::DB::Item::Expense;
use RPS::DB::Item::FinanceAccount;
use RPS::DB::Item::FinanceTransaction;
use RPS::DB::Item::ArtistContractTermReserve;
use RPS::DB::Item::ArtistContractTermReserveRun;
use RPS::DB::Item::ReserveLiquidation;
#use RPS::DB::Item::SaleArtistRoyaltyRunMap;
use RPS::DB::Item::SaleRunMap;
use RPS::DB::Item::NewArtistContractTerm;

use lib '/app/tools/raptor/lib';
use Raptor::DB::Item::Sale;


my $gVerbosityLevel = 1;
my $gRunLabel;

# Get the id from the command-line
#
my %options;
_parseCommandLine(\%options);
_commitRun($options{clientID}, $options{runID});


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

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

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

    if (defined $opt{V})
    {
        $gVerbosityLevel = $opt{V};
    }
}

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


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

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


    # Get the run state, and make sure it isn't already committed.
    #
    my $run = RPS::DB::Item::ArtistRoyaltyRun->Lookup(artist_royalty_run_id => $runID);
    die "royalty run $runID does not exist!" unless $run;

    if (RPS::RoyaltyRun::Status::kCommitted == $run->status)
    {
        die "run $runID has already been committed";
    }

    if (RPS::RoyaltyRun::Status::kWaitingToCommit != $run->status)
    {
#        die "run $runID has not completed";
        die "run $runID is not in the 'waiting to commit' state";
    }

    # Before we start messing with any data, we need to do some pre-flight sanity checks.
    #
    if (! _preflightCheck($runID))
    {
        _report("*** preflight checks FAILED, exiting\n");
        return 1;
    }

    _report("*** Committing Artist Royalty Run $runID ***\n");
    $gRunLabel = $run->label;
    
    # Fetch all the statements in this run, and commit them.
    #
    my $statements = RPS::DB::Item::ArtistRoyaltyStatement->GetByArtistRoyaltyRunID($runID);

    _report("statements to commit: " . $statements->size(), 2);
    while ($statements->hasNext())
    {
        my $statement = $statements->next();
        _commitStatement($statement);
    }


    # Tag all sales processed this run as 'pending'.
    # Meaning we've looked at them.
    #
    my $saleMapItems = RPS::DB::Item::SaleRunMap->GetPaidByArtistRoyaltyRunID($runID);
    if ($saleMapItems)
    {
        _report("marking " . $saleMapItems->size() . " sales as processed", 2);
        while (my $saleMapItem = $saleMapItems->next())
        {
            my $sale = Raptor::DB::Item::Sale->Lookup(sale_id => $saleMapItem->sale_id);
            $sale->artist_royalty_status(Raptor::DB::Item::Sale::kPending);
            $sale->save();
        }
    }


    # decrement the periods_remaining for the reserves we 'touched' this run.
    #
    my $reserveMapItems = RPS::DB::Item::ArtistContractTermReserveRun->GetByArtistRoyaltyRunID($runID);
    while (my $reserveMap = $reserveMapItems->next())
    {
        my $reserve = RPS::DB::Item::ArtistContractTermReserve->Lookup
        (   
            artist_contract_term_reserve_id => $reserveMap->artist_contract_term_reserve_id,
        );


        my $period = $reserve->periods_remaining();

        if (1 == $period)
        {
            $reserve->liquidated_run_id($runID);
        }

        $reserve->periods_remaining($period - 1);
        $reserve->save();
    }

    # Mark this run as 'committed'
    #
    _report("setting run status to committed");
    $run->status(RPS::RoyaltyRun::Status::kCommitted);
    $run->save();

    # Mark any previous 'committed' runs as 'closed', if they have the same payor profile.
    #
    my $payorIDProfile = _getRunPayorIDProfile($runID);

    my $allRuns = RPS::DB::Item::ArtistRoyaltyRun->GetAll();
    while (my $oldRun = $allRuns->next())
    {
        my $oldRunID = $oldRun->artist_royalty_run_id;
        next if ($runID == $oldRunID);
        next unless (RPS::RoyaltyRun::Status::kCommitted == $oldRun->status());

        my $oldProfile = _getRunPayorIDProfile($oldRunID);
        if ($oldProfile eq $payorIDProfile)
        {
            _report("setting status of previous run $oldRunID to Closed");
            $oldRun->status(RPS::RoyaltyRun::Status::kClosed);
            $oldRun->save();
        }
    }


    # Re-generate the pdf statements, to remove the 'DRAFT' watermark.
    #
    my $statementsPath = RPS::Statement::Artist::PDF::StatementPathFromRunID($runID, $clientID);
    if (-d $statementsPath)
    {
        system("rm -rf $statementsPath");
    }
    my $cmd = "/app/tools/rps/bin/statements/createAllArtistStatementsPDF.pl -c $clientID -r $runID -p $statementsPath";
    system($cmd);

    _report("done");
}


sub _getRunPayorIDProfile
{
    my ($runID) = @_;

    my @payorIDs = RPS::DB::Item::ArtistRoyaltyStatement->GetDistinctPayorsByRunID($runID);
    @payorIDs = sort @payorIDs;
    my $profile = join(',', @payorIDs);
    return $profile;
}


sub _commitStatement
{
    my ($statement) = @_;

    my $statementID = $statement->artist_royalty_statement_id;
    my $payorID = $statement->payor_id;

    _report("\n------------------------------------\n", 2);

    _report("committing statement id $statementID", 2);


    # Go through each album on the statement.
    #
    my $albums = RPS::DB::Item::ArtistRoyaltyAlbum->GetByArtistRoyaltyStatementID($statementID);
    while (my $album = $albums->next())
    {
        _commitAlbum($album, $statement);
    }

    
    # Handle the crossed license income account
    #
    if ($statement->cross_collateralized_subtotal < 0)
    {
        # Carry the total balance forward, by applying the subtotal as a transaction.
        #
        if ($statement->contract_level_license_income_subtotal != 0)
        {
            _applyTransactionToContractLevelBalanceAccount($statement->payee_id, $payorID, $statement->contract_level_license_income_subtotal);
        }
    }
    else
    {
        # We paid off any previous balance, so apply a transaction to zero the accuont.
        # !!! If there is an account...
        #
        if ($statement->contract_level_license_income_previous_balance != 0)
        {
            _applyTransactionToContractLevelBalanceAccount($statement->payee_id, $payorID, (-1 * $statement->contract_level_license_income_previous_balance));
        }
    }
    

    # Now handle the holdover account
    #
    my $total = $statement->total;
    my $artistPayeeID = $statement->payee_id;

    my $payeeAccountMap = RPS::DB::Item::ArtistPayeeAccount->Lookup(artist_payee_id => $artistPayeeID, payor_id => $payorID);
    if (! $payeeAccountMap)
    {
        $payeeAccountMap = RPS::DB::Item::ArtistPayeeAccount->Create
        (
            artist_payee_id => $artistPayeeID,
            payor_id => $payorID,
            # !!! Will need to account for currency_code
        );
        $payeeAccountMap->save();
    }

    my $payeeAccountID = $payeeAccountMap->finance_account_id;
    if (! $payeeAccountID)
    {
        my $account = RPS::DB::Item::FinanceAccount->Create
        (
            description => "advance account for artist payee $artistPayeeID",
            type_code => RPS::DB::Item::FinanceAccount::kAccountTypeAdvance,
            # !!! Will need to account for currency_code
        );
        $account->save();
        $payeeAccountID = $account->finance_account_id;
        _report("created new account, id $payeeAccountID", 2);


        # Save the account id in the mapping table.
        #
        $payeeAccountMap->finance_account_id($payeeAccountID);
        $payeeAccountMap->save();
    }


    # Apply the royalties total as an adjustment
    #
    my $statementSubtotal = $statement->total;
    if ($statementSubtotal < 0 or $statementSubtotal > 0)
    {
        _report("adjusting balance by total amount of $statementSubtotal", 2);
        my $transaction = RPS::DB::Item::FinanceTransaction->Create
        (
            finance_account_id => $payeeAccountID,
            amount => $statementSubtotal,
            type_code => RPS::DB::Item::FinanceTransaction::kTypeClosingBalance,
            memo => "artist royalty run $gRunLabel",
            # !!! currency_code?
        );
        $transaction->save();
    }

    # Now apply all pending transactions, and delete them.
    #
    my $transactionItems = RPS::DB::Item::ArtistRoyaltyTransaction->GetByArtistRoyaltyStatementID($statementID);
    while (my $item = $transactionItems->next())
    {
        # Fetch the 'real' pending transaction.
        #
        my $pending = RPS::DB::Item::PendingTransaction->Lookup(pending_transaction_id => $item->pending_transaction_id);


        # !!! Note that amount == amount. no sign flipping.  We have to get the sign right when we create the
        # !!! pending transaction in the first place.
        #
        _report("applying pending transaction id " . $pending->pending_transaction_id . " amount of " . $pending->amount(), 2);
        my $transaction = RPS::DB::Item::FinanceTransaction->Create
        (
            finance_account_id => $pending->finance_account_id,
            amount => $pending->amount,
            type_code => $pending->type_code,
            memo => $pending->memo,
            check_number => $pending->check_number,
            currency_code => $pending->currency_code,
        );
        $transaction->save();

        $pending->delete();
    }
}


sub _commitAlbum
{
    my ($album, $statement) = @_;

    
    my $payorID = $statement->payor_id;

    # Need to adjust this album's balance.
    #
    my $previousBalance = $album->previous_balance;
    my $total = $album->total;
    my $appliedToAccount = 0;


    # Whether we carry a _positive_ balance forward depends on whether
    # this album is crossed, and whether the crossed total is < 0
    #
    if ($album->is_cross_collateralized())
    {
        if ($statement->cross_collateralized_subtotal < 0)
        {
            # Carry forward everything
            #
            # !!! This is WRONG !!!
            # !!! Should be $total - $previousBalance, just like always.
#            $appliedToAccount = $total;
            $appliedToAccount = $total - $previousBalance;
        }
        else
        {
            # Zero out album balances
            #
            $appliedToAccount = (-1 * $previousBalance);
        }
    }
    else
    {
        if ($previousBalance)
        {
            if ($total >= 0)
            {
                $appliedToAccount = (-1 * $previousBalance);
            }
            else
            {
                $appliedToAccount = $total - $previousBalance;
            }
        }
        elsif ($total < 0)
        {
            $appliedToAccount = $total;
        }
    }

    if (0 != $appliedToAccount)
    {

        my $artistPayeeID = $statement->payee_id;
        my $albumID = $album->album_id;
        my $artistContractID = $album->artist_contract_id;

        _report("applying balance of $appliedToAccount to holdover for album $albumID, payee $artistPayeeID", 2);


        # Get the account, post the transaction
        #
        my $accountID;
        my $accountMap = RPS::DB::Item::ArtistRoyaltyAlbumBalanceAccount->Lookup
        (
            album_id => $albumID, 
            artist_payee_id => $artistPayeeID,
            payor_id => $payorID,
            artist_contract_id => $artistContractID,
        );
        if ($accountMap)
        {
            $accountID = $accountMap->account_id;
        }
        else
        {
            # Create a new account
            #
            my $account = RPS::DB::Item::FinanceAccount->Create
            (
                description => "holdover account for album $albumID artist payee $artistPayeeID",
                type_code => RPS::DB::Item::FinanceAccount::kAccountTypeHoldover,
            # !!! Will need to account for currency_code
            );
            $account->save();
            $accountID = $account->finance_account_id;
            _report("created new holdover account, id $accountID", 2);

            # Create a new mapping
            #
            $accountMap = RPS::DB::Item::ArtistRoyaltyAlbumBalanceAccount->Create
            (
                album_id => $albumID,
                artist_payee_id => $artistPayeeID,
                payor_id => $payorID,
                account_id => $accountID,
                artist_contract_id => $artistContractID,
            # !!! Will need to account for currency_code
            );
            $accountMap->save();
        }


        # Post the transaction
        #
        my $statementID = $statement->artist_royalty_statement_id;
        my $transaction = RPS::DB::Item::FinanceTransaction->Create
        (
            finance_account_id => $accountID,
            amount => $appliedToAccount,
            type_code => RPS::DB::Item::FinanceTransaction::kTypeAdjustment,
            memo => "artist statement $statementID",
            # !!! currency_code?
        );
        $transaction->save();
    }



    # Mark all processed expenses as 'processed'
    #

    my $expenseItems = RPS::DB::Item::ArtistRoyaltyExpenseItem->GetByArtistRoyaltyAlbumID($album->artist_royalty_album_id);
    if ($expenseItems)
    {
        _report("marking " . $expenseItems->size() . " expenses as 'processed'", 2);
        while (my $expenseItem = $expenseItems->next())
        {
            my $expense = RPS::DB::Item::Expense->Lookup(expense_id => $expenseItem->expense_id);
            $expense->processed(1);
            $expense->save();
        }
    }
}



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

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



sub _preflightCheck
{
    my ($runID) = @_;

    return 0 unless _pendingTransactionsAreValid($runID);
}


sub _pendingTransactionsAreValid
{
    my ($runID) = @_;

    # Make sure nobody deleted the pending transactions that appear on this run!
    #
    my $statements = RPS::DB::Item::ArtistRoyaltyStatement->GetByArtistRoyaltyRunID($runID);
    while (my $statement = $statements->next())
    {
        my $statementID = $statement->artist_royalty_statement_id;
        my $statementTransItems = RPS::DB::Item::ArtistRoyaltyTransaction->GetByArtistRoyaltyStatementID($statementID);
        my $transItem;
        while ($transItem = $statementTransItems->next())
        {
            my $pendingTransactionID = $transItem->pending_transaction_id;
            my $pending = RPS::DB::Item::PendingTransaction->Lookup(pending_transaction_id => $pendingTransactionID);
            if (! $pending)
            {
                _report("ERROR - original pending transaction was deleted! : " . Dumper($transItem));
                return 0;
            }
        }

    }

    return 1;
}

sub _applyTransactionToContractLevelBalanceAccount
{
    my ($payeeID, $payorID, $amount) = @_;

    my $balanceAccount = RPS::DB::Item::ContractLevelLicenseIncomeBalanceAccount->Lookup(artist_payee_id => $payeeID, payor_id => $payorID);
    if (! $balanceAccount)
    {
        $balanceAccount = RPS::DB::Item::ContractLevelLicenseIncomeBalanceAccount->Create(artist_payee_id => $payeeID, payor_id => $payorID);
        $balanceAccount->save();
    }

    my $accountID = $balanceAccount->finance_account_id;
    if (! $accountID)
    {
        my $account = RPS::DB::Item::FinanceAccount->Create
        (
            description => "contract level balance account for artist payee $payeeID",
            type_code => RPS::DB::Item::FinanceAccount::kAccountTypeAdvance,
            # !!! Will need to account for currency_code
        );
        $account->save();
        $accountID = $account->finance_account_id;

        # Save the account id in the mapping table.
        #
        $balanceAccount->finance_account_id($accountID);
        $balanceAccount->save();
    }

    my $transaction = RPS::DB::Item::FinanceTransaction->Create
    (
        finance_account_id => $accountID,
        amount => $amount,
        type_code => RPS::DB::Item::FinanceTransaction::kTypeAdjustment,
        memo => "artist royalty run $gRunLabel",
    );
    $transaction->save();
}

