#------------------------------------------------------------
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------

package RPS::Mechanical::Process::CreateStatement;

# This class will serve as the base class for the processes that create
# Mechanical statements.  There should be a fair amount of common code, so that
# will go here.
#
# The basic idea is the overall 'flow' will be defined here.  Meaning, the
# essential steps will be encapsulated as abstract methods which subclasses
# will override.  And there will be a 'createStatement' method that is implemented
# here in terms of these abstract methods.
#
use strict;

use lib '/app/tools/common/lib';
use lib '/app/tools/rps/lib';
use lib '/app/tools/raptor/lib';
use Data::Dumper;
use Common::Log;
use Common::Assert;
use Common::Util;
use RPS::Mechanical::Process;
use RPS::Statement::Status;
use RPS::Mechanical::Process::SaleException;
use RPS::DB::Item::Product;
use RPS::DB::Item::SaleRunMap;
use RPS::DB::Item::ProductType;

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

use constant kDefaultLogLevel => 2;



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


    # Invoke the inherited method to set up logging.
    #
    $self->SUPER::_init(%args);

    # The statementID is required.
    # AND the statement should exist in the database already.
    #
    assert($args{statementID});

    $self->{_statementID} = $args{statementID};


    # Grab the 'run' data, so we can fetch the payor_id.
    #
    my $run = $self->_getRunDBItem();
    $self->{_payorID} = $run->payor_id;

    $self->_report("CreateStatement process for statement " . $self->{_statementID} . " initialized", 3);
    return $self;
}

sub _statementID
{
    my ($self) = @_;
    return $self->{_statementID};
}
    
sub _payorID
{
    my ($self) = @_;
    return $self->{_payorID};
}


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

    my $exception = $args{exception};

    my $errorString;
    if (ref $exception && $exception->isa('Common::Exception'))
    {
        $errorString = $exception->errorMessage();
    }
    else
    {
        $errorString = $exception;
    }

    my $statement = $self->_getStatementDBItem();
    $statement->error($errorString);
    $statement->status(RPS::Statement::Status::kError);
    $statement->save();
}


sub run
{
    my ($self) = @_;
    $self->_report("Entering run");

    my $statement = $self->_getStatementDBItem();

    $statement->status(RPS::Statement::Status::kRunning);
    $statement->save();



    # We'll iterate over all the possible licenses first.
    # This will allow us to pull in reserves, carryover,etc.
    #
    my $allLicenses = $self->_getAllPossibleLicenses();
    $self->_report("Processing reserves and carryover");
    while (my $license = $allLicenses->next())
    {
        $self->_processLicense($license);
    }


    # Get the array of sale IDs we plan on processing.
    #
    my $saleIDs = $self->_getSaleIDs();
    $self->_report("Fetched sales. Count = " . scalar(@$saleIDs));
    $self->_processSales($saleIDs);


    # I would imagine that we'd want to finalize the statement in some fashion - Calculate
    # sums and so forth
    #
    $self->_report("Finalizing");
    $self->_finalize();
    $self->_report("Done, marking statement as complete");

    $statement->status(RPS::Statement::Status::kComplete);
    $statement->save();

    return 0;
}


sub _processSales
{
    my ($self, $saleIDs) = @_;


    $self->_report("Processing sales");
    foreach my $saleID (@$saleIDs)
    {
        my $sale = Raptor::DB::Item::Sale->Lookup(sale_id => $saleID);

        $self->_report("--------------------------------------------------------", 3);
        $self->_report("  sale_id " . $sale->sale_id, 3);
        $self->_report("--------------------------------------------------------", 3);

        # Skip inactive products.
        #
        my $product = RPS::DB::Item::Product->Lookup(product_id => $sale->product_id);
        if (RPS::DB::Item::Product::kProductStatusActive != $product->product_status_id())
        {
            $self->_report("Product inactive, skipping", 3);
            $self->_markSaleError($sale, RPS::DB::Item::SaleRunMap::kStatusInactiveProduct);
            next;
        }

        # I want to catch any exceptions, so I can display the naughty sale id.
        #
        eval {
            $self->_processSale($sale);
        };

        if ($@)
        {
            if (ref $@ && $@->isa('RPS::Mechanical::Process::SaleException'))
            {
                # We can log these.
                #
                $self->_logSaleException($@);
            }
            else
            {
                # Something else (bad) happened.
                # Re-throw the original exception
                #
                die($@);
            }
        }
    }
}


sub _processLicense
{
    my ($self, $license) = @_;

    $self->_processLicenseReserves($license);
    $self->_processLicenseCarryover($license);
}


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

    # !!! We'll probably have a different accessor in the Sale class for each
    # !!! type of statement.  So I don't think there will be any inherited behavior.
    #
    die "ERROR - You must overload _getSales";
}


