#---------------------------------------------------------------
# ____                   _ _         ____  _
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/
#
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
#---------------------------------------------------------------
package BookPub::Catalog::Import::Process::Importer::Base;
use strict;
use warnings;
use Data::Dumper;
use File::Path;

use lib '/app/tools/common/lib';
use lib '/app/tools/bookpub/lib';
use Common::RSApp;
use Common::RSDB;
use Common::Util;
use Common::Assert;
use Common::Process;
use Common::Locale;
use BookPub::DB::Item::ContributorRole;
use BookPub::DB::Item::ProductType;
use BookPub::DB::Item::CatalogImportItem::Book;
use BookPub::DB::Item::CatalogImportItem::BookContributor;
use BookPub::DB::Item::CatalogImportItem::BookProductContributor;
use BookPub::DB::Item::CatalogImportItem::BookProduct;
use BookPub::DB::Item::CatalogImportItem::BookSubject;
use BookPub::DB::Item::CatalogImportItem::Contributor;
use BookPub::DB::Item::CatalogImportItem::Discount;
use BookPub::DB::Item::CatalogImportItem::Imprint;
use BookPub::DB::Item::CatalogImportItem::Publisher;
use BookPub::DB::Item::CatalogImportItem::Market;
use BookPub::DB::Item::CatalogImportItem::Product;
use BookPub::DB::Item::CatalogImportItem::ProductMarket;
use BookPub::DB::Item::CatalogImportItem::ProductMarketPrice;
use BookPub::DB::Item::CatalogImportItem::Region;
use BookPub::DB::Item::CatalogImportItem::RegionIncludedCountry;
use BookPub::DB::Item::CatalogImportItem::RegionExcludedCountry;
use BookPub::DB::Item::CatalogImportItem::RegionIncludedTerritory;
use BookPub::DB::Item::CatalogImportItem::RegionExcludedTerritory;
use BookPub::DB::Item::CatalogImportItem::Supplier;
use BookPub::DB::Item::CatalogImport;
use BookPub::DB::Item::Book;
use BookPub::DB::Item::BookContributor;
use BookPub::DB::Item::BookProductContributor;
use BookPub::DB::Item::BookProduct;
use BookPub::DB::Item::BookSubject;
use BookPub::DB::Item::Contributor;
use BookPub::DB::Item::Discount;
use BookPub::DB::Item::Imprint;
use BookPub::DB::Item::Publisher;
use BookPub::DB::Item::Market;
use BookPub::DB::Item::Product;
use BookPub::DB::Item::ProductMarket;
use BookPub::DB::Item::ProductMarketPrice;
use BookPub::DB::Item::Region;
use BookPub::DB::Item::RegionIncludedCountry;
use BookPub::DB::Item::RegionExcludedCountry;
use BookPub::DB::Item::RegionIncludedTerritory;
use BookPub::DB::Item::RegionExcludedTerritory;
use BookPub::DB::Item::Supplier;
use BookPub::Config;

use base 'BookPub::Catalog::Import::Process';

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

    $self->{_importRecord}->status(BookPub::DB::Item::CatalogImport::kImportStatusImportRun);
    $self->{_importRecord}->save();

    # Set the catalogImportID in the Application singleton, so that we'll have
    # that ID available in the table_change_log.
    #
    Common::RSApp::GetRAMCache()->{catalogImportID} = $self->_catalogImportID();

    # This really better not fail.
    # But, it might.
    # So we're going to take a quick snapshot so we can at least attempt to roll back if
    # something goes wrong.
    # !!! It would be swell if we could have the database do this for us (transactions).
    #
    my $clientID      = Common::RSApp::GetClientID();
    my $dbCredentials = ${Common::RSDB::CLIENT_DB}{$clientID};

    die "ERROR - unable to find database credentials for clientID $clientID!" unless $dbCredentials;

    my $backupPath = $self->_getBaseStagingPath . "/DB_BACKUP";

    my $backupCommand =
        '/usr/bin/mysqldump' . ' -u'
      . $dbCredentials->{username} . ' -p'
      . $dbCredentials->{password} . ' -h'
      . $dbCredentials->{server} . ' '
      . $dbCredentials->{db_name}
      . ' book book_contributor book_product_contributor book_product book_subject contributor discount imprint publisher market product'
      . ' product_market product_market_price region region_included_country region_excluded_country region_included_territory region_excluded_territory supplier'
      . " > $backupPath";

    my $exitVal = system($backupCommand);
    if ( 0 != $exitVal ) {
        die "ERROR - backup command failed: error=$!, command=$backupCommand";
    }

    # Now execute everything in an eval block.
    # If we catch an exception, we'll (attempt to) roll back.
    # Otherwise, nuke the backup and exit cleanly.
    #
    eval {
        $self->_importRegions();
        $self->_importSuppliers();
        $self->_importMarkets();
        $self->_importProducts();
    };

    if ($@) {
        $self->_report("CAUGHT EXCEPTION!: $@");
        $self->_report("... attempting to rollback changes");

        my $rollbackCommand =
            '/usr/bin/mysql' . ' -u'
          . $dbCredentials->{username} . ' -p'
          . $dbCredentials->{password} . ' -h'
          . $dbCredentials->{server} . ' '
          . $dbCredentials->{db_name}
          . " < $backupPath";

        $exitVal = system($rollbackCommand);
        if ( 0 != $exitVal ) {
            die "ERROR - UNABLE TO ROLL BACK!";
        }

        # Give exitVal a non-0 value so we'll know something went wonky...
        #
        $exitVal = 255;
    } else {

        # Delete the db backup?   Do I really need to bother?
    }

    return $exitVal;
}

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

    my $allMarkets = BookPub::DB::Item::CatalogImportItem::Market->GetAllByCatalogImport( $self->_catalogImportID() );
    while ( my $market = $allMarkets->next() ) {
        $self->_importMarket($market);
    }
}

