package BookPub::POS::Import::Importer;

use strict;

# When adding an importer, edit the following files:
#   Tracker/Service.pm
#   Sale/File.pm
#   Import/Factory.pm
#   Import/Importer/SERVICE>/Version<N>.pm

use Date::Calc qw(Add_Delta_Days Days_in_Month);
use Time::Local;

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::PriceType;
use BookPub::Tracker::Service;

use lib '/app/tools/sale_import/lib/';
use Import::ValidationError;

use lib '/app/tools/data_classes/lib/';
use File::Sale;

use lib '/app/tools/common/lib';
use Common::RSApp;
use Common::Client;
use Common::Locale;
use Common::Consts;
use Common::Log;
use Common::Assert;
use Common::CurrencyFormat;
use Common::Util;
use Common::Date;

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::POSSale;
use BookPub::DB::Item::Sale;
use BookPub::Sale::Match;
use BookPub::POS::Import::SaleRec;

use Data::Dumper;

use base 'BookPub::Import::Importer';

# Valid accessor fields.  Lists all the valid accessors that can be used in the
# _getOffset method.
sub _validAccessors {
    qw(
      serviceID importStatus purchaseType serviceProductID rServiceName rDistributor
      isAgency isFree isPreOrder units listPrice discount unitPrice revenue conversionRate
      currencyCode countryCode dateBegin dateEnd rFormat rClientProductID
      rIsbn10 rIsbn13 rTitle rSubtitle rAuthor rChapterTitle rChapterOrdinal
      rImprint rNarrator rComments rChannel rUnits rSales rReturns rAdjustments
      rFee rListPrice rDiscount rUnitPrice rRevenue rPromoCode priceType isbn
      rProductType rListPriceCurrency rPurchaseType rPriceType
    );
}

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

    my $fileObj = $args{file};
    assert($fileObj);

    my $clientID = $args{client_id};
    assert($clientID);

    my $fileID = $fileObj->POSFileID;

    $self->{clientID}  = $clientID;
    $self->{posFileID} = $fileID;

    # JPK - Seems a little funny to dereference this here.
    # If we're getting passed in a File object, can't we just look at that?
    # Do we cache that anyplace?
    #
    $self->{fileServiceID} = $fileObj->ServiceID;

    # Make note of all the service IDs we've seen.
    #
    $self->{services} = {};
    $self->{services}{ $fileObj->ServiceID } = 1;

}

sub _saveLine {

    my $self = shift;

    my $startDate = $self->dateBegin();
    my $endDate   = $self->dateEnd();

    my $sale = BookPub::POS::Import::SaleRec->new();

    $sale->dateBegin($startDate);
    $sale->dateEnd($endDate);

    $sale->importStatus( $self->importStatus );
    $sale->productType( $self->productType );
    $sale->formatType( $self->formatType );
    $sale->priceTypeID( $self->priceTypeID );
    $sale->purchaseType( $self->purchaseType );
    $sale->serviceProductID( $self->serviceProductID );
    $sale->isAgency( $self->isAgency );
    $sale->isFree( $self->isFree );
    $sale->isPreOrder( $self->isPreOrder );
    $sale->units( $self->units );
    $sale->listPrice( $self->listPrice );
    $sale->discount( $self->discount );
    $sale->unitPrice( $self->unitPrice );
    $sale->revenue( $self->revenue );
    $sale->conversionRate( $self->conversionRate );
    $sale->currencyCode( $self->currencyCode );
    $sale->countryCode( $self->countryCode );
    $sale->rFormat( $self->rFormat );
    $sale->rPriceType( $self->rPriceType );
    $sale->rPurchaseType( $self->rPurchaseType );
    $sale->rProductType( $self->rProductType );
    $sale->rClientProductID( $self->rClientProductID );
    $sale->rIsbn10( $self->rIsbn10 );
    $sale->rIsbn13( $self->rIsbn13 );
    $sale->rServiceName( $self->rServiceName );
    $sale->rDistributor( $self->rDistributor );
    $sale->rTitle( $self->rTitle );
    $sale->rSubtitle( $self->rSubtitle );
    $sale->rAuthor( $self->rAuthor );
    $sale->rChapterTitle( $self->rChapterTitle );
    $sale->rChapterOrdinal( $self->rChapterOrdinal );
    $sale->rImprint( $self->rImprint );
    $sale->rNarrator( $self->rNarrator );
    $sale->rComments( $self->rComments );
    $sale->rChannel( $self->rChannel );
    $sale->rUnits( $self->rUnits );
    $sale->rSales( $self->rSales );
    $sale->rReturns( $self->rReturns );
    $sale->rAdjustments( $self->rAdjustments );
    $sale->rFee( $self->rFee );
    $sale->rListPrice( $self->rListPrice );
    $sale->rListPriceCurrency( $self->rListPriceCurrency );
    $sale->rDiscount( $self->rDiscount );
    $sale->rUnitPrice( $self->rUnitPrice );
    $sale->rRevenue( $self->rRevenue );
    $sale->rPromoCode( $self->rPromoCode );

    $sale->lineNum( $self->linenum );

    $self->_insertSale( sale => $sale );
}

