package BookPub::RDAS::Response::ProductPrice;

use Data::Dumper;

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

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::ProductMonitorAlert;

use BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Metadata;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Price;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::CoverArt;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Availability;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Failure;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Rank;

use BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::Price;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::Available;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::Unavailable;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::CoverArt;
use BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::PhysicalPrice;

use base 'BookPub::RDAS::Response';

sub _validOptions {
    return ( shift->SUPER::_validOptions, 'parser', 'productID' );
}

sub serviceID { die "must be overloaded" }

sub run {
    my $self = shift;

    assert( $self->serviceID, "Service ID required" );
    assert( $self->productID, "Product ID required" );
    assert( $self->parser,    "Parser required" );

    Log->info("Validating parsed data");

    #$self->parser->debugFields();

    my $error = $self->parser->validate();
    $error = $self->validate() unless ($error);

    if ($error) {
        $self->fail( failure => $error );
    } else {
        $self->_storeData();
    }
}

sub fail {
    my $self    = shift;
    my %args    = @_;
    my $code    = $args{code};
    my $failure = $args{failure};

    my $msg = "Code: $code " if ( defined($code) );
    $msg = "Error: $failure " if ( defined($failure) );

    Log->info("Product Price Request Failure: $msg");

    assert( $code || $failure, "code or failure required" );

    assert( $self->serviceID, "Service ID required" );
    assert( $self->productID, "Product ID required" );

    my $error = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Failure(
        serviceID => $self->serviceID,
        productID => $self->productID
    );

    $self->_resolveItems( productID => $self->productID, serviceID => $self->serviceID );
    $self->_resolveOldAlerts( productID => $self->productID, serviceID => $self->serviceID );

    if ( $error->requiresUpdate( \%args ) ) {
        $error->create( \%args );
    } else {
        $error->updateTimestamp();
    }
}

sub _storeData {
    my $self = shift;
    Log->info("Store parsed data");

    # Clear any previous failures
    my $error = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Failure();
    $error->invalidate( serviceID => $self->serviceID, productID => $self->productID );

    # Only parse data if the product is avaialble
    if ( $self->_storeAvailability() ) {
        $self->_storeProductData();
    }

    $self->_processAlerts();
}

sub _storeProductData {
    my $self = shift;

    Log->info(" ---  Product is available, storing product information ---");

    $self->_storeMetadata();
    $self->_storePrice();
    $self->_storeCoverArt();
}

sub _getMetadata { die "Must be overloaded" }

sub _storeMetadata {
    my $self = shift;

    my $metadata = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Metadata(
        serviceID => $self->serviceID,
        productID => $self->productID
    );

    my $websiteData = $self->_getMetadata();

    if ( $metadata->requiresUpdate($websiteData) ) {
        $metadata->create($websiteData);
    } else {
        $metadata->updateTimestamp();
    }
}

sub _getPriceData { die "Must be overloaded" }

sub _storePrice {
    my $self = shift;

    my $price = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Price(
        serviceID => $self->serviceID,
        productID => $self->productID
    );

    my $websiteData = $self->_getPriceData();

    if ( $price->requiresUpdate($websiteData) ) {
        $price->create($websiteData);
    } else {
        $price->updateTimestamp();
    }
}

sub _getCoverArtData { die "Must be overloaded" }

sub _storeCoverArt {
    my $self = shift;

    my $cover = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::CoverArt(
        serviceID => $self->serviceID,
        productID => $self->productID
    );

    my $websiteData = $self->_getCoverArtData();

    if ( $cover->requiresUpdate($websiteData) ) {
        $cover->create($websiteData);
    } else {
        $cover->updateTimestamp();
    }
}

sub _getAvailabilityData {
    my $self = shift;

    my $available =
        $self->parser->available() > 0 ? 'available'
      : $self->parser->available() < 0 ? 'pre_order'
      :                                  'not_available';

    return { availability => $available };
}

sub _storeAvailability {
    my $self = shift;

    Log->info(" ---  Checking availability of the product  ---");
    my $availability = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorItem::Availability(
        serviceID => $self->serviceID,
        productID => $self->productID
    );

    my $websiteData = $self->_getAvailabilityData();

    if ( $availability->requiresUpdate($websiteData) ) {
        $availability->create($websiteData);
    } else {
        $availability->updateTimestamp();
    }

    return $availability->Availability && $availability->Availability ne 'not_available';
}

sub validate {
    my $self = shift;

    Log->info(" - Request Validation");

    my $product = new BookPub::Catalog::Product::Book( productID => $self->productID );
    my $book = new BookPub::Catalog::Book::WithAuthor( bookID => $product->BookID );

    return "Title validation failed" unless ( $self->_validateTitle( $book, $product ) );

    #return "Author validation failed" unless( $self->_validateAuthor( $book, $product ) );
    return "ISRC validation failed" unless ( $self->_validateISRC( $book, $product ) );
    return "Price validation failed" unless ( $self->_validatePrice( $book, $product ) );

    return;
}