sub _importMarket {
    my ( $self, $market ) = @_;

    _assertValidStatus($market);

    return if $market->imported();

    $market->imported(1);
    $market->save();

    $self->_report( "importing market:", 4 );
    $self->_report( $market,             4 );

    if ( BookPub::DB::Item::CatalogImport::kNew eq $market->import_status ) {
        $self->_report( "creating new Market", 3 );

        # Need to fetch the importing region and supplier.
        # These should have been importer earlier, and therefore
        # should have the 'original' ids set.
        #
        my $importSupplier =
          BookPub::DB::Item::CatalogImportItem::Supplier->Lookup( catalog_import_supplier_id => $market->catalog_import_supplier_id );
        die "ERROR - unable to load catalog import supplier with id " . $market->catalog_import_supplier_id() unless $importSupplier;

        my $importRegion =
          BookPub::DB::Item::CatalogImportItem::Region->Lookup( catalog_import_region_id => $market->catalog_import_region_id );
        die "ERROR - unable to load catalog import region with id " . $market->catalog_import_region_id() unless $importRegion;

        die "ERROR - imported supplier missing original id for market:" . Dumper($market) unless $importSupplier->original_supplier_id();
        die "ERROR - imported region missing original id for market:" . Dumper($market)   unless $importRegion->original_region_id();

        my $newMarket = BookPub::DB::Item::Market->Create(
            supplier_id => $importSupplier->original_supplier_id(),
            region_id   => $importRegion->original_region_id(),
        );

        $newMarket->save();
        $self->_report( $newMarket, 4 );

        $market->original_market_id( $newMarket->market_id );
        $market->save();
    }
}

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

    my $allSuppliers = BookPub::DB::Item::CatalogImportItem::Supplier->GetAllByCatalogImport( $self->_catalogImportID() );
    while ( my $supplier = $allSuppliers->next() ) {
        $self->_importSupplier($supplier);
    }
}

sub _importSupplier {
    my ( $self, $supplier ) = @_;

    _assertValidStatus($supplier);

    return if $supplier->imported();

    $supplier->imported(1);
    $supplier->save();

    $self->_report( "importing supplier:", 4 );
    $self->_report( $supplier,             4 );

    if ( BookPub::DB::Item::CatalogImport::kNew eq $supplier->import_status ) {
        $self->_report( "Creating new Supplier", 3 );

        my $newSupplier = BookPub::DB::Item::Supplier->Create(
            name => $supplier->name(),
            san  => $supplier->san(),
        );
        $newSupplier->save();

        # Update the catalog_import record with the 'original' id.
        $supplier->original_supplier_id( $newSupplier->supplier_id() );
        $supplier->save();

        $self->_report( $newSupplier, 4 );
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $supplier->import_status ) {
        $self->_report( "Updating existing supplier - id:" . $supplier->original_supplier_id, 3 );

        my $oldSupplier = BookPub::DB::Item::Supplier->Lookup( supplier_id => $supplier->original_supplier_id );
        die "ERROR - Unable to load Supplier with id " . $supplier->original_supplier_id unless $oldSupplier;

        $oldSupplier->name( $supplier->name() );
        $oldSupplier->san( $supplier->san() );

        $oldSupplier->save();
    }
}

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

    my $allRegions = BookPub::DB::Item::CatalogImportItem::Region->GetAllByCatalogImport( $self->_catalogImportID() );
    while ( my $region = $allRegions->next() ) {
        $self->_importRegion($region);
    }
}

sub _importRegion {
    my ( $self, $region ) = @_;

    _assertValidStatus($region);

    return if $region->imported();

    $region->imported(1);
    $region->save();

    if ( BookPub::DB::Item::CatalogImport::kNew eq $region->import_status ) {
        $self->_report( "Creating new Region", 3 );
        $self->_report( $region,               4 );

        my $detailsHash = $region->getCountryAndTerritoryHash(caller_obj => $self);

        my $newRegion = BookPub::DB::Item::Region->CreateRegion(
            includedCountries   => $detailsHash->{includedCountries},
            includedTerritories => $detailsHash->{includedTerritories},
            excludedCountries   => $detailsHash->{excludedCountries},
            excludedTerritories => $detailsHash->{excludedTerritories},
            name                => $region->name(),
            catalogImportID     => $self->_catalogImportID(),
            caller_obj          => $self,
        );

        $newRegion->save();

        $self->_report( " new region:", 4 );
        $self->_report( $newRegion,     4 );

        $region->original_region_id( $newRegion->region_id() );
        $region->save();
    }
}

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

    # Iterate through all staged products, and copy over/update affected metadata.
    #
    my $allProducts = BookPub::DB::Item::CatalogImportItem::Product->GetAllByCatalogImport( $self->_catalogImportID() );
    while ( my $product = $allProducts->next() ) {
        $self->_importProduct($product);
    }
}

