#------------------------------------------------------------
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package RPS::Mechanical::CA::Process::RunController;
use strict;

use Data::Dumper;
use File::Path;

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


use lib '/app/tools/rps/lib';
use lib '/app/tools/job/lib';
use lib '/app/tools/raptor/lib';

use RPS::Mechanical::CA::MechanicalRun;
use RPS::Mechanical::Process::Exception;
use RPS::DB::Item::StatRate;
use RPS::DB::Item::CALicenseReserve;
use RPS::DB::Item::CALicenseReserveRun;
use RPS::DB::Item::CAMechanicalRun;
use RPS::DB::Item::CAMechanicalRunUnlicensedTrackLog;
use RPS::DB::Item::CAMechanicalStatement;
use RPS::DB::Item::CAMechanicalStatementAdjustmentItem;
use RPS::DB::Item::CAMechanicalStatementItem;
use RPS::DB::Item::CAMechanicalStatementLicense;
use RPS::DB::Item::CAMechanicalStatementLicenseTransaction;
use RPS::DB::Item::CAMechanicalStatementTrack;
use RPS::DB::Item::CAMechanicalStatementTransaction;
use RPS::DB::Item::CAPublisher;
use RPS::DB::Item::CATrackLicense;
use RPS::DB::Item::SaleCAPublisherMap;
use RPS::Mechanical::CA::Job::CreateStatement;
use RPS::Mechanical::CA::Job::CreatePDFStatements;
use RPS::Mechanical::CA::Job::CreateTextStatements;
use RPS::Mechanical::CA::Job::CreateCMRRAStatements;
use RPS::Mechanical::CA::Job::RunMechanicalRoyalties;
use RPS::Mechanical::CA::Process::CreateStatement;
use RPS::Statement::Mechanical::CA::PDF;
use RPS::Statement::Mechanical::CA::Text;
use RPS::Statement::Mechanical::CA::CMRRA;

use Raptor::DB::Item::Sale;
use RPS::Mechanical::Process::RunController::Complex;
use RPS::Mechanical::CA::Job::CreateReservePipelineReport;
use RPS::DB::Item::ReportQueries::CAMechanicalReservePipeline;
use RPS::Mechanical::CA::Job::CreateRoyaltyExportReport;
use RPS::Mechanical::CA::Job::CreateRoyaltyExportReportExcel;
use RPS::Mechanical::CA::Job::CreateCMRRAAgentReportExcel;
use RPS::Mechanical::CA::Job::CreateCMRRASummaryReportExcel;

use base 'RPS::Mechanical::Process::RunController::Complex';

use constant kStatementVersion => 4;

sub _runType
{
    return RPS::DB::Item::SaleRunMap::kRunTypeCAMechanical;
}

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

    # Call the inherited method
    #
    my $rval = $self->SUPER::run();


    # We don't want the data in the SalePublisherMap table to persist.
    # It takes up a lot of space...
    #
    RPS::DB::Item::SaleCAPublisherMap->DeleteAllForRun($self->{_runID}, RPS::DB::Item::SaleRunMap::kRunTypeMechanical());

    # Create the reserve pipeline report
    #
    $self->_createReservePipelineReport();

    # Create the royalty data export report
    #
    $self->_createRoyaltyDataExportReport();

    # Create the CMRRA Agent and Summary reports
    #
    $self->_createCMRRAAgentReports();

    return $rval;
}

sub _createSalePublisherMapEntry
{
    my ($self, %args) = @_;
    assert($args{run_id});
    assert($args{sale_id});
    assert($args{publisher_id});

    my $newMapItem = RPS::DB::Item::SaleCAPublisherMap->Create(
        run_id => $args{run_id},
        sale_id => $args{sale_id},
        ca_publisher_id => $args{publisher_id},
    );

    $newMapItem->save();

}

sub _trackIsMechanicalExempt
{
    my ($self, $track) = @_;
    return $track->ca_mechanical_exempt();
}

sub _nativeCountryCode
{
    my ($self) = @_;
    return 'CA';
}

sub _logPublicDomainShare
{
    my ($self, $sale, $trackID, $trackLicense, $share) = @_;

    return unless ('CA' eq $sale->country_code);

    $self->SUPER::_logPublicDomainShare($sale, $trackID, $trackLicense, $share);
}


