#!/usr/bin/perl
use strict;

# -----------------------------------------
# input params: (default is all clients, all open file, all services,
#   match using any possible method)
#
# 	- c client_id
#   - f file_id
# 	- s service_id
#   - m (use mapped matching only)
#   - d (decide whether to use mapped matching only)
#   - r (no regexp search)
# 	- u (no update)
#
# -----------------------------------------
# what it does:
#
#   - for every file with open status
# 	  - get unmatched sale records from db
#     - if match exists, assign it, otherwise on to next record
#
# -----------------------------------------

use Sys::Hostname;
use POSIX;
use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::RSDB;
use Common::RSApp;
use Common::DB::Item::Client;
use Common::DB::Item::ClientType;

use lib '/app/tools/bookpub/lib';
use BookPub::Price::Validator;

use BookPub::Tracker::File;
use BookPub::Sale::Match;
use BookPub::DB::Item::ProductInputMap;
use BookPub::DB::Item::Book;
use BookPub::DB::Item::Chapter;
use BookPub::DB::Item::Contributor;
use BookPub::DB::Item::BookContributor;
use BookPub::DB::Item::BookSubject;
use BookPub::DB::Item::Product;
use BookPub::DB::Item::BookProduct;
use BookPub::DB::Item::ChapterProduct;

use lib '/app/tools/rps/lib';
use RPS::File::Sale;
use RPS::File::File;
use RPS::Sale::File;
use RPS::Sale::Match;

use lib '/app/tools/raptor/lib';
use Raptor::DB::Item::ProductInputMap;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::Album;
use RPS::DB::Item::Master;
use RPS::DB::Item::Product;
use RPS::DB::Item::Track;

use Getopt::Std;
$| = 1;

# -----------------------------------------

unless ( -t STDIN ) {
    die "can't open log file\n" unless ( open( LOG, '>>/app/data/sale_import/rematch.log' ) );
    select LOG;
}
print scalar localtime(time) . " - running with args '@ARGV'\n";

my %opt;
getopts( 'c:f:s:h:rmdu', \%opt );

usage() unless $opt{c};
my $hostName = $opt{h} || hostname();
$hostName = lc $hostName;
$hostName =~ s/^(.+?)\..*$/$1/;

my @client_list = $opt{c} =~ /all/i ? sort { $a <=> $b } keys %Common::RSDB::CLIENT_DB : split /,/, $opt{c};
my $file_id    = $opt{f} or 0;
my $service_id = $opt{s} or 0;
my $no_regexp  = $opt{r} or 0;
my $map_only        = $opt{m} ? 1 : 0;
my $decide_map_only = $opt{d} ? 1 : 0;
my $UPDATE          = $opt{u} ? 0 : 1;

# Let's figure out the epoch time 24 hours ago and then later we can
# test if the table update times are greater then that.
my $current_time   = time;
my $yesterday_time = $current_time - 86400;