# At this point, _all_ staged metadata should have an import_status of New, Update, or Unchanged.
# Anything else signifies that we should not be attempting to import it.
# Going to have multiple levels of sanity checking here to make sure we don't pollute the
# live metadata.
#
sub _assertValidStatus {
    my ($item) = @_;
    my $status = $item->import_status();

    assert(
        BookPub::DB::Item::CatalogImport::kNew eq $status
          || BookPub::DB::Item::CatalogImport::kUpdate eq $status
          || BookPub::DB::Item::CatalogImport::kDelete eq $status
          || BookPub::DB::Item::CatalogImport::kNewFuture eq $status
          || BookPub::DB::Item::CatalogImport::kReplace eq $status
          || BookPub::DB::Item::CatalogImport::kUnchanged eq $status,
        "ERROR: Bad status for item: " . Dumper($item)
    );
}

sub _importProduct {
    my ( $self, $product ) = @_;
    $self->_report( '_importProduct', 3 );
    $self->_report( $product,         4 );

    _assertValidStatus($product);

    # These '_import' methods are going to be called recursively.
    # We need a mechanism to ensure we don't loop forever.
    #
    return if $product->imported();

    # Set the 'imported' flag immediately.
    #
    $product->imported(1);
    $product->save();

    if ( BookPub::DB::Item::ProductType::kBook == $product->product_type_id() ) {

        # First, copy over or update the Product.
        #
        if ( BookPub::DB::Item::CatalogImport::kNew eq $product->import_status ) {
            $self->_report( '_importProduct : Creating new live Product', 3 );

            # Create a new Product entry.
            #
            my $newLiveProduct = BookPub::DB::Item::Product->Create(
                product_type_id => $product->product_type_id(),
                release_date    => $product->release_date(),
            );
            $newLiveProduct->save();

            # Update the staged data to include the new 'original' product_id.
            #
            $product->original_product_id( $newLiveProduct->product_id() );
            $product->save();
        } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $product->import_status ) {
            $self->_report( '_importProduct : updating existing live Product', 3 );

            # The 'original' product_id should have already been established by the Analyzer process.
            #
            die "ERROR - Invalid origina product id for: " . Dumper($product) unless $product->original_product_id();
            my $oldLiveProduct = BookPub::DB::Item::Product->Lookup( product_id => $product->original_product_id() );
            $oldLiveProduct->product_type_id( $product->product_type_id() );
            $oldLiveProduct->release_date( $product->release_date() );
            $oldLiveProduct->save();
        } elsif ( BookPub::DB::Item::CatalogImport::kUnchanged ne $product->import_status ) {
            die "ERROR - Have not implemented action for product import_status = " . $product->import_status;
        }

        # Now create/update the associated BookProduct.
        #
        my $bookProduct =
          BookPub::DB::Item::CatalogImportItem::BookProduct->Lookup( catalog_import_product_id => $product->catalog_import_product_id() );
        die "ERROR - unable to load book_product associated with product: " . Dumper($product) unless $bookProduct;
        $self->_importBookProduct($bookProduct);
    } else {
        die "ERROR - we're not ready to handle anything but book products yet.";
    }

    #  Now import the product_market (and product_market_prices).
    #
    $self->_importProductMarkets($product);
}

sub _importProductMarkets {
    my ( $self, $product ) = @_;
    $self->_report( '_importProductMarkets', 3 );
    $self->_report( $product,                4 );

    # Some of the existing product_markets may end up being superceeded by new product_markets.
    # So let's keep track of any product_markets that did not get 'addressed', and delete those.
    #
    my %visitedLiveIDs;

    my $productMarkets =
      BookPub::DB::Item::CatalogImportItem::ProductMarket->GetAllByProductIDCatalogImportID( $product->catalog_import_product_id,
        $self->_catalogImportID );
    while ( my $productMarket = $productMarkets->next() ) {
        $self->_importProductMarket( $productMarket, $product );
        $visitedLiveIDs{ $productMarket->original_product_market_id() } = 1;
    }

    my $liveProductID      = $product->original_product_id();
    my $liveProductMarkets = BookPub::DB::Item::ProductMarket->GetAllByProductID($liveProductID);
    while ( my $liveProductMarket = $liveProductMarkets->next() ) {
        if ( !$visitedLiveIDs{ $liveProductMarket->product_market_id() } ) {

            # This product_market is no longer valid, so we delete it.
            #
            $liveProductMarket->delete();
        }
    }
}

sub _importProductMarket {
    my ( $self, $productMarket, $product ) = @_;
    $self->_report( '_importProductMarket', 3 );
    $self->_report( $productMarket,         4 );

    _assertValidStatus($productMarket);
    return if $productMarket->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $productMarket->imported(1);
    $productMarket->save();

    my $market =
      BookPub::DB::Item::CatalogImportItem::Market->Lookup( catalog_import_market_id => $productMarket->catalog_import_market_id() );
    die "ERROR - unable to load market for catalog_import_product_market: " . Dumper($productMarket) unless $market;

    my $liveProductID = $product->original_product_id();
    my $liveMarketID  = $market->original_market_id();
    die "ERROR - live product_id not set in product for catalog_import_product_market: " . Dumper($productMarket) unless $liveProductID;
    die "ERROR - live market_id not set in market for catalog_import_product_market: " . Dumper($productMarket)   unless $liveMarketID;

    if ( BookPub::DB::Item::CatalogImport::kNew eq $productMarket->import_status ) {
        $self->_report( "creating new product_market", 3 );
        my $newProductMarket = BookPub::DB::Item::ProductMarket->Create(
            product_id          => $liveProductID,
            market_id           => $liveMarketID,
            availability_status => $productMarket->availability_status,
            on_sale_date        => $productMarket->on_sale_date,
        );
        $newProductMarket->save();
        $self->_report( $newProductMarket, 4 );

        $productMarket->original_product_market_id( $newProductMarket->product_market_id() );
        $productMarket->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $productMarket->import_status ) {

        # !!! We don't account for a change in _market_id_, which can actually happen.
        #

        $self->_report( "updating live product_market", 3 );
        my $liveProductMarket =
          BookPub::DB::Item::ProductMarket->Lookup( product_market_id => $productMarket->original_product_market_id() );
        die "ERROR - unable to find live product_market to update for: " . Dumper($productMarket) unless $liveProductMarket;

        $liveProductMarket->availability_status( $productMarket->availability_status() );
        $liveProductMarket->expected_availability_date( $productMarket->expected_availability_date() );

        $liveProductMarket->save();
        $self->_report( $liveProductMarket, 4 );
    }

    # Now, while we're at it, let's import the _prices_ for this product_market
    $self->_importProductMarketPrices($productMarket);
}