sub _getAllUnprocessedSales
{
    my ($self, $endDate) = @_;

    my $allSales = Raptor::DB::Item::Sale->GetUnprocessedCAMechanicalSales(ending_sale_date => $endDate);
    return $allSales;
}

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

    my $allPublishers = RPS::DB::Item::CAPublisher->GetAll();
    return $allPublishers;
}

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

    my $allItems = RPS::DB::Item::MechanicalRunTrackLicense->GetByRunIDAndTypeAndCountryCode($self->{_runID}, $self->_runType, 'CA');
    return $allItems;
}

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

    my $allItems = RPS::DB::Item::MechanicalRunTrackLicense->DeleteByRunIDAndType($self->{_runID}, $self->_runType);
    return $allItems;
}

sub _publisherIDFromTrackLicense
{
    my ($self, $tl) = @_;
    return $tl->ca_publisher_id;
}

sub _getReserveErrors {
    my $self = shift;

    return RPS::DB::Item::CATrackLicense->GetAllWithoutReserveLiquidation();
}

sub _getPennyRateErrors {
    my $self = shift;

    return RPS::DB::Item::CATrackLicense->GetAllWithInvalidPennyRate();
}

sub _getLicenseShareErrors {
    my $self = shift;

    return RPS::DB::Item::CATrackLicense->GetAllWithZeroShare();
}

sub _getAllTrackLicenses
{
    my $self = shift;

    return RPS::DB::Item::CATrackLicense->GetAll();
}

sub _publisherIDFromPublisher
{
    my ($self, $publisher) = @_;

    return $publisher->ca_publisher_id();
}

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

    my %idHash;
    my $payorID = $self->{_payorID};
    my $collection = RPS::DB::Item::CAPublisher->GetAllActiveOrOnHoldWithLicensesForPayor($payorID);
    while (my $publisher = $collection->next())
    {
        $self->_report("_getAllActivePublisherIDs : publisher:", 4);
        $self->_report($publisher, 4);

        # Add this publisher to the hash
        #
        $idHash{$publisher->ca_publisher_id} = 1;

        # Also add this guys admin or agent id..
        # agent trumps admin
        #
        if ($publisher->agent_id)
        {
            $idHash{$publisher->agent_id} = 1;
        }
        elsif ($publisher->admin_id)
        {
            $idHash{$publisher->admin_id} = 1;
        }
    }

    return keys %idHash;
}

sub _getRunDBItem
{
    my ($self) = @_;
    assert($self->{_runID});

    my $dbItem = RPS::DB::Item::CAMechanicalRun->Lookup(ca_mechanical_run_id => $self->{_runID});
    return $dbItem;
}

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

    $self->_testPendingTransactionsAreValid();

	# See if all the reserves are present and accounted for...
	#
	if (RPS::DB::Item::CALicenseReserveRun->CountMissingRunReserves($self->{_runID}) > 0)
	{
		die RPS::Mechanical::Process::Exception->new("ERROR - Not all run reserves are accounted for");
	}

	return 1;
}


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

    my $runID = $self->{_runID};

    # Make sure nobody deleted the pending transactions that appear on this run!
    #
    my $statements = RPS::DB::Item::CAMechanicalStatement->GetByMechanicalRunID($runID);
    while (my $statement = $statements->next())
    {
        my $statementID = $statement->ca_mechanical_statement_id;
        my $statementTransItems = RPS::DB::Item::CAMechanicalStatementTransaction->GetByMechanicalStatementID($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)
            {
                die RPS::Mechanical::Process::Exception->new("ERROR - original pending transaction was deleted! : ", $transItem);
            }
        }

        my $licenseTransItems = RPS::DB::Item::CAMechanicalStatementLicenseTransaction->GetByMechanicalStatementID($statementID);
        while ($transItem = $licenseTransItems->next())
        {
            my $pendingTransactionID = $transItem->pending_transaction_id;
            my $pending = RPS::DB::Item::PendingTransaction->Lookup(pending_transaction_id => $pendingTransactionID);
            if (! $pending)
            {
                die RPS::Mechanical::Process::Exception->new("ERROR - original pending transaction was deleted! : ", $transItem);
            }
        }
    }

    return 1;
}


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

     # !!! We don't DO carryover anymore.

    # What we're really doing here is clearing out the _old_ carryovers.
    # Each 'statement' will then create entries for the new carryover (later).
    #

    # Truncate the carryover table.
    #