foreach my $client_id (@client_list) {
    next unless ( $Common::RSDB::CLIENT_DB{$client_id}{host_id} eq $hostName );

    print "client $client_id\n";

    # We need to instantiate the application singleton, so that libraries
    # we might call downstream will have their proper global context.
    #
    my $singleton = Common::RSApp->new( clientID => $client_id );

    # Two different code paths - 1 for RPS, 1 for BookPub.
    # !!! This really needs better abstraction...
    #
    my $clientDB = Common::DB::Item::Client->Lookup( client_id => $client_id );
    next unless $clientDB;

    my $client_summary;
    if ( $clientDB->type_mask & Common::DB::Item::ClientType::kClientTypeBitMaskDA ) {
        $client_summary = map_unmatched(
            client_id       => $client_id,
            file_id         => $file_id,
            service_id      => $service_id,
            no_regexp       => $no_regexp,
            map_only        => $map_only,
            decide_map_only => $decide_map_only,
            yesterday_time  => $yesterday_time,
        );
    } elsif ( $clientDB->type_mask & Common::DB::Item::ClientType::kClientTypeBitMaskBookPub ) {
        $client_summary = map_unmatched_bookpub(
            client_id       => $client_id,
            file_id         => $file_id,
            service_id      => $service_id,
            no_regexp       => $no_regexp,
            map_only        => $map_only,
            decide_map_only => $decide_map_only,
            yesterday_time  => $yesterday_time,
        );
    } else {
        print "skipping\n";
        next;
    }

    print "summary for $client_id";

    if ( keys %$client_summary ) {
        my %count = (
            files      => 0,
            mapped     => 0,
            matched    => 0,
            exceptions => 0,
        );

        print "\n";

        # This blows up with a divide by zero error in some circumstances!  That's pretty lame...
        #
        #        map {
        #            my $total = $client_summary->{$_}{mapped} + $client_summary->{$_}{matched};
        #            printf("%d: %d mapped + %d matched = %d of %d total exceptions (%0.1f\%)\n",
        #                $_, $client_summary->{$_}{mapped}, $client_summary->{$_}{matched}, $total,
        #                $client_summary->{$_}{exceptions}, ($total/$client_summary->{$_}{exceptions}) * 100
        #            );
        #            $count{files} += $client_summary->{$_}{files};
        #            $count{mapped} += $client_summary->{$_}{mapped};
        #            $count{matched} += $client_summary->{$_}{matched};
        #            $count{exceptions} += $client_summary->{$_}{exceptions};
        #        } sort { $a <=> $b } keys %$client_summary;
        #
        #        my $total = $count{mapped} + $count{matched};
        #        printf("\n%d files processed, %d mapped + %d matched = %d of %d (%0.02f\%)\n\n",
        #               $count{files}, $count{mapped}, $count{matched}, $total,
        #               $count{exceptions}, $total / $count{exceptions} * 100);

        print Dumper($client_summary) . "\n";
    } else {
        print " - no change\n";
    }
    print scalar localtime(time) . " - END client $client_id\n";
}
close(LOG);

# -----------------------------------------