sub _importProductMarketPrices {
    my ( $self, $productMarket ) = @_;

    # We're going to want to drop all the 'future' prices we have already, before
    # we import any new ones.
    #
    BookPub::DB::Item::ProductMarketPrice->DeleteFuturePrices(
        productMarketID => $productMarket->original_product_market_id,
        date            => $self->_catalogImportDate()
    );

    my $productMarketPrices = BookPub::DB::Item::CatalogImportItem::ProductMarketPrice->GetAllByProductMarketID(
        $productMarket->catalog_import_product_market_id() );
    while ( my $productMarketPrice = $productMarketPrices->next() ) {
        $self->_importProductMarketPrice( $productMarketPrice, $productMarket );
    }
}

sub _importProductMarketPrice {
    my ( $self, $productMarketPrice, $productMarket ) = @_;

    _assertValidStatus($productMarketPrice);
    return if $productMarketPrice->imported();

    $self->_report( "_importProductMarketPrice", 4 );
    $self->_report( $productMarketPrice,         4 );

    $productMarketPrice->imported(1);
    $productMarketPrice->save();

    my $liveProductMarketID = $productMarket->original_product_market_id;
    die "ERROR - invalid live product market id for: " . Dumper($productMarketPrice) unless $liveProductMarketID;

    # We'll need to fetch the import region to get the live region id.
    #
    my $liveRegionID;
    if ( $productMarketPrice->catalog_import_region_id() ) {
        my $region =
          BookPub::DB::Item::CatalogImportItem::Region->Lookup(
            catalog_import_region_id => $productMarketPrice->catalog_import_region_id() );
        $liveRegionID = $region->original_region_id;
    }

    # Let's also deal with the discount, if any.
    #
    my $liveDiscountID;
    if ( $productMarketPrice->catalog_import_discount_id ) {
        my $stagedDiscount =
          BookPub::DB::Item::CatalogImportItem::Discount->Lookup(
            catalog_import_discount_id => $productMarketPrice->catalog_import_discount_id() );
        if ( !$stagedDiscount->imported() ) {
            $stagedDiscount->imported(1);

            if ( BookPub::DB::Item::CatalogImport::kNew eq $stagedDiscount->import_status() ) {
                my $newLiveDiscount = BookPub::DB::Item::Discount->Create(
                    type        => $stagedDiscount->type(),
                    value       => $stagedDiscount->value(),
                    client_code => $stagedDiscount->client_code(),
                );
                $newLiveDiscount->save();

                $stagedDiscount->original_discount_id( $newLiveDiscount->discount_id() );
            }

            $stagedDiscount->save();
        }
        $liveDiscountID = $stagedDiscount->original_discount_id();
    }

    if ( BookPub::DB::Item::CatalogImport::kNewFuture eq $productMarketPrice->import_status() ) {
        $self->_report( "  kNewFuture", 4 );

        # Can I rely in any way on these dates being arranged properly?
        # Do I care?
        #
        my $newPrice = BookPub::DB::Item::ProductMarketPrice->Create(
            product_market_id       => $liveProductMarketID,
            price                   => $productMarketPrice->price,
            currency_code           => $productMarketPrice->currency_code,
            discount_id             => $liveDiscountID,
            region_id               => $liveRegionID,
            start_date              => $productMarketPrice->start_date,
            end_date                => $productMarketPrice->end_date,
            price_type_id           => $productMarketPrice->price_type_id,
            price_type_qualifier_id => $productMarketPrice->price_type_qualifier_id,
        );
        $newPrice->save();
        $self->_report( $newPrice, 4 );
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $productMarketPrice->import_status() ) {
        $self->_report( "  kUpdate", 4 );
        my $livePrice =
          BookPub::DB::Item::ProductMarketPrice->Lookup( product_market_price_id => $productMarketPrice->original_product_market_price_id,
          );
        die "ERROR - unable to load live price for: " . Dumper($productMarketPrice) unless $livePrice;

        # end date is the ONLY thing we allow to be updated for a 'current' price.
        #
        $livePrice->end_date( $productMarketPrice->end_date() );
        $livePrice->save();
        $self->_report( $livePrice, 4 );
    } elsif ( BookPub::DB::Item::CatalogImport::kReplace eq $productMarketPrice->import_status() ) {
        $self->_report( "   kReplace", 4 );

     # !!! WHY do we use the accessor?   Why not use the id from the table?   We went through the trouble of looking that shit up earlier...
     #

        # This state means that the current price becomes a historical price, and we create a new current price.
        #
        #        my $currentPrice = BookPub::DB::Item::ProductMarketPrice->GetCurrentPrice(
        my $currentPriceID = $productMarketPrice->original_product_market_price_id();
        assert($currentPriceID);
        my $currentPrice = BookPub::DB::Item::ProductMarketPrice->Lookup( product_market_price_id => $currentPriceID );

        #        my $currentPrice = BookPub::DB::Item::ProductMarketPrice->GetLastPrice(
        #            product_market_id => $liveProductMarketID,
        #            currency_code => $productMarketPrice->currency_code(),
        #            date => $self->_catalogImportDate(),
        #        );

        # The current price really should be there...
        #
        if ( !$currentPrice ) {
            $self->_report( "ERROR - unable to find current price to _replace_ with: " . Dumper($productMarketPrice) );
            $self->_report("LOOKUP ARGS: product_market_id: $liveProductMarketID");
            $self->_report( "currency_code: " . $productMarketPrice->currency_code() );
            $self->_report( "date:" . $self->_catalogImportDate() );

            my $originalPrice =
              BookPub::DB::Item::ProductMarketPrice->Lookup(
                product_market_price_id => $productMarketPrice->original_product_market_price_id() );
            if ($originalPrice) {
                $self->_report("  the original product market price now looks like this:");
                $self->_report($originalPrice);
            } else {
                $self->_report("  UNABLE TO FIND the original product market price...");
            }

            die "ERROR - unable to find current price to replace!";
        }

        $currentPrice->end_date( $self->_catalogImportDate() );
        $currentPrice->save();

        # Make sure the new current price sets the corrent start date
        #
        my $newPrice = BookPub::DB::Item::ProductMarketPrice->Create(
            product_market_id       => $liveProductMarketID,
            price                   => $productMarketPrice->price,
            currency_code           => $productMarketPrice->currency_code,
            discount_id             => $liveDiscountID,
            region_id               => $liveRegionID,
            start_date              => $self->_catalogImportDate(),
            end_date                => $productMarketPrice->end_date,
            price_type_id           => $productMarketPrice->price_type_id,
            price_type_qualifier_id => $productMarketPrice->price_type_qualifier_id,
        );

        $newPrice->save();
        $self->_report( "current:",    4 );
        $self->_report( $currentPrice, 4 );
        $self->_report( "new:",        4 );
        $self->_report( $newPrice,     4 );
    } elsif ( BookPub::DB::Item::CatalogImport::kNew eq $productMarketPrice->import_status() ) {
        $self->_report( "   kNew", 4 );
        my $newPrice = BookPub::DB::Item::ProductMarketPrice->Create(
            product_market_id       => $liveProductMarketID,
            price                   => $productMarketPrice->price,
            currency_code           => $productMarketPrice->currency_code,
            discount_id             => $liveDiscountID,
            region_id               => $liveRegionID,
            start_date              => $productMarketPrice->start_date,
            end_date                => $productMarketPrice->end_date,
            price_type_id           => $productMarketPrice->price_type_id,
            price_type_qualifier_id => $productMarketPrice->price_type_qualifier_id,
        );

        $newPrice->save();
        $self->_report( $newPrice, 4 );
    }
}