#    my $runCarryoverIDs = RPS::DB::Item::MechanicalRunCarryover->GetByMechanicalRunID($self->{_runID});
#    while (my $carryoverMapItem = $runCarryoverIDs->next())
#    {
#        my $carryoverID = $carryoverMapItem->mechanical_carryover_id;
#        my $carryover = RPS::DB::Item::MechanicalCarryover->Lookup(mechanical_carryover_id => $carryoverID);
#        $carryover->delete();
#    }


    # Get rid of entries in the carryover run map
    #
# !!! KEEP FOR DEBUGGING
#    RPS::DB::Item::MechanicalRunCarryover->DeleteByMechanicalRunID($self->{_runID});
}


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

    my $runID = $self->{_runID};

    # 'turn the crank' on the reserves table.
    #
    my $runReserves = RPS::DB::Item::CALicenseReserveRun->GetByMechanicalRunID($runID);
    while (my $runReserveItem = $runReserves->next())
    {
        my $reserve = RPS::DB::Item::CALicenseReserve->Lookup(ca_license_reserve_id => $runReserveItem->ca_license_reserve_id);

		if (! $reserve)
		{
			# This should never happen!
			#
			$self->_report("ERROR!  reserve id " . $runReserveItem->ca_license_reserve_id . " is MISSING");
			next;
		}
        my $period = $reserve->periods_remaining();

        if (1 == $period)
        {
            # This reserve has been liquidated;
            # So we'll make note of the run id.
            #
            $reserve->liquidated_run_id($runID);
        }

        # Just decrement the periods_remaining column.
        # Liquidated reserves will end up with '0' periods_remaining.
        #
        $reserve->periods_remaining($period - 1);
        $reserve->save();
    }

}


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


    # Instantiate the proper CreatePublisherStatement entity, and let that handle the details.
    #
    my $statementProcessor = RPS::Mechanical::CA::Process::CreateStatement->new(statementID => $statement->ca_mechanical_statement_id);
    $statementProcessor->commit();
}


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

    my $statements = RPS::DB::Item::CAMechanicalStatement->GetByMechanicalRunID($self->{_runID});
    return $statements;
}


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

    RPS::DB::Item::SaleRunMap->DeleteByCAMechanicalRunID($self->{_runID});
}


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

    my $logBasePath = RPS::Mechanical::CA::Job::RunMechanicalRoyalties->LogFilePath(Common::RSApp::GetClientID(), $self->{_runID});
    return $logBasePath;
}


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

    RPS::DB::Item::CALicenseReserveRun->DeleteByMechanicalRunID($self->{_runID});
#    RPS::DB::Item::CAMechanicalRunCarryover->DeleteByMechanicalRunID($self->{_runID});

    $self->SUPER::delete();

    # MAKE SURE this is emptied out.
    # We clear this table when a run exits normally, but if something went haywire we might need to try again.
    #
    RPS::DB::Item::SaleCAPublisherMap->DeleteAllForRun($self->{_runID});

    # Clear out any associated pipeline report
    #
    RPS::DB::Item::ReportQueries::CAMechanicalReservePipeline->DeleteByMechanicalRunID($self->{_runID});

    return 0;
}



sub _deleteStatement
{
    my ($self, $statement) = @_;

    my $statementID = $statement->ca_mechanical_statement_id;

    $self->_report("deleting statement $statementID");
    $statement->delete();

    $self->_report("  ... deleting reserve items",2);
    RPS::DB::Item::CALicenseReserve->DeleteByStatementID($statementID);

    $self->_report("  ... deleting track items",2);
    RPS::DB::Item::CAMechanicalStatementTrack->DeleteByMechanicalStatementID($statementID);

    $self->_report("  ... deleting license items",2);
    RPS::DB::Item::CAMechanicalStatementLicense->DeleteByMechanicalStatementID($statementID);

    $self->_report("  ... deleting adjustment items",2);
    RPS::DB::Item::CAMechanicalStatementAdjustmentItem->DeleteByMechanicalStatementID($statementID);

    $self->_report("  ... deleting statement items",2);
    RPS::DB::Item::CAMechanicalStatementItem->DeleteByMechanicalStatementID($statementID);

    return 0;
}


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

    # Before we kick off the individual publisher jobs, we
    # need to 'pre-scan' the sale table.
    # For huge customers, I don't want to have every job looking at every sale.
    # So we will do a quick (15 minutes or so for our biggest clients) sorting process.
    #
    $self->_createSalePublisherMapping();

    $self->SUPER::_createStatementsAndJobs();
}