sub map_unmatched {
    my %args    = @_;
    my %summary = ();

    my $sql = 'SELECT file_id FROM file WHERE file_status=' . File::File::STATUS_OPEN . ' AND remaining_exceptions > 0';
    $sql .= " AND file_id=$args{file_id}" if $args{file_id};
    $sql .= ' ORDER BY file_id';

    my $files = RPS::File::Files->new( client_id => $args{client_id} );
    return unless $files->getByQuery($sql);

    my $yesterday_time = $args{yesterday_time};

    my $map_only = 0;

    if ( $args{map_only} ) {
        $map_only = 1;
    } elsif ( $args{decide_map_only} ) {
        $map_only = check_for_updated_metadata($yesterday_time);
    }

    my $updated_product_input_map;
    if ( $map_only == 1 ) {

        # We aren't going to find any new matches using mapped matchings only
        # if the product_input_map table hasn't changed.
        #
        $updated_product_input_map = check_for_updated_product_input_map($yesterday_time);
    }

    if ( $map_only == 0 || ( $map_only == 1 && $updated_product_input_map == 1 ) ) {
        while ( my $fileObj = $files->GetNext() ) {
            my $matches_found   = 0;
            my $file_service_id = $fileObj->ServiceID();
            next if ( $args{service_id} and $args{service_id} != $file_service_id );

            $summary{$file_service_id}{files}++;
            $summary{$file_service_id}{exceptions} += $fileObj->RemainingExceptions();

            my $sales = RPS::File::Sales->new( client_id => $args{client_id} );
            $sales->GetUnmatchedByFile( file_id => $fileObj->FileID );

            while ( my $sale = $sales->GetNext() ) {
                my $data = {
                    product_type       => $sale->ProductType,
                    upc                => $sale->UPC,
                    isrc               => $sale->ISRC,
                    artist             => $sale->ArtistName,
                    album              => $sale->AlbumName,
                    track              => $sale->TrackName,
                    track_num          => $sale->TrackNum,
                    format             => $sale->FormatType,
                    media_type         => $sale->MediaType,
                    service_id         => $file_service_id,
                    service_product_id => $sale->ServiceProductID,
                };

                my $match = RPS::Sale::Match->new( client_id => $args{client_id}, map_type => 'batch' );

                my $result;

                if ( $map_only == 1 ) {
                    $result = $match->FindBestMatch(
                        data               => $data,
                        skip_rec           => 1,
                        skip_regexp_search => $args{no_regexp},
                        map_only           => 1,
                    );
                    next
                      unless (
                        $result
                        && (   $result->ImportStatus == File::Sale::STATUS_MAPPED
                            || $result->ImportStatus == File::Sale::STATUS_AUTO_MAPPED
                            || $result->ImportStatus == File::Sale::STATUS_BATCH_MAPPED
                            || $result->ImportStatus == File::Sale::STATUS_DONT_MATCH )
                      );
                    $summary{$file_service_id}{mapped}++;
                    $matches_found++;
                } else {
                    next
                      unless (
                        $result = $match->FindBestMatch(
                            data               => $data,
                            skip_rec           => 1,
                            skip_regexp_search => $args{no_regexp},
                        )
                      );

                    if ( $result->ImportStatus == File::Sale::STATUS_MATCH ) {
                        $summary{$file_service_id}{matched}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_MAPPED ) {
                        $summary{$file_service_id}{mapped}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_AUTO_MAPPED ) {
                        $summary{$file_service_id}{mapped}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_BATCH_MAPPED ) {
                        $summary{$file_service_id}{mapped}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_DONT_MATCH ) {
                        $summary{$file_service_id}{mapped}++;    ## really...??
                    } else {
                        next;
                    }
                    $matches_found++;
                }

                if ($UPDATE) {
                    $sale->ImportStatus( $result->ImportStatus );
                    $sale->ProductID( $result->ProductIDs->[0] );
                    $sale->MapID( $result->MapID )
                      if ( $result->ImportStatus == File::Sale::STATUS_MAPPED
                        || $result->ImportStatus == File::Sale::STATUS_AUTO_MAPPED
                        || $result->ImportStatus == File::Sale::STATUS_BATCH_MAPPED
                        || $result->ImportStatus == File::Sale::STATUS_DONT_MATCH );
                    $sale->Save();

                }
            }

            if ($matches_found) {
                printf( "file %d - %d\n", $fileObj->FileID(), $matches_found );
                $fileObj->RecalculateRemainingExceptions() if $UPDATE;
            }

        }
    }
    return \%summary;
}

sub check_for_updated_metadata {
    print "Checking for metadata updates in the past 24 hours...\n";

    my $yesterday_time = shift;

    # Need to check album, track, product, and master tables.
    #
    my $album_table_status = RPS::DB::Item::Album->GetTableStatusHashDateMod();
    my $album_update_time  = $album_table_status->{Update_time};
    $album_update_time = convert_to_epoch($album_update_time);

    # As soon as we find a table that has been updated in the past 24 hours, we can stop looking and
    # return the signal to _not_ use mapped matching only.
    #
    if ( $album_update_time > $yesterday_time ) {
        print "Found albums updated in the past 24 hours!\n";
        return 0;
    }

    my $track_table_status = RPS::DB::Item::Track->GetTableStatusHashDateMod();
    my $track_update_time  = $track_table_status->{Update_time};
    $track_update_time = convert_to_epoch($track_update_time);

    if ( $track_update_time > $yesterday_time ) {
        print "Found tracks updated in the past 24 hours!\n";
        return 0;
    }

    my $product_table_status = RPS::DB::Item::Product->GetTableStatusHashDateMod();
    my $product_update_time  = $product_table_status->{Update_time};
    $product_update_time = convert_to_epoch($product_update_time);

    if ( $product_update_time > $yesterday_time ) {
        print "Found products updated in the past 24 hours!\n";
        return 0;
    }

    my $master_table_status = RPS::DB::Item::Master->GetTableStatusHashDateMod();
    my $master_update_time  = $master_table_status->{Update_time};
    $master_update_time = convert_to_epoch($master_update_time);

    if ( $master_update_time > $yesterday_time ) {
        print "Found masters updated in the past 24 hours!\n";
        return 0;
    }

    # If nothing has been updated in the past 24 hours,
    # we will set the flag to use mapped matching only.
    print "No updates found.\n";
    return 1;
}