sub _importBookProduct {
    my ( $self, $bookProduct ) = @_;
    $self->_report( '_importBookProduct', 3 );
    $self->_report( $bookProduct,         4 );

    _assertValidStatus($bookProduct);

    return if $bookProduct->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $bookProduct->imported(1);
    $bookProduct->save();

    # First we import the associated book. That way, if the book is new, we'll have the ID.
    #
    my $book = BookPub::DB::Item::CatalogImportItem::Book->Lookup( catalog_import_book_id => $bookProduct->catalog_import_book_id() );
    die "ERROR - unable to load book associated with book_product: " . Dumper($bookProduct) unless $book;
    $self->_importBook($book);

    $book = BookPub::DB::Item::CatalogImportItem::Book->Lookup( catalog_import_book_id => $bookProduct->catalog_import_book_id() );
    assert( $book->original_book_id );

    # Import the associated Imprint, so we can get at the correct original id.
    #
    my $imprint =
      BookPub::DB::Item::CatalogImportItem::Imprint->Lookup( catalog_import_imprint_id => $bookProduct->catalog_import_imprint_id() );
    die "ERROR - unable to load imprint associated with bookProduct: " . Dumper($bookProduct) unless $imprint;
    $self->_importImprint($imprint);

    $imprint =
      BookPub::DB::Item::CatalogImportItem::Imprint->Lookup( catalog_import_imprint_id => $bookProduct->catalog_import_imprint_id() );
    assert( $imprint->original_imprint_id );

    # Import the associated Publisher
    #
    my $publisher =
      BookPub::DB::Item::CatalogImportItem::Publisher->Lookup( catalog_import_publisher_id => $bookProduct->catalog_import_publisher_id() );
    die "ERROR - unable to load publisher associated with bookProduct: " . Dumper($bookProduct) unless $publisher;
    $self->_importPublisher($publisher);

    $publisher =
      BookPub::DB::Item::CatalogImportItem::Publisher->Lookup( catalog_import_publisher_id => $bookProduct->catalog_import_publisher_id() );
    assert( $publisher->original_publisher_id );

    my $product =
      BookPub::DB::Item::CatalogImportItem::Product->Lookup( catalog_import_product_id => $bookProduct->catalog_import_product_id() );
    die "ERROR - unable to load product associated with book_product: " . Dumper($bookProduct) unless $product;
    $self->_importProduct($product);
    $product =
      BookPub::DB::Item::CatalogImportItem::Product->Lookup( catalog_import_product_id => $bookProduct->catalog_import_product_id() );
    assert( $product->original_product_id );

    # Now we can import the book_product.
    # (We may need to re-load the Book, so we can get the associated original_book_id).
    #
    if ( BookPub::DB::Item::CatalogImport::kNew eq $bookProduct->import_status ) {
        $self->_report( '_importProduct : creating new BookProduct', 3 );

        # Create a new book product.
        #
        my $newBookProduct = BookPub::DB::Item::BookProduct->Create(
            product_id                  => $product->original_product_id,
            book_id                     => $book->original_book_id,
            book_format_id              => $bookProduct->book_format_id,
            onix_code_publishing_status => $bookProduct->onix_code_publishing_status,
            publication_country         => $bookProduct->publication_country,
            isbn10                      => $bookProduct->isbn10,
            isbn13                      => $bookProduct->isbn13,
            imprint_id                  => $imprint->original_imprint_id,
            publisher_id                => $publisher->original_publisher_id,
            language_code               => $bookProduct->language_code,
            num_pages                   => $bookProduct->num_pages,
            edition                     => $bookProduct->edition,
        );
        $newBookProduct->save();

        # Update the staged book product with the original book id.
        #
        $bookProduct->original_book_id( $book->original_book_id() );
        $bookProduct->original_product_id( $product->original_product_id() );
        $bookProduct->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $bookProduct->import_status ) {
        $self->_report( '_importProduct : updating existing BookProduct', 3 );

        # Another sanity check...
        #
        die "ERROR - book has no id!" unless $book->original_book_id();

        #        die "ERROR - original book_id of " . $book->original_book_id() . " does not match loaded book_id: " . Dumper($bookProduct)
        #         unless $book->original_book_id() == $bookProduct->original_book_id();

        # Update an existing book product.
        #
        my $oldBookProduct = BookPub::DB::Item::BookProduct->Lookup( product_id => $bookProduct->original_product_id() );
        die "ERROR - unable to load original book_product to update: " . Dumper($bookProduct) unless $oldBookProduct;

        $oldBookProduct->book_id( $book->original_book_id() );
        $oldBookProduct->book_format_id( $bookProduct->book_format_id() );
        $oldBookProduct->onix_code_publishing_status( $bookProduct->onix_code_publishing_status() );
        $oldBookProduct->publication_country( $bookProduct->publication_country() );
        $oldBookProduct->isbn10( $bookProduct->isbn10() );
        $oldBookProduct->isbn13( $bookProduct->isbn13() );
        $oldBookProduct->language_code( $bookProduct->language_code() );
        $oldBookProduct->num_pages( $bookProduct->num_pages() );
        $oldBookProduct->edition( $bookProduct->edition() );
        $oldBookProduct->imprint_id( $imprint->original_imprint_id() );
        $oldBookProduct->publisher_id( $publisher->original_publisher_id() );

        $oldBookProduct->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUnchanged ne $bookProduct->import_status ) {
        die "ERROR - Have not implemented action for book product import_status = " . $bookProduct->import_status;
    }

    # Now deal with the book product contributors.
    # But first, we delete any current book product contributors for this book product.
    # We're just going to rebuild them product-by-product.
    #
    BookPub::DB::Item::BookProductContributor->DeleteAllByProductID( $bookProduct->original_product_id() );
    my $bookProductContributors = BookPub::DB::Item::CatalogImportItem::BookProductContributor->GetAllByProductIDCatalogImportID(
        $bookProduct->catalog_import_product_id,
        $self->_catalogImportID() );
    while ( my $bookProductContributor = $bookProductContributors->next() ) {
        $self->_importBookProductContributor($bookProductContributor);
    }

    # Now, finally, let's deal with any image urls.
    #
    if ( $bookProduct->image_link() ) {

        # Create the 'images' directory in the staging area, if necessary.
        #
        my $imageDir = $self->_imageSourceDirectory();
        if ( !-d $imageDir ) {
            File::Path::mkpath($imageDir) || die "ERROR - unable to create directory $imageDir : $!";
        }

        my $imageURLFileName = $bookProduct->isbn13 ? $bookProduct->isbn13 : $bookProduct->isbn10;
        $imageURLFileName .= '.url';
        open IMAGEURLFILE, "> $imageDir/$imageURLFileName" or die "ERROR - unable to open $imageDir/$imageURLFileName for writing: $!";
        print IMAGEURLFILE $bookProduct->image_link . "\n";
        close IMAGEURLFILE;
    }
}

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

    if ( !$self->{_baseStagingPath} ) {
        $self->_report( "  generating the path from config", 3 );

        # Get the base path from the config system.
        # Then tack on the client clean name, and the catalog_import_id.
        #
        my $config = $self->_config();
        my $client = Common::Client::Current();

        my $basePath  = $config->get('catalog_import_staging_dir');
        my $cleanName = $client->ClientNameClean();
        my $id        = $self->_catalogImportID();

        $self->{_baseStagingPath} = "$basePath/$cleanName/$id";
    }

    $self->_report( "  returning this path:'" . $self->{_baseStagingPath} . "'", 4 );
    return $self->{_baseStagingPath};
}

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

    return $self->_getBaseStagingPath() . "/images/";
}