sub _insertSale {
    my $self = shift;
    my %args = @_;

    my $saleRec = $args{sale};

    # Price type is a new feature.  Some importers don't support this field so we
    # have to derive it.
    my $priceTypeID = $saleRec->priceTypeID;

    # As part of FB 4933, we are adding kPriceTypeAgencyPlusTax and kPriceTypeRRPPlusTax to
    # our price type model.  I propose the following convention: if priceTypeID is not set
    # and isAgency is known, then we assume the price type is Agency or RRP based on that.
    # We only set a price type of "PlusTax" if we have a priceTypeID defined in a sales
    # file.

    # case 1) We don't know priceTypeID
    unless ($priceTypeID) {

        # Agency is type 41 or 42, otherwise it is retail price, type 1 or 2
        $priceTypeID =
          $saleRec->isAgency() eq 'Y'
          ? BookPub::DB::Item::PriceType::kPriceTypeAgency
          : BookPub::DB::Item::PriceType::kPriceTypeRRP;
    }

    # case 2) We don't know isAgency
    if ( $priceTypeID && !$saleRec->isAgency() ) {
        $saleRec->isAgency( (
                     $priceTypeID == BookPub::DB::Item::PriceType::kPriceTypeAgency
                  || $priceTypeID == BookPub::DB::Item::PriceType::kPriceTypeAgencyPlusTax
            ) ? 'Y' : 'N'
        );
    }

    # case 3) Price type mismatch
    if (   lc( $saleRec->isAgency ) eq 'y' && ( $priceTypeID != 41 && $priceTypeID != 42 )
        || lc( $saleRec->isAgency ) eq 'n' && ( $priceTypeID != 1 && $priceTypeID != 2 ) ) {

        $self->fail("Price type mismatch, type: $priceTypeID isAgency: $saleRec->isAgency\n");
    }

    $saleRec->priceTypeID($priceTypeID);

    # Keep track of the service id, if there is one.
    # We'll need to know this during post-processing.
    #
    $self->{services}{ $saleRec->serviceID } = 1 if ( $saleRec->serviceID );

    # We need to remap some older or incorrect country codes to the correct country codes en masse.
    # So we'll use a little hash and swap where appropriate, this will allow for the expantion
    # of the substitutions where necessary.
    my %countriesFixed = (
        FX => 'FR',
        JA => 'JM',
        UK => 'GB',
        YU => 'MK',
    );

    $saleRec->countryCode( $countriesFixed{ $saleRec->countryCode } ) if ( defined $countriesFixed{ $saleRec->countryCode } );

    # !!! move this
    # Make sure the currency_code is _VALID_.
    if ( !Common::CurrencyFormat::CodeIsValid( $saleRec->currencyCode ) ) {
        die Import::ValidationError->new( $saleRec, "currencyCode '" . $saleRec->currencyCode . "' is not recognized" );
    }

    # Check for our 'native' currency code here, and adjust
    # conversion_rate if necessary.
    #
    # This will essentially override any specific logic in the derived classes
    # that pertained to setting the conversion rate.
    #
    # But, don't bother to do this if the conversionRate has already been set.
    # !!! Actually, we will _always_ override conversionRate now.
    #
    #    if (! defined $saleRec->conversionRate())
    #    {
    my $nativeCurrency = Common::Client::Current()->Locale()->currencyFormat()->currencyCode();
    if ( $saleRec->currencyCode ne $nativeCurrency ) {
        $saleRec->conversionRate(0);
    } else {
        $saleRec->conversionRate(1);
    }

    #    }

    # Now we can validate the saleRec as best we can.
    # This will throw an exception if validation fails.
    #
    $self->_validateSaleRec($saleRec);

    # !!! JPK - It seems like a bit of a waste, frankly, to have
    # both the SaleRec class and the DB::Item::Sale.
    # I suppose we can end up with some additional conversion stuff in the SaleRec class?
    # Still, though, it seems a bit pointless.
    # !!! But, for now, keep this model.
    #
    my $saleDB = BookPub::DB::Item::POSSale->Create();

    # Default to the file's service id unless the importer specified one.
    my $serviceID = $saleRec->serviceID;
    $serviceID = $self->{fileServiceID} unless $serviceID;

    $saleDB->pos_file_id( $self->{posFileID} );
    $saleDB->service_id($serviceID);
    $saleDB->product_type( $saleRec->productType );
    $saleDB->format_type( $saleRec->formatType );
    $saleDB->purchase_type( $saleRec->purchaseType );
    $saleDB->service_product_id( $saleRec->serviceProductID );
    $saleDB->is_agency( $saleRec->isAgency );
    $saleDB->is_free( $saleRec->isFree );
    $saleDB->is_pre_order( $saleRec->isPreOrder );
    $saleDB->price_type_id($priceTypeID);
    $saleDB->units( $saleRec->units );
    $saleDB->list_price( $saleRec->listPrice );
    $saleDB->discount( $saleRec->discount );
    $saleDB->unit_price( $saleRec->unitPrice );
    $saleDB->revenue( $saleRec->revenue );
    $saleDB->conversion_rate( $saleRec->conversionRate );
    $saleDB->currency_code( $saleRec->currencyCode );
    $saleDB->country_code( $saleRec->countryCode );
    $saleDB->line_num( $saleRec->lineNum );
    $saleDB->date_begin( $saleRec->dateBegin );
    $saleDB->date_end( $saleRec->dateEnd );

    # the rest we'll store in "r_" fields
    $saleDB->r_format( $saleRec->rFormat );
    $saleDB->r_price_type( $saleRec->rPriceType );
    $saleDB->r_purchase_type( $saleRec->rPurchaseType );
    $saleDB->r_service_name( $saleRec->rServiceName );
    $saleDB->r_distributor( $saleRec->rDistributor );
    $saleDB->r_product_type( $saleRec->rProductType );
    $saleDB->r_client_product_id( $saleRec->rClientProductID );
    $saleDB->r_isbn10( $saleRec->rIsbn10 );
    $saleDB->r_isbn13( $saleRec->rIsbn13 );
    $saleDB->r_title( $saleRec->rTitle );
    $saleDB->r_subtitle( $saleRec->rSubtitle );
    $saleDB->r_author( $saleRec->rAuthor );
    $saleDB->r_chapter_title( $saleRec->rChapterTitle );
    $saleDB->r_chapter_ordinal( $saleRec->rChapterOrdinal );
    $saleDB->r_imprint( $saleRec->rImprint );
    $saleDB->r_narrator( $saleRec->rNarrator );
    $saleDB->r_comments( $saleRec->rComments );
    $saleDB->r_channel( $saleRec->rChannel );
    $saleDB->r_units( $saleRec->rUnits );
    $saleDB->r_sales( $saleRec->rSales );
    $saleDB->r_returns( $saleRec->rReturns );
    $saleDB->r_adjustments( $saleRec->rAdjustments );
    $saleDB->r_fee( $saleRec->rFee );
    $saleDB->r_list_price( $saleRec->rListPrice );
    $saleDB->r_list_price_currency( $saleRec->rListPriceCurrency );
    $saleDB->r_discount( $saleRec->rDiscount );
    $saleDB->r_unit_price( $saleRec->rUnitPrice );
    $saleDB->r_revenue( $saleRec->rRevenue );
    $saleDB->r_promo_code( $saleRec->rPromoCode );

    # now try matching to catalog
    my $match = BookPub::Sale::Match->new( client_id => $self->{clientID} );
    my $result = $match->FindBestMatch( data => $saleDB, skip_rec => 1 );
    my $matches = scalar @{ $result->ProductIDs };

    if ( 1 == $matches ) {
        $result->matchToSale($saleDB);
    } else {
        $saleDB->import_status(BookPub::DB::Item::Sale::STATUS_NOMATCH);
    }

    $saleDB->save();
}

###
1;    # Play nicely.
###