sub _getStatement
{
    my ($self, $statementID) = @_;

    my $statement = RPS::DB::Item::CAMechanicalStatement->Lookup(ca_mechanical_statement_id => $statementID);
    return $statement;
}


sub _createDirectPublisherStatementJob
{
    my ($self, $publisherID) = @_;
    assert($publisherID);

    # First, create an 'empty' statement database record.
    #
    my $statementObj = RPS::DB::Item::CAMechanicalStatement->Create
    (
        ca_mechanical_run_id => $self->{_runID},
        payor_id => $self->{_payorID},
        ca_publisher_id => $publisherID,
        status => RPS::Statement::Status::kNotQueued(),
        version => kStatementVersion,
    );
    $statementObj->save();

    my $statementID = $statementObj->ca_mechanical_statement_id();


    # Create the Job object
    #
    my $job = RPS::Mechanical::CA::Job::CreateStatement->new(runID => $self->{_runID}, statementID => $statementID, logLevel => $self->{_logLevel});
    $job->enqueue();


    # Update the statement record with the job id, and
    # update the status to 'in queue'
    #
    $statementObj->job_id($job->id());
    $statementObj->status(RPS::Statement::Status::kInQueue());
    $statementObj->save();

    return $job;
}


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


    # Make sure the path exists
    #
    my $filePath = RPS::Statement::Mechanical::CA::PDF->StatementPathFromRunID($self->{_runID});
    if (! -d $filePath)
    {
        mkpath($filePath) or die RPS::Mechanical::Process::Exception->new("Unable to create path $filePath: $!");
    }



    # Create a job for all statements, and put it in the queue.
    #
    my $clientID = Common::RSApp::GetClientID();


    my $jobArgs = RPS::Mechanical::CA::Job::CreatePDFStatements->new
    (
        runID => $self->{_runID},
        clientID 	=> $clientID,
        filePath 	=> $filePath,
        mode     	=> 'b',
    );
    my $job = $jobArgs->enqueue();

}

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

    # This report will be stored in the same directory as the mechanical statements.
    #
    my $filePath = RPS::Statement::Mechanical::CA::Text->StatementPathFromRunID($self->{_runID});
    if (! -d $filePath)
    {
        mkpath($filePath) or die RPS::Mechanical::Process::Exception->new("ERROR: Unable to create path $filePath: $!");
    }

    my $clientID = Common::RSApp::GetClientID();

    # Queue the text report
    #
    my $jobArgs = RPS::Mechanical::CA::Job::CreateRoyaltyExportReport->new
    (
        runID => $self->{_runID},
        clientID    => $clientID,
        filePath    => $filePath,
    );
    my $job = $jobArgs->enqueue();

    # Queue the Excel report
    #
    my $jobArgs = RPS::Mechanical::CA::Job::CreateRoyaltyExportReportExcel->new
    (
        runID => $self->{_runID},
        clientID    => $clientID,
        filePath    => $filePath,
    );
    my $job = $jobArgs->enqueue();

    $self->_report("_createRoyaltyDataExportReport - filePath=$filePath", 2);
}

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

    # This report will be stored in the same directory as the mechanical statements.
    #
    my $filePath = RPS::Statement::Mechanical::CA::Text->StatementPathFromRunID($self->{_runID});
    if (! -d $filePath)
    {
        mkpath($filePath) or die RPS::Mechanical::Process::Exception->new("ERROR: Unable to create path $filePath: $!");
    }

    my $clientID = Common::RSApp::GetClientID();

    # Queue the CMRRA Agent report
    #
    my $jobArgs = RPS::Mechanical::CA::Job::CreateCMRRAAgentReportExcel->new
    (
        runID => $self->{_runID},
        clientID    => $clientID,
        filePath    => $filePath,
    );
    my $job = $jobArgs->enqueue();

    # Queue the CMRRA summary report
    #
    my $jobArgs = RPS::Mechanical::CA::Job::CreateCMRRASummaryReportExcel->new
    (
        runID => $self->{_runID},
        clientID    => $clientID,
        filePath    => $filePath,
    );
    my $job = $jobArgs->enqueue();

    $self->_report("_createAgentReports - filePath=$filePath", 2);
}

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

    my $filePath = RPS::Statement::Mechanical::CA::Text->StatementPathFromRunID($self->{_runID});
    if (! -d $filePath)
    {
        mkpath($filePath) or die RPS::Mechanical::Process::Exception->new("ERROR: Unable to create path $filePath: $!");
    }

    # Create a job for all text statements, and put it in the queue.
    #
    my $clientID = Common::RSApp::GetClientID();

    my $jobArgs = RPS::Mechanical::CA::Job::CreateTextStatements->new
    (
        runID => $self->{_runID},
        clientID 	=> $clientID,
        filePath 	=> $filePath,
    );


    my $job = $jobArgs->enqueue();


    # We also need to create the CMRRA formatted text statements
    #
    # Create a job for all statements, and put it in the queue.
    #

    $jobArgs = RPS::Mechanical::CA::Job::CreateCMRRAStatements->new
    (
        runID => $self->{_runID},
        clientID 	=> $clientID,
        filePath 	=> $filePath,
    );


    $job = $jobArgs->enqueue();

}