# This can probably be implemented generically.
# !!! For a given sale, though, there can be _multiple_ licenses!
# !!! For every album sale, for example, it may will map to several
# !!! licenses for this publisher.  That's just the way it is, I need to re-do this
#
sub _processSale
{
    my ($self, $sale) = @_;

    $self->_report("processSale", 4);
    $self->_report($sale, 4);

    # Quick sanity-check for bad sales.
    #
    die RPS::Mechanical::Process::SaleException::NoDateEnd->new($sale, 'Sale has invalid date_end, unable to determine stat rate')
     unless ($sale->date_end() && $sale->date_end ne '0000-00-00');

    # Match this sale to the correct license.
    #
    # So will there be more than 1?   I don't think so, not in the single-statement
    # context we have here.
    #
    my $licenses = $self->_findMatchingLicenses($sale);
    if (! $licenses || 0 == scalar @$licenses)
    {
        $self->_report("   no license found", 3);


        # So, if we didn't find a license, we want to _report_ that, so we can 
        # display it as '100% unlicensed' in the retention report.
        #
        $self->_logSaleNoLicense($sale);


        # It would be swell if we could combine these logs somehow...
        #
        $self->_markSaleError($sale, RPS::DB::Item::SaleRunMap::kStatusNoLicense);

        die RPS::Mechanical::Process::SaleException::NoLicense->new($sale, 'No license could be found to match this sale');
    }

    
    # There will be _vastly_ different strategies for this step.
    #
    $self->_processSaleWithLicenses($sale, $licenses);

}

sub _processSaleWithLicenses
{
    my ($self, $sale, $licenses) = @_;

    $self->_report('processSaleWithLicenses : sale_id ' . $sale->sale_id, 3);
    $self->_report('   number of licenses: ' . scalar @$licenses, 3);

    # !!! Need to add back in the previous paid statement logic!

    foreach my $trackLicense (@$licenses)
    {
        # !!! Or can I expect the licenses to have been scrubbed already?
        #
        if (! $self->_licenseIsValid($trackLicense))
        {
            next;
        }

        if ($self->_testAlreadyPaidSaleWithLicense($sale, $trackLicense))
        {
            $self->_report('already paid');
            $self->_processAlreadyPaidSaleWithLicense($sale, $trackLicense);
        }
        else
        {
            $self->_processSaleWithLicense($sale, $trackLicense);
        }

        # Adding a stub in here so we have a place to log for reporting purposes.
        #
        $self->_logProcessedSaleWithLicense($sale, $trackLicense);
    }
}

sub _logProcessedSaleWithLicense
{
    my ($self, $sale, $trackLicense) = @_;

    # Just a stub - We'll override this in the 'Complex' class.
    #
}

sub _logSaleNoLicense
{
    my ($self, $sale) = @_;

    # Just a stub - We'll override this in the 'Complex' class.
    #
}


sub _testAlreadyPaidSaleWithLicense
{   
    my ($self, $sale, $trackLicense) = @_;

    assert(0, 'override this');
}

sub _processAlreadyPaidSaleWithLicense
{   
    my ($self, $sale, $trackLicense) = @_;
    assert(0, 'override this');
}
sub _processSaleWithLicense
{
    my ($self, $sale, $licenses) = @_;
    assert(0, 'override this');
}


sub _markSaleError
{
    my ($self, $sale, $status) = @_;

    # Create an entry in the sale_run_map
    #
    my $mapEntry = RPS::DB::Item::SaleRunMap->Create(
        sale_id => $sale->sale_id(),
        run_id => $self->_runID(),
        run_type => $self->_runType(),
        status => $status,
    );

    $mapEntry->save();
}


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

    die "ERROR - You must override _runType()";
}


sub _markSalePaid
{
    my ($self, $sale, $statementItem) = @_;

    assert($statementItem);

    $self->_markSalePaidWithIDs($sale->sale_id(), $self->_idFromStatementItem($statementItem));
}

sub _markSalePaidWithIDs
{
    my ($self, $saleID, $statementItemID) = @_;

    assert($saleID);
    assert($statementItemID);

    # Create an entry in the sale_run_map
    #
    my $mapEntry = RPS::DB::Item::SaleRunMap->Create(
        sale_id => $saleID,
        run_id => $self->_runID(),
        run_type => $self->_runType(),
        statement_item_id => $statementItemID,
        status => RPS::DB::Item::SaleRunMap::kStatusPaid,
    );

    $mapEntry->save();
}


# Override this to return the id associated with the statement item.
# Different types have different column names.
# !!! A better design might have been to have a 'StatementItem' class with
#     well-known public accessor methods.
#
sub _idFromStatementItem
{
    my ($self, $statementItem) = @_;

    die "ERROR - You must override _statementItemToID()";
}



sub _findMatchingLicenses
{
    my ($self, $sale) = @_;

    die "ERROR - You must overload _findMatchingLicenses";
}



# Override this
#
sub _getAllPossibleLicenses
{
    my ($self) = @_;

    die "ERROR - you must override _getAllPossibleLicenses\n";
}


sub _logSaleException
{
    my ($self, $exception) = @_;

    # !!! By default, we will do nothing.
    # !!! This is only relevant for UK runs at this point.

    # (... so the UK run code will override this method).
}


sub _licenseTypeMatches
{
    my ($self, $license) = @_;

    return 1;
}

sub _productTypeMatchesLicenseType
{
    my ($self, $productTypeID, $trackLicenseProductTypeID) = @_;

    $self->_report("_productTypeMatchesLicenseType:  Comparing productTypeID of $productTypeID to license productTypeID of $trackLicenseProductTypeID", 4);

    # NULL == all products.  Matches any product.
    #
    return 1 if (!$trackLicenseProductTypeID);
    return 1 if ($productTypeID == $trackLicenseProductTypeID);

    if (3 == $productTypeID || 4 == $productTypeID)
    {
        return 1 if ($trackLicenseProductTypeID == RPS::DB::Item::ProductType::kAllDigitalProducts);
        return 0;
    }
    else
    {
        return 1 if ($trackLicenseProductTypeID == RPS::DB::Item::ProductType::kAllPhysicalProducts);
        return 0;
    }
}


1;