sub check_for_updated_product_input_map {
    print "Checking for product_input_map updates in the past 24 hours...\n";

    my $yesterday_time = shift;

    my $product_input_map_status = Raptor::DB::Item::ProductInputMap->GetTableStatusHashDateMod();
    my $product_input_map_time   = $product_input_map_status->{Update_time};
    $product_input_map_time = convert_to_epoch($product_input_map_time);

    if ( $product_input_map_time > $yesterday_time ) {
        print "product_input_map has been updated in the past 24 hours!\n";
        return 1;
    }

    print "No product_input_map updates found.\n";
    return 0;
}

# !!! For now, I'm just going to go through a hideous cut-and-paste hack job.
# !!! This whole process needs to be abstracted better.
#
sub map_unmatched_bookpub {
    my %args    = @_;
    my %summary = ();

    my $sql = 'SELECT * FROM file WHERE file_status=' . File::File::STATUS_OPEN . ' AND remaining_exceptions > 0';
    $sql .= " AND file_id=$args{file_id}" if $args{file_id};
    $sql .= ' ORDER BY file_id';

    my $files = BookPub::DB::Item::File->GetAll($sql);

    my $yesterday_time = $args{yesterday_time};

    my $map_only = 0;

    if ( $args{map_only} ) {
        $map_only = 1;
    } elsif ( $args{decide_map_only} ) {
        $map_only = check_for_updated_metadata_bookpub($yesterday_time);
    }

    my $updated_product_input_map;
    if ( $map_only == 1 ) {

        # We aren't going to find any new matches using mapped matchings only
        # if the product_input_map table hasn't changed.
        #
        $updated_product_input_map = check_for_updated_product_input_map_bookpub($yesterday_time);
    }

    if ( $map_only == 0 || ( $map_only == 1 && $updated_product_input_map == 1 ) ) {
        while ( my $fileObj = $files->next() ) {
            my $matches_found   = 0;
            my $file_service_id = $fileObj->service_id();
            next if ( $args{service_id} and $args{service_id} != $file_service_id );

            $summary{$file_service_id}{files}++;
            $summary{$file_service_id}{exceptions} += $fileObj->remaining_exceptions();

            #            my $sales = RPS::File::Sales->new(client_id => $args{client_id});
            #            $sales->GetUnmatchedByFile(file_id => $fileObj->FileID);
            my $sales = BookPub::DB::Item::Sale->GetUnmatchedByFile( $fileObj->file_id );

            while ( my $sale = $sales->next() ) {
                my $match = BookPub::Sale::Match->new( client_id => $args{client_id}, map_type => 'batch' );

                my $result;

                if ( $map_only == 1 ) {
                    $result = $match->FindBestMatch(
                        data               => $sale,              # we just pass the DB::Item straight through now...
                        skip_rec           => 1,
                        skip_regexp_search => $args{no_regexp},
                        map_only           => 1,
                    );
                    next
                      unless (
                        $result
                        && (   $result->ImportStatus == File::Sale::STATUS_MAPPED
                            || $result->ImportStatus == File::Sale::STATUS_AUTO_MAPPED
                            || $result->ImportStatus == File::Sale::STATUS_BATCH_MAPPED
                            || $result->ImportStatus == File::Sale::STATUS_DONT_MATCH )
                      );
                    $summary{$file_service_id}{mapped}++;
                    $matches_found++;
                } else {
                    next
                      unless (
                        $result = $match->FindBestMatch(
                            data               => $sale,
                            skip_rec           => 1,
                            skip_regexp_search => $args{no_regexp},
                        )
                      );

                    if ( $result->ImportStatus == File::Sale::STATUS_MATCH ) {
                        $summary{$file_service_id}{matched}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_MAPPED ) {
                        $summary{$file_service_id}{mapped}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_AUTO_MAPPED ) {
                        $summary{$file_service_id}{mapped}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_BATCH_MAPPED ) {
                        $summary{$file_service_id}{mapped}++;
                    } elsif ( $result->ImportStatus == File::Sale::STATUS_DONT_MATCH ) {
                        $summary{$file_service_id}{mapped}++;    ## really...??
                    } else {
                        next;
                    }
                    $matches_found++;
                }

                if ($UPDATE) {
                    $sale->import_status( $result->ImportStatus );
                    $sale->product_id( $result->ProductIDs->[0] );
                    $sale->map_id( $result->MapID )
                      if ( $result->ImportStatus == File::Sale::STATUS_MAPPED
                        || $result->ImportStatus == File::Sale::STATUS_AUTO_MAPPED
                        || $result->ImportStatus == File::Sale::STATUS_BATCH_MAPPED
                        || $result->ImportStatus == File::Sale::STATUS_DONT_MATCH );
                    $sale->save();

                }
            }

            if ($matches_found) {
                printf( "file %d - %d\n", $fileObj->file_id(), $matches_found );

                if ($UPDATE) {
                    my $otherFileObj = BookPub::Tracker::File->new( dbItem => $fileObj );
                    $otherFileObj->updateSummary();
                    $otherFileObj->save();
                }
            }

        }
    }

    # Update price validation
    my $validator = new BookPub::Price::Validator();
    $validator->run();

    return \%summary;
}