sub _createUnlicensedTrackLogEntry
{
    my ($self, $album, $label, $track, $product, $trackArtistName, $isrc, $upc, $productType, $missingShare, $totalOwed, $units, $year, $rate) = @_;

    # Skip tracks that are mechanical exempt
    #
    return if ($track->ca_mechanical_exempt);
    
    # Grab the product title from the parent product for digital track products
    my $productTitle;
    
    if ($product->product_type_id == RPS::DB::Item::Product::kProductTypeDigitalTrack && $product->parent_product_id) 
    {
        my $parentProduct = $self->_getProduct($product->parent_product_id);
        $productTitle = $parentProduct->title;
    } 
    else 
    {
        $productTitle = $product->title;
    }     

    my $log = RPS::DB::Item::CAMechanicalRunUnlicensedTrackLog->Create
    (
	    ca_mechanical_run_id => $self->{_runID},
        #run_type => $self->_runType(),
        label_id => $album->label_id,
        label_name => $label->label_name,
        catalog_number => $album->catalog_number,
	    track_id => $track->track_id,
	    album_id => $album->album_id,
	    album_title => $album->title,
        track_artist_name => $trackArtistName,
	    track_title => $track->title,
	    product_id => $product->product_id,
	    product_title => $productTitle,		    
        isrc => $isrc,
	    upc_ean => $upc,
	    product_type => $productType,
	    unlicensed_share => $missingShare,
	    accrual => $totalOwed,
	    year => $year,
	    rate => $rate,
	    units => $units,
    );

    return $log;
}

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

    my $runID = $self->{_runID};
    return RPS::DB::Item::CAMechanicalRun->GetOtherCommittedRuns($runID);
}

sub _getStatRates
{
    my ($self) = @_;
    return RPS::DB::Item::StatRate->GetRatesCA();
}

sub _ringtoneStatRateType
{
    my ($self) = @_;
    return RPS::DB::Item::StatRate::kCARingStatRateType;
}

sub _idFromTrackLicense
{
    my ($self, $trackLicense) = @_;
    return $trackLicense->ca_track_license_id();
}

sub _trackLicenseFromID
{
    my ($self, $trackLicenseID) = @_;

    return RPS::DB::Item::CATrackLicense->Lookup(ca_track_license_id => $trackLicenseID);
}

sub _defaultIssueStatRateID { 

    my ($self, $licenseType) = @_;
    # This is the only possible value for CA (for now).
    #
    return 26; 
}

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

    $self->_report("*** adding reserve pipeline job to queue");
    my $clientID = Common::RSApp::GetClientID();

    my $jobArgs = RPS::Mechanical::CA::Job::CreateReservePipelineReport->new
    (
        runID           => $self->{_runID},
        clientID        => $clientID,
    );
    my $job = $jobArgs->enqueue();
    $self->_report("*** done adding reserve pipeline job to queue!");
}


1;
