#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#------------------------------------------------------------
package BookPub::Matcher::Command::Sale;
use strict;
use warnings;
use Data::Dumper;

use lib '/app/tools/common/lib';
use RSApache::Response::XSLT;
use Common::Assert;
use Common::Log;

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::Sale;
use BookPub::Matcher::Product;
use BookPub::Price::PriceTypeList;
use BookPub::DB::Item::ProductMarketPrice;
use BookPub::DB::Item::Region;
use BookPub::Price::Validator;

# --> use BookPub::Tracker::File;
# --> use BookPub::Sale::Match;

use base 'BookPub::Command';

# !!! OVERLOAD THESE
#sub cmd  { return "sale"; }
#sub area { return "matcher"; }
#sub pageName { return "Resolve Catalog Exceptions"; }
#use constant kTemplate => '/app/tools/bookpub/templates/matcher/main.xsl';

# JPK - Want to make this abstract, so we can re-use this logic for the POS sales as well.
# Going to be a very similar interface.
#
# So, what needs abstraction?
# - Instantiation of the correct Matcher::Sale class
#   -- two different constructions.  One takes a sale id, the other takes fileID and linenum
#
# - Instantiation of the correct Tracker::File class.
#
# - GetFirstException($file), returns the id of the first sale with an exception.
#
# - The redirection URL (c=sale or c=pos_sale, etc).
#
# - The matching loop should probably be moved into its own method.
#
# - !!! Not clear what level of abstraction exists already with Sale::Match. Bill is using _something_ today...
# - --> May need to abstract the Search command.  May need to abstract the entire class?

sub _getSaleFromSaleID {
    my ( $self, $saleID ) = @_;
    assert( 0, 'override' );
}

sub _getSaleFromFileIDLineNum {
    my ( $self, $fileID, $lineNum ) = @_;
    assert( 0, 'override' );
}

sub _getFirstExceptionSaleIDFromFileID {
    my ( $self, $fileID ) = @_;
    assert( 0, 'override' );
}

sub _getFileFromFileID {
    my ( $self, $fileID ) = @_;
    assert( 0, 'override' );
}