sub check_for_updated_metadata_bookpub {
    print "Checking for metadata updates in the past 24 hours...\n";

    my $yesterday_time = shift;

    # Metadata tables to check for book publisher app are:
    # - book
    # - chapter
    # - contributor
    # - book_contributor
    # - book_subject
    # - product
    # - book_product
    # - chapter_product
    #
    foreach my $metadataPackage (
        'BookPub::DB::Item::Book',        'BookPub::DB::Item::Chapter',
        'BookPub::DB::Item::Contributor', 'BookPub::DB::Item::BookContributor',
        'BookPub::DB::Item::BookSubject', 'BookPub::DB::Item::Product',
        'BookPub::DB::Item::BookProduct', 'BookPub::DB::Item::ChapterProduct',
      ) {
        my $tableStatus = $metadataPackage->GetTableStatusHashDateMod();
        my $updateTime  = convert_to_epoch( $tableStatus->{Update_time} );

        if ( $updateTime > $yesterday_time ) {
            print "Found $metadataPackage updated in the past 24 hours!\n";
            print "$updateTime > $yesterday_time\n";
            return 0;
        }
    }

    # If nothing has been updated in the past 24 hours,
    # we will set the flag to use mapped matching only.
    print "No updates found.\n";
    return 1;
}

sub check_for_updated_product_input_map_bookpub {
    print "Checking for product_input_map updates in the past 24 hours...\n";

    my $yesterday_time = shift;

    my $product_input_map_status = BookPub::DB::Item::ProductInputMap->GetTableStatusHashDateMod();
    my $product_input_map_time   = $product_input_map_status->{Update_time};
    $product_input_map_time = convert_to_epoch($product_input_map_time);

    if ( $product_input_map_time > $yesterday_time ) {
        print "product_input_map has been updated in the past 24 hours!\n";
        return 1;
    }

    print "No product_input_map updates found.\n";
    return 0;
}

sub convert_to_epoch {
    my ($date_time) = @_;
    my $yday        = 0;
    my $wday        = 0;
    my $year  = substr( $date_time, 0,  4 ) - 1900;
    my $month = substr( $date_time, 5,  2 ) - 1;
    my $day   = substr( $date_time, 8,  2 );
    my $hours = substr( $date_time, 11, 2 );
    my $min   = substr( $date_time, 14, 2 );
    my $sec   = substr( $date_time, 17, 2 );

    return mktime( $sec, $min, $hours, $day, $month, $year, $wday, $yday );
}

sub usage {

    print <<EofUSAGE;
Usage: $0 (only -c option is required)
  -c <CLIENT_ID> (separate multiple with ',' no spaces or specify 'all')
  -f <FILE_ID>
  -s <SERVICE_ID>
  -h <hostname> (only required for testing on non-prod machines)
  -m (mapped matching only)
  -d (decide whether to use mapped matching only)
  -r (no regexp search)
  -u (no update)
EofUSAGE
    exit();
}