sub _importBook {
    my ( $self, $book ) = @_;
    $self->_report( '_importBook', 3 );
    $self->_report( $book,         4 );

    _assertValidStatus($book);

    return if $book->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $book->imported(1);
    $book->save();

    # !!! What dangles off of book?
    # book_subject
    # book_contributor
    #
    # ... but we'll need a valid book_id before we can touch those.
    #
    if ( BookPub::DB::Item::CatalogImport::kNew eq $book->import_status ) {
        $self->_report( '_importBook: creating new Book', 3 );

        # Create the new book.
        #
        my $newBook = BookPub::DB::Item::Book->Create(
            title            => $book->title(),
            subtitle         => $book->subtitle(),
            title_clean      => $book->title_clean(),
            subtitle_clean   => $book->subtitle_clean(),
            audience_code_id => $book->audience_code_id(),
        );
        $newBook->save();

        # Fill in the missing 'original' ids in the staged Book data.
        #
        $book->original_book_id( $newBook->book_id() );
        $book->save();

    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $book->import_status ) {
        $self->_report( '_importBook: updating existing Book', 3 );

        # Update an existing book
        #
        my $oldBook = BookPub::DB::Item::Book->Lookup( book_id => $book->original_book_id() );
        die "ERROR - unable to load original book to update: " . Dumper($book) unless $oldBook;

        $oldBook->title( $book->title() );
        $oldBook->subtitle( $book->subtitle() );
        $oldBook->title_clean( $book->title_clean() );
        $oldBook->subtitle_clean( $book->subtitle_clean() );
        $oldBook->audience_code_id( $book->audience_code_id() );

        $oldBook->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUnchanged ne $book->import_status ) {
        die "ERROR - Have not implemented action for book import_status = " . $book->import_status;
    }

    # !!! Now update the contributors and subjects
    #
    # !!! Change from old method.  Now, we're going to simply blow away all the current book contributors for this book (if any), and
    # !!! rebuild this table.  We'll do the same for book subjects.
    #
    BookPub::DB::Item::BookContributor->DeleteAllByBookID( $book->original_book_id() );
    my $bookContributors =
      BookPub::DB::Item::CatalogImportItem::BookContributor->GetAllByBookIDCatalogImportID( $book->catalog_import_book_id,
        $self->_catalogImportID() );
    while ( my $bookContributor = $bookContributors->next() ) {
        $self->_importBookContributor($bookContributor);
    }

    BookPub::DB::Item::BookSubject->DeleteAllByBookID( $book->original_book_id() );
    my $bookSubjects = BookPub::DB::Item::CatalogImportItem::BookSubject->GetAllByBookIDCatalogImportID( $book->catalog_import_book_id,
        $self->_catalogImportID() );
    while ( my $bookSubject = $bookSubjects->next() ) {
        $self->_importBookSubject($bookSubject);
    }
}

