#!/usr/bin/perl
use strict;

use Data::Dumper;
use Getopt::Std;
use POSIX qw(:sys_wait_h :signal_h :errno_h);

use lib '/app/tools/common/lib';
use Common::Amazon::Mail;
use Common::Process;
use Common::RSApp;
use Common::Log;
use Common::Assert;
use Common::Email;
use Common::Client;
use Common::DB::Item;

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::CatalogImport;
use BookPub::Catalog::Import::Process::Importer::Factory;
use BookPub::Catalog::Import::Process::Fetcher::Factory;
use BookPub::Catalog::Import::Job::ImageRetrieve;
use BookPub::Price::Job::PriceValidation;

# Parse the command-line options.
#
my $gVerbosityLevel = 1;
my %gOptions;
_parseCommandLine( \%gOptions );

my $gCatalogImportID = $gOptions{importID};
assert($gCatalogImportID);
my $gClientID = $gOptions{clientID};
assert($gClientID);

# Now we fork off a child process.
# This way, if the child didn't exit cleanly, we can tell.
#
my $gChildPID = fork();
if ($gChildPID) {
    _parent();
} else {
    _child();
}

sub _child {

    # Install a signal handler to catch ctrl-c
    #
    $SIG{INT} = \&CTRL_C;

    # Instantiate the Application singleton.
    #
    my $app = Common::RSApp->new( clientID => $gClientID );

    # Fetch the CatalogImport record.
    #
    my $catalogImport = BookPub::DB::Item::CatalogImport->Lookup( catalog_import_id => $gCatalogImportID );
    die "ERROR - Invalid catalog import id $gCatalogImportID" unless $catalogImport;
    die "ERROR - catalog import id $gCatalogImportID is in an invalid state"
      unless BookPub::DB::Item::CatalogImport::kImportStatusImportReady eq $catalogImport->status();

    $catalogImport->import_start_time(Common::DB::Item::kDateTimeNow);
    $catalogImport->save();

    # Finish it.
    #
    my $importerProcess =
      BookPub::Catalog::Import::Process::Importer::Factory::NewImporter( importRecord => $catalogImport, logLevel => $gVerbosityLevel );
    my $exitCode = $importerProcess->run();

    $catalogImport->status(BookPub::DB::Item::CatalogImport::kImportStatusDone);
    $catalogImport->import_end_time(Common::DB::Item::kDateTimeNow);
    $catalogImport->save();

    # Don't do any cleanup or queue any more jobs if the processes exited with an error status.
    # I want to keep the import data to examine for debugging purposes...
    #
    if ( !$exitCode ) {

        if( Common::RSApp::IsProductionServer ) {
            my $fetchProcess =
            BookPub::Catalog::Import::Process::Fetcher::Factory::NewFetcher( importRecord => $catalogImport, logLevel => $gVerbosityLevel );
            $fetchProcess->archiveFTPFiles();
        }

        # !!! Temporary hack to stop this from happening for DTMV Test !!!
        #
        if ( $gClientID != 310 ) {

            # Queue up the image retrieval job.
            #
            my $job = BookPub::Catalog::Import::Job::ImageRetrieve->new( importID => $gCatalogImportID, logLevel => $gVerbosityLevel );
            $job->enqueue();
        }


        # Call the clean-up code.  This deletes all the duplicate metadata stored in the 'catalog_import_*' files (everything but
        # the catalog_import_file table).
        #
        BookPub::DB::Item::CatalogImport->CleanUp($gCatalogImportID);

        # !!! Temporary hack to stop this from happening for DTMV Test !!!
        #
        if ( $gClientID != 310 ) {

            # Queue a job to re-validate the prices for sales that might have been touched by this import
            #
            my $vJob = BookPub::Price::Job::PriceValidation->new( catalogImportID => $gCatalogImportID );
            $vJob->enqueue();
        }
    }

    Common::Log::Print("CHILD EXIT CODE = $exitCode");
    exit($exitCode);
}

my $gChildExitCode;
my $gChildIsRunning;