sub _previousPage {
    my ($self) = @_;
    assert( 0, 'override' );
}

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

    # Give any inherited methods the first crack at this.
    # (This checks for login and basic access permissions).
    #
    my $response = $self->SUPER::execute();
    return $response if $response;

    my $searchParam     = $self->getParam("search");
    my $searchTypeParam = $self->getParam("search_type");
    my $saleID          = $self->getParam('sale');
    my $trackerPage     = $self->getParam('trackerPage');
    my ( $fileID, $sale );

    # do we wanna advance to the next exception if a match is made? default to yes
    my $advanceFlag = 1;

    if ($saleID) {
        Common::Log::Debug("get withnav $saleID");
        $sale = $self->_getSaleFromSaleID($saleID);

        # JPK - Note the accessor name needs to be 'FileID' for all subclasses of Matcher::Sale.
        #
        $fileID = $sale->FileID;
    } else {
        $fileID = $self->getParam('file');
        my $lineNum = $self->getParam('line');

        if ( !$lineNum ) {
            Common::Log::Debug("get 1st exception in file $fileID");
            my $saleID = $self->_getFirstExceptionSaleIDFromFileID($fileID);

            #            my $saleID = BookPub::DB::Item::Sale->GetFirstException($fileID);
            Common::Log::Debug("$saleID is it");
            my $redirURL =
              $saleID
              ? "/bookpub/" . $self->area() . "?c=" . $self->cmd() . "&sale=$saleID&trackerPage=$trackerPage"
              : $self->_previousPage();

            return RSApache::Response::Redirect->new($redirURL);
        }

        # else

        # since we got here by requesting a line num, stay on the page even if a match was made
        $advanceFlag = 0;
        $sale = $self->_getSaleFromFileIDLineNum( $fileID, $lineNum );
        if ( !$sale->SaleID ) {

            # they likely entered a line num that doesn't exist so setup the file XML
            # then redisplay the page and let the template show a message

            Common::Log::Debug("get file $fileID (alt)");
            $self->{xml}{File} = $self->_getFileFromFileID($fileID);

            #            $self->{xml}{File} = BookPub::Tracker::File->new(fileID => $fileID);
            return RSApache::Response::XSLT->new( $self->{xml}, $self->kTemplate() );
        }
    }

    my $matchingProductIDs;
    my $oldSaleID = $sale->SaleID;
    my $file      = $self->_getFileFromFileID( $sale->FileID );

    #    my $file = BookPub::Tracker::File->new(fileID => $sale->FileID);

    if (
        $sale->ProductID
        && (   $sale->ImportStatus != BookPub::DB::Item::Sale::STATUS_NOMATCH
            || $sale->ImportStatus != BookPub::DB::Item::Sale::STATUS_MULTIMATCH )
      ) {
        $matchingProductIDs = [ $sale->ProductID() ];
    } else {

        # !!! Will this need to be abstracted?
        #
        my $matcher = BookPub::Sale::Match->new( client_id => Common::RSApp::GetClientID() );

        Common::Log::Debug("!!! Starting Match loop");
        while ($sale) {
            my $matchResult;
            if ($searchParam) {
                $matchResult = $matcher->SearchCatalog( keyword => $searchParam, type => $searchTypeParam, sale => $sale->dbItem() );
            } else {

                # our suggested matches
                #
                $matchResult = $matcher->FindBestMatch( data => $sale->dbItem(), skip_regexp_search => 1 );
            }

            # If there are no matches, that's it.
            #
            if (
                !$matchResult
                || (   ( !defined $matchResult->ProductIDs || 0 == scalar @{ $matchResult->ProductIDs } )
                    && ( !defined $matchResult->RecProductIDs || 0 == scalar @{ $matchResult->RecProductIDs } )
                    && ( !defined $matchResult->SearchProductIDs || 0 == scalar @{ $matchResult->SearchProductIDs } ) )
              ) {
                $self->addMessageXML( type => "info", code => "no_matches" );
                last;
            }

            $matchingProductIDs = $matchResult->ProductIDs();
            my $suggestedProductIDs = $matchResult->RecProductIDs();
            my $searchProductIDs    = $matchResult->SearchProductIDs();

            # If we've got 1 match, we'll make that match official, and move on to the next unmatched sale.
            #
            if ( 1 == scalar @$matchingProductIDs ) {
                Common::Log::Debug("found 1 match @$matchingProductIDs");

                # JPK - The old code was very twisty, and there were a lot of calls that
                # had hidden side-effects (changing database state, essentially).
                # I've tried to simplify things, and bring all the state changes out to this level.
                #

                # JPK - I don't really like the way we violate encapsulation here.  But, we want to use
                # the same 'matchToSale' logic both here and in the Importer class.  And the Importer class
                # is going to just have a DB::Item::Sale object, not a BookPub::Sale::Match object.
                #
                # Calling $sale->save() directly here would be a bug - That would result in the state we just
                # put in the object's DB::Item getting _overwritten_ (i.e. we'd lose the match result).

                $matchResult->matchToSale( $sale->dbItem() );
                $sale->dbItem->save();

                # Get the next sale id
                # !!! We'll see if these calls make sense...
                #
                $file->recalculateRemainingExceptions();
                $file->save();

                # And now validate the price on this newly matched sale.
                my $validator = new BookPub::Price::Validator( saleID => $sale->SaleID );
                $validator->run();

                if ($advanceFlag) {
                    my $tmp_sale_id = $sale->NextUnfixedID() || $sale->PreviousUnfixedID();
                    Common::Log::Debug("next saleid: $tmp_sale_id");

                    if ($tmp_sale_id) {
                        $sale = $self->_getSaleFromSaleID($tmp_sale_id);
                        Common::Log::Debug( "new sale: " . $sale->SaleID() . ", " . $sale->rTitle() );
                        next;
                    }
                }

                # else

                last;
            } elsif ( 0 == scalar @$matchingProductIDs && 0 < scalar @$suggestedProductIDs ) {
                $matchingProductIDs = $suggestedProductIDs;
            } elsif ( 0 == scalar @$matchingProductIDs && 0 == scalar @$suggestedProductIDs && 0 < scalar @$searchProductIDs ) {
                $matchingProductIDs = $searchProductIDs;
            }

            last;
        }
    }

    # If we have chosen a different sale id than the one passed in,
    # let's redirect so that the URL matches.
    #
    if ( $oldSaleID != $sale->SaleID ) {
        Common::Log::Debug( "redir to new saleID " . $sale->SaleID );
        my $redirURL = '/bookpub/' . $self->area() . '?c=' . $self->cmd() . '&sale=' . $sale->SaleID() . "&trackerPage=$trackerPage";
        return RSApache::Response::Redirect->new($redirURL);
    }

    if ($matchingProductIDs) {
        my $regionID;
        my $countryCode = $sale->CountryCode;

        my $region = BookPub::DB::Item::Region->FindMatchingRegion( country => $countryCode, caller_obj => $self );
        if ($region) {
            $regionID = $region->region_id;
        }

        foreach my $productID (@$matchingProductIDs) {
            my $product = BookPub::Matcher::Product->new( productID => $productID );

            # Let's split up the author list so that we can highlight exact matches;
            my @authorList = split( '; ', $product->Book->AuthorName );
            foreach my $authorName (@authorList) {
                push @{ $product->{Authors}{Author} }, $authorName;
            }

            # Let's add some pricing information for each product.
            #
            my $collection = BookPub::DB::Item::ProductMarketPrice->GetAllPrices(
                product_id    => $productID,
                start_date    => $sale->DateBegin,
                end_date      => $sale->DateEnd,
                currency_code => $sale->CurrencyCode,
                region_id     => $regionID,
            );

            while ( my $dbItem = $collection->next() ) {
                my $productPrice;
                $productPrice->{DisplayPrice} =
                  Common::FormObject::Scalar::Money->new( value => $dbItem->price, currencyCode => $sale->CurrencyCode, readOnly => 1 );
                $productPrice->{PriceTypeID} = $dbItem->price_type_id;
                push @{ $product->{Pricing}{Price} }, $productPrice;

                # Let's make the max and min prices easy to find, for sorting purposes.
                my $price = $dbItem->price;
                if ( !$product->{MaxPrice} || $price > $product->{MaxPrice} ) {
                    $product->{MaxPrice} = $price;
                }
                if ( !$product->{MinPrice} || $price < $product->{MinPrice} ) {
                    $product->{MinPrice} = $price;
                }
            }

            push @{ $self->{xml}{Products}{Product} }, $product;
        }
    }

    # Navigation
    # !!! This stuff moved into the Sale class itself.
    #
    #    $self->{xml}{PreviousError}     = $sale->GetPreviousErrorID();
    #    $self->{xml}{PreviousUnfixed}   = $sale->GetPreviousUnfixedID();
    #    $self->{xml}{NextError}         = $sale->GetNextErrorID();
    #    $self->{xml}{NextUnfixed}       = $sale->GetNextUnfixedID();

    $self->{xml}{Sale}       = $sale;
    $self->{xml}{File}       = $file;
    $self->{xml}{PriceTypes} = BookPub::Price::PriceTypeList->new();

    $response = RSApache::Response::XSLT->new( $self->{xml}, $self->kTemplate() );

    return $response;
}

1;