sub _importImprint {
    my ( $self, $imprint ) = @_;
    $self->_report( '_importImprint', 3 );
    $self->_report( $imprint,         4 );

    _assertValidStatus($imprint);

    return if $imprint->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $imprint->imported(1);
    $imprint->save();

    if ( BookPub::DB::Item::CatalogImport::kNew eq $imprint->import_status ) {
        $self->_report( '_importImprint: creating new Imprint', 3 );
        my $newImprint = BookPub::DB::Item::Imprint->Create( name => $imprint->name(), );

        $newImprint->save();

        $imprint->original_imprint_id( $newImprint->imprint_id() );
        $imprint->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $imprint->import_status ) {

        # !!! Can we really ever get here?
        # !!! How would I _update_ an imprint?  How would I know?
        die "Error - not ready to handle Imprint updates...";
    } elsif ( BookPub::DB::Item::CatalogImport::kUnchanged ne $imprint->import_status ) {
        die "ERROR - Have not implemented action for imprint import_status = " . $imprint->import_status;
    }
}

sub _importPublisher {
    my ( $self, $publisher ) = @_;
    $self->_report( '_importPublisher', 3 );
    $self->_report( $publisher,         4 );

    _assertValidStatus($publisher);

    return if $publisher->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $publisher->imported(1);
    $publisher->save();

    if ( BookPub::DB::Item::CatalogImport::kNew eq $publisher->import_status ) {
        $self->_report( '_importPublisher: creating new Publisher', 3 );
        my $newPublisher = BookPub::DB::Item::Publisher->Create( name => $publisher->name(), );

        $newPublisher->save();

        $publisher->original_publisher_id( $newPublisher->publisher_id() );
        $publisher->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $publisher->import_status ) {
        die "Error - not ready to handle Publisher updates...";
    } elsif ( BookPub::DB::Item::CatalogImport::kUnchanged ne $publisher->import_status ) {
        die "ERROR - Have not implemented action for publisher import_status = " . $publisher->import_status;
    }
}