sub _parent {

    # Install a 'reaper' signal handler.  This is a callback that gets invoked
    # when the child process exits.
    #
    local $SIG{CHLD} = \&REAPER;

    # We will install a signal handler in the _child_ to catch SIGINT (i.e ctrl-c), so
    # we will want to ignore that signal here in the parent.
    #
    local $SIG{INT} = 'IGNORE';
    $gChildIsRunning = 1;
    while ($gChildIsRunning) {

        # Basically, we just wait around until the child exits.
        # !!! Is it possible to use sleep(0) here?   Will the kernel wake us up (i.e. cause
        # sleep to return) if we catch a signal?  That would be better...
        #
        sleep(2);
    }

    # The Process object is generally responsible for updating the Import::Record state.
    # However, if something odd happened, we'll have a chance here to _attempt_ to record that state.
    #
    my $app = Common::RSApp->new( clientID => $gClientID );
    my $catalogImport = BookPub::DB::Item::CatalogImport->Lookup( catalog_import_id => $gCatalogImportID );
    die "ERROR - Invalid catalog import id $gCatalogImportID" unless $catalogImport;

    my $fetchProcess =
      BookPub::Catalog::Import::Process::Fetcher::Factory::NewFetcher( importRecord => $catalogImport, logLevel => $gVerbosityLevel );
    my $errorFilePath = $fetchProcess->errorFilePath();
    if ( 0 != $gChildExitCode || -f $errorFilePath ) {
        if ( 0 != $gChildExitCode ) {
            my $status;
            if ( 1 == $gChildExitCode ) {
                $status = BookPub::DB::Item::CatalogImport::kImportStatusAborted;
            } else {
                $status = BookPub::DB::Item::CatalogImport::kImportStatusErrorImport;
            }

            $catalogImport->status($status);
            $catalogImport->import_end_time(Common::DB::Item::kDateTimeNow);
            $catalogImport->save();
        }

        # Send an email reporting the problem.
        #
        if ( Common::RSApp::IsProductionServer() ) {
            my $client = Common::Client->new( clientID => $gClientID );
            my $clientName = $client->ClientName();
            my $body;

            if ( -f $errorFilePath ) {
                $body = "Client ID $gClientID ( $clientName ), Catalog Import ID $gCatalogImportID : Errors occurred during Import:\n";
                open ERRORFILE, "< $errorFilePath" or die "UNABLE TO OPEN $errorFilePath : $!";
                while ( my $line = <ERRORFILE> ) {
                    $body .= "$line\n";
                }
                close ERRORFILE;
            } else {
                $body = "Client ID $gClientID ( $clientName ), Catalog Import ID $gCatalogImportID : Complete Import Failed\n";
            }

            my $subject = 'Error Report';
            if ( 0 != $gChildExitCode ) {
                $subject = 'FATAL ' . $subject;
            }
            my $emailTo = Common::RSApp::GetConfig( 'bookpub', 'error_report_email' );

            my $importLogPath = $errorFilePath;
            $importLogPath =~ s/ERROR_LOG/import_log/;

            my $mail = Common::Amazon::Mail->new;
            $mail->to( $emailTo );
            $mail->subject($subject);
            $mail->body($body);

            if ( -f $errorFilePath ) {
                $mail->sendit( [$errorFilePath] );
            } elsif ( -f $importLogPath ) {
                $mail->sendit( [$importLogPath] );
            } else {
                $mail->sendit();
            }
        }
    }

    exit($gChildExitCode);
}

sub REAPER {

    # -1 means 'any child process'
    # WNOHANG means to not block until something exits - waitpid will return immediately.
    #
    my $deadChildPID = waitpid( -1, &WNOHANG );

    if ( -1 == $deadChildPID ) {

        # just kidding... ignore this.
        #
    } elsif ( WIFEXITED($?) ) {

        # Fetch the exit status code.  This requires an 8 bit shift...
        #
        $gChildExitCode = $? >> 8;

        # Set the flag which tells the parent the child is done.
        #
        $gChildIsRunning = 0;
    }

    # In some systems, once a signal handler is invoked, we need to re-install it.
    #
    $SIG{CHLD} = \&REAPER;
}

sub CTRL_C {
    Common::Log::Print("caught a ctrl-c, aborting");
    exit(1);
}

sub _parseCommandLine {
    my ($settings) = @_;

    my %opt;
    getopts( 'i:c:V:', \%opt );

    if ( !$opt{i} || !$opt{c} ) {
        _usage();
        exit(1);
    }
    $settings->{importID} = $opt{i};
    $settings->{clientID} = $opt{c};

    if ( defined $opt{V} ) {
        $gVerbosityLevel = $opt{V};
    }
}

sub _usage {
    print "\nusage: $0 -c client_id -f path_to_XML_file [-V n]\n";
    print "\n";
    print "Arguments:\n";
    print "\t-c <client_id>\t\tThe client_id of the client to process\n";
    print "\t-i <catalog_import_id>\tThe ID of the catalog import record we're processing.\n";
    print "\t-V <N>\t\t\tVerbosity level - 0 means no output\n";
}