sub _validateTitle {
    my $self    = shift;
    my $book    = shift;
    my $product = shift;

    my $hasSubtitle = $self->can('Subtitle');

    my $fetchedTitle = $self->parser->Title;
    my $fetchedSubtitle = $self->parser->Subtitle if ($hasSubtitle);

    my $catalogTitle = $book->Title;
    my $catalogSubtitle = $book->Subtitle if ($hasSubtitle);

    Log->info("   Validate fetched title matches catalog title");

    if ( !defined($fetchedTitle) ) {
        Log->info("   -- Undetermined title from request object");
        return;
    }

    if ( !defined($catalogTitle) ) {
        Log->info("   -- Undetermined title from catalog");
        return;
    }

    my $fetched = new BookPub::Catalog::Book::Title( title => $fetchedTitle, subtitle => $fetchedSubtitle );
    my $catalog = new BookPub::Catalog::Book::Title( title => $catalogTitle, subtitle => $catalogSubtitle );
    my $parsed  = new BookPub::Catalog::Book::Title( parse => $fetchedTitle );

    #die $parsed->Title . " eq " . $catalog->Title;
    unless ( $fetched->fuzzyMatch( title => $catalog->Title, subtitle => $catalog->Subtitle )
        || $parsed->fuzzyMatch( title => $catalog->Title, subtitle => $catalog->Subtitle )
        || $parsed->titleFuzzyMatch( title => $catalog->Title )
        || $fetched->contains( title => $catalog->Title, subtitle => $catalog->Subtitle ) ) {
        Log->info( "   -- Title match failed. Fetched: \"" . $fetched->Combined . "\" Catalog: \"" . $catalog->Combined . "\"" );
        return;
    }

    Log->info("   -- Title match success!");
    return 1;
}

sub _validateAuthor {
    my $self    = shift;
    my $book    = shift;
    my $product = shift;

    Log->info("   Validate fetched author matches catalog author");

    my $fetched = new BookPub::Catalog::Author( $self->parser->Author );
    my $catalog = new BookPub::Catalog::Author( $book->AuthorName );

    if ( !defined( $fetched->Author ) ) {
        Log->info("   -- Undetermined author from request object");
        return;
    }

    if ( !defined( $catalog->Author ) ) {
        Log->info("   -- Undetermined author from catalog");
        return;
    }

    unless ( $fetched->fuzzyMatch( $catalog->Author ) ) {
        Log->info( "   -- Author match failed. Fetched: \"" . $fetched->Author . "\" Catalog: \"" . $catalog->Author . "\"" );
        return;
    }

    Log->info( "  Fetched: " . $fetched->Author() );
    Log->info( "  Catalog: " . $catalog->Author() );

    Log->info("   -- Author match success!");
    return 1;
}

sub _validateISRC { 1 }

sub _validatePrice { 1 }

sub _processAlerts {
    my $self = shift;

    $self->_resolveOldAlerts();

    $self->_processAlertsPrice();
    $self->_processAlertsAvailability();
    $self->_processAlertsCoverArt();
    $self->_processAlertsPhysicalPrice();
}

sub _processAlertsPrice {
    my $self = shift;

    my $websiteData = $self->_getPriceData();
    my $alert       = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::Price;

    $alert->set( productID => $self->productID, serviceID => $self->serviceID, price => $websiteData->{price} );
}

sub _processAlertsAvailability {
    my $self = shift;

    my $websiteData = $self->_getAvailabilityData();

    # Alert if the product is not available after the publication date.
    my $alert = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::Unavailable;
    $alert->set( productID => $self->productID, serviceID => $self->serviceID, available => $websiteData->{availability} );

    # Alert if the product is available before the publication date.
    $alert = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::Available;
    $alert->set( productID => $self->productID, serviceID => $self->serviceID, available => $websiteData->{availability} );
}

sub _processAlertsCoverArt {
    my $self = shift;

    my $websiteData = $self->_getCoverArtData();
    my $alert       = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::CoverArt;

    $alert->set( productID => $self->productID, serviceID => $self->serviceID, url => $websiteData->{cover_art_url} );
}

sub _processAlertsPhysicalPrice {
    my $self = shift;

    my $websiteData = $self->_getPriceData();
    my $alert       = new BookPub::RDAS::Form::ProductMonitor::ProductMonitorAlert::PhysicalPrice;

    $alert->set( productID => $self->productID, serviceID => $self->serviceID, price => $websiteData->{price} );
}

sub _resolveOldAlerts {
    my $self = shift;

    Log->info("Resolve alerts for product with items marked 'old'");
    BookPub::DB::Item::ProductMonitorAlert->ResolveOldAlerts( product_id => $self->productID, service_id => $self->serviceID );
}

sub _resolveItems {
    my $self = shift;

    Log->info("Resolve monitored items for product.  Marking them 'old'");
    BookPub::DB::Item::ProductMonitorItem->ResolveItems( product_id => $self->productID, service_id => $self->serviceID );
}

1;