sub _importBookContributor {
    my ( $self, $bookContributor ) = @_;
    $self->_report( '_importBookContributor', 3 );
    $self->_report( $bookContributor,         4 );

    # TREAT ALL AS NEW
    #    _assertValidStatus($bookContributor);

    return if $bookContributor->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $bookContributor->imported(1);
    $bookContributor->save();

    # Well, I am going to need to have access to the Book _and_ the Contributor...
    #
    my $book = BookPub::DB::Item::CatalogImportItem::Book->Lookup( catalog_import_book_id => $bookContributor->catalog_import_book_id );
    die "ERROR - unable to find book for book_contributor: " . Dumper($bookContributor) unless $book;

    # We don't need the 'contributor' (indeed, there will not be one) when deleting...
    #
    my $contributor =
      BookPub::DB::Item::CatalogImportItem::Contributor->Lookup(
        catalog_import_contributor_id => $bookContributor->catalog_import_contributor_id );
    die "ERROR - unable to find contributor for book_contributor: " . Dumper($bookContributor) unless $contributor;
    $self->_importContributor($contributor);

    $self->_report( '_importBookContributor: creating new BookContributor', 3 );
    my $newBookContributor = BookPub::DB::Item::BookContributor->Create(
        book_id             => $book->original_book_id(),
        contributor_role_id => $bookContributor->contributor_role_id(),
        contributor_id      => $contributor->original_contributor_id(),
        sequence            => $bookContributor->sequence(),
    );

    $newBookContributor->save();

    $bookContributor->original_contributor_id( $contributor->original_contributor_id() );
    $bookContributor->original_book_contributor_id( $newBookContributor->book_contributor_id() );
    $bookContributor->save();
}

sub _importBookProductContributor {
    my ( $self, $bookProductContributor ) = @_;
    $self->_report( '_importBookProductContributor', 3 );
    $self->_report( $bookProductContributor,         4 );

    # TREAT ALL AS NEW
    #    _assertValidStatus($bookContributor);

    return if $bookProductContributor->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $bookProductContributor->imported(1);
    $bookProductContributor->save();

    # Well, I am going to need to have access to the Book _and_ the Contributor...
    #
    my $bookProduct =
      BookPub::DB::Item::CatalogImportItem::BookProduct->Lookup(
        catalog_import_product_id => $bookProductContributor->catalog_import_product_id );
    die "ERROR - unable to find book_product for book_product_contributor: " . Dumper($bookProductContributor) unless $bookProduct;

    my $contributor =
      BookPub::DB::Item::CatalogImportItem::Contributor->Lookup(
        catalog_import_contributor_id => $bookProductContributor->catalog_import_contributor_id );
    die "ERROR - unable to find contributor for book_product_contributor: " . Dumper($bookProductContributor) unless $contributor;
    $self->_importContributor($contributor);

    $self->_report( '_importBookProductContributor: creating new BookProductContributor', 3 );
    my $newBookProductContributor = BookPub::DB::Item::BookProductContributor->Create(
        product_id          => $bookProduct->original_product_id(),
        contributor_role_id => $bookProductContributor->contributor_role_id(),
        contributor_id      => $contributor->original_contributor_id(),
        sequence            => $bookProductContributor->sequence(),
    );

    $newBookProductContributor->save();

    $bookProductContributor->original_contributor_id( $contributor->original_contributor_id() );
    $bookProductContributor->original_book_product_contributor_id( $newBookProductContributor->book_product_contributor_id() );
    $bookProductContributor->save();
}

sub _importBookSubject {
    my ( $self, $bookSubject ) = @_;
    $self->_report( '_importBookSubject', 3 );
    $self->_report( $bookSubject,         4 );

    #    _assertValidStatus($bookSubject);

    return if $bookSubject->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $bookSubject->imported(1);
    $bookSubject->save();

    my $book = BookPub::DB::Item::CatalogImportItem::Book->Lookup( catalog_import_book_id => $bookSubject->catalog_import_book_id );
    die "ERROR - unable to find book for book_subject: " . Dumper($bookSubject) unless $book;

    $self->_report( '_importBookSubject: creating new BookSubject', 3 );

    my $newBookSubject = BookPub::DB::Item::BookSubject->Lookup(
        book_id            => $book->original_book_id(),
        bisac_subject_code => $bookSubject->bisac_subject_code()
    );
    if ( !$newBookSubject ) {
        $newBookSubject = BookPub::DB::Item::BookSubject->Create(
            book_id            => $book->original_book_id(),
            bisac_subject_code => $bookSubject->bisac_subject_code(),
        );

        $newBookSubject->save();
    }
}

sub _importContributor {
    my ( $self, $contributor ) = @_;
    $self->_report( '_importContributor', 3 );
    $self->_report( $contributor,         4 );

    #    _assertValidStatus($contributor);

    return if $contributor->imported();

    # Set the 'imported' flag immediately, in case we end up in here again via recursion...
    #
    $contributor->imported(1);
    $contributor->save();

    if ( BookPub::DB::Item::CatalogImport::kNew eq $contributor->import_status ) {
        $self->_report( '_importContributor: creating new Contributor', 3 );
        my $newContributor = BookPub::DB::Item::Contributor->Create(
            last_name     => $contributor->last_name,
            first_name    => $contributor->first_name,
            name_inverted => $contributor->name_inverted,
            clean_name    => $contributor->clean_name,
        );

        $newContributor->save();

        $contributor->original_contributor_id( $newContributor->contributor_id() );
        $contributor->save();
    } elsif ( BookPub::DB::Item::CatalogImport::kUpdate eq $contributor->import_status ) {

        # !!! This really seems silly.
        die "ERROR - not yet ready to handle updating contributor";
    } elsif ( BookPub::DB::Item::CatalogImport::kUnchanged ne $contributor->import_status ) {
        die "ERROR - Have not implemented action for contributor import_status = " . $contributor->import_status;
    }

}

###
1;    #
###
