#!/usr/bin/perl
use strict;

use Proc::Pidfile;
use Text::ParseWords;
use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::RSApp;
use Common::Log;
use Common::DB::Item::TableChangeLog;
use Common::DB::Item::Client;
use Common::DB::Item::ClientType;
use Common::DB::Item::ClientCMIDMap;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::Track;
use RPS::DB::Item::Album;
use RPS::DB::Item::Product;
use RPS::DB::Item::Master;
use RPS::DB::Item::Song;
use RPS::DB::Item::Artist;
use RPS::DB::Item::Composer;
use RPS::DB::Item::Label;
use RPS::DB::Item::ProductTrack;

use constant kCMClientID => 112;


# Create a pidfile.  This is just a file that will contain this script's
# process id.  We'll use the Proc::Pidfile module - This will allow
# us to silently exit if the file already exists (so we don't run two instances
# of this script at once), and will automatically clean up the pid file.
#
use constant kPIDFile => '/tmp/consumeMetadataChangeLog.pid';
my $pidfile = Proc::Pidfile->new(pidfile => kPIDFile, silent => 1);


# Redirect stderr to a log file.
#
use constant kLogFilePath => '/app/data/logs/consumeMetadataChangeLog.log';
open(STDERR, ">> " . kLogFilePath) or die "cannot redirect STDERR: $!\n";
select STDERR; $| = 1;
select STDOUT; $| = 1;


my $gErrors;

# This will map table name to constructor, and any additional state we
# might need to keep track of.
#
my %gTableMap = 
(
    'track' =>
    {
        class => 'RPS::DB::Item::Track',
        autoField => 'track_id',
    },
    'album' =>
    {
        class => 'RPS::DB::Item::Album',
        autoField => 'album_id',
    },
    'product' =>
    {
        class => 'RPS::DB::Item::Product',
        autoField => 'product_id',
    },
    'master' =>
    {  
        class => 'RPS::DB::Item::Master',
        autoField => 'master_id',
    },
    'song' =>
    {
        class => 'RPS::DB::Item::Song',
        autoField => 'song_id',
    },
    'artist' =>
    {
        class => 'RPS::DB::Item::Artist',
        autoField => 'artist_id',
		noDelete => 1,
		uniqueKey => 'name',
    },
    'composer' =>
    {
        class => 'RPS::DB::Item::Composer',
        autoField => 'composer_id',
		noDelete => 1,
		uniqueKey => 'name',
    },
    'label' =>
    {
        class => 'RPS::DB::Item::Label',
        autoField => 'label_id',
    },
    'product_track' =>
    {
        class => 'RPS::DB::Item::ProductTrack',
        autoField => 'product_track_id',
        # No autoincrement key for this table
    },
);



my $singleton = Common::RSApp->new(clientID => kCMClientID);

# Make a map of valid client ids.
#
my %gDistClientIDs;
my $distClients = Common::DB::Item::Client->GetAllOfType(Common::DB::Item::ClientType::kClientTypeBitMaskDistribution);
while (my $client = $distClients->next())
{
    # !!! Just in case...
    # 
    next if $client->client_id == kCMClientID;
	next if $client->client_id == 4; # BIG_FISH
	next if $client->client_id == 42; # IDEA
	next if $client->client_id == 23; # COMPASS
	next if $client->client_id == 38; # BLIND_PIG
	next if $client->client_id == 9; # LAUGH
	next if $client->client_id == 60; # RSDEMO
#	next if $client->client_id == 122; # CENTAUR
	next if $client->client_id == 123; # WELK
	next if $client->client_id == 202; # rstest
	next if $client->client_id == 288; # rstest

    $gDistClientIDs{$client->client_id} = 1;
}


# We're also going to need to know which ids get mapped.
# !!! We might want to put these into a table someplace?
# !!! But for now I'll just hard-code it.
#
my %gIDsToRemap = 
(
    track_id => 1,
    album_id => 1,
    product_id => 1,
    master_id => 1,
    song_id => 1,
    artist_id => 1,
    composer_id => 1,
    label_id => 1,
	asset_id => 1,
	product_track_id => 1,
);

my $changes = Common::DB::Item::TableChangeLog->GetAllNew();
while (my $change = $changes->next())
{
    # Delete entries for non-distribution clients
    # !!! Not going to delete - going to mark these as 'skipped'.
    # !!! Seems safer until we get all the kinks worked out.
    #
    if (! $gDistClientIDs{$change->client_id})
    {
        $change->status(Common::DB::Item::TableChangeLog::kStatusSkipped);
        $change->save();
        next;
    }


    # !!! Wrap this whole damn thing in an eval block.
    # If something throwns an exeception, we'll mark the record and move on.
    #

    eval {
    my $tableDef = $gTableMap{$change->table_name};
    if (! $tableDef)
    {
        # The log table is now catching changes for tables that this script may not care about.
        # That's not really an error.
        #
        $change->status(Common::DB::Item::TableChangeLog::kStatusSkipped);
        $change->save();
        next;
    }
    
    if (Common::DB::Item::TableChangeLog::kActionDelete eq $change->action)
    {
        handleDelete($change, $tableDef);
    }
    elsif (Common::DB::Item::TableChangeLog::kActionInsert eq $change->action)
    {
        # !!! If we already _have_ this record in the CM data, then we want to treat
        # !!! this like an update...
        #
        handleInsert($change, $tableDef);
    }
    elsif (Common::DB::Item::TableChangeLog::kActionUpdate eq $change->action)
    {
        handleUpdate($change, $tableDef);
    }
    else
    {
        # We don't recognize this action for some reason.  It is probably a malformed
        # log entry.  So we'll mark it as bad rather than deleting it, so we have some
        # hope of later fixing the problem.
        #
        logError("ERROR - Unknown action '".$change->action."' for log entry " . $change->table_change_log_id . " - marking as bad");
        $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
        $change->save();
    }
    };
    if ($@)
    {
        logError("ERROR - Caught exception: $@ : for log entry " . $change->table_change_log_id . " - marking as bad");
        $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
        $change->save();
    }
}


if (defined $gErrors)
{
    open(MAIL, "|/usr/lib/sendmail -oi -t");
    print MAIL "From: consumeMetaDataChangeLog\n";
    print MAIL 'To: knott@royaltyshare.com' . "\n";
    print MAIL 'Subject: ERROR REPORT' . "\n\n";
    print MAIL "$gErrors\n";
    close(MAIL);
}
    

sub handleDelete
{
    my ($change, $tableDef) = @_;

    my $whereHash = parsePairString($change->where_pairs);
    remapClientIDs($whereHash, $change->client_id);


    # Fetch the current metadata record.
    #
    my $class = $tableDef->{class};
    my $data = $class->Lookup(%$whereHash);
    if (! $data)
    {
        logError("ERROR ON DELETE - could not find matching record for log entry " . $change->table_change_log_id . " - marking as bad");
        $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
        $change->save();
        return;
    }


    # !!! We need some special handling for the artist and composer tables.
    # !!! Basically, we _never_ delete artist or composers.
    #
    if ($tableDef->{noDelete})
    {
        $change->status(Common::DB::Item::TableChangeLog::kStatusNoDelete);
	    $change->save();
	    return;
    }
    

    # Delete it!
    #
    $data->delete();


    # Done with the change log item
    #
    $change->status(Common::DB::Item::TableChangeLog::kStatusProcessed);
    $change->save();
#    $change->delete();
}

sub handleInsert
{
    my ($change, $tableDef) = @_;

    my $setHash = parsePairString($change->set_pairs);

    # Pull the auto-increment value out of the hash, if there is one.
    # !!! Well, currently every table we map has an auto-increment column...
    #
    # !!! If we already _have_ a record with this id, we want to treat this like an update.
    #
    my $autoFieldName = $tableDef->{autoField};
    my $autoFieldValue;
    if (defined $autoFieldName)
    {
        $autoFieldValue = $setHash->{$autoFieldName};
        if (! defined $autoFieldValue)
        {
            logError("ERROR ON INSERT- missing primary key for log entry " . $change->table_change_log_id . " - marking as bad");
            $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
            $change->save();
            return;
        }

        
        # ... actually, if the record is already there, I think we want to _punt_.
        #
        my $mapping = Common::DB::Item::ClientCMIDMap->Lookup
        (
            client_id => $change->client_id,
            id_type => $autoFieldName,
            client_site_id => $autoFieldValue,
        );

        if ($mapping)
        {
            logError("WARNING - Skipping INSERT for change " . $change->table_change_log_id . " - record with that key already exists");
            $change->status(Common::DB::Item::TableChangeLog::kStatusSkipped);
            $change->save();
            return;
        }

        delete $setHash->{$autoFieldName};
    }


    # Now we re-map the ids.
    #
    remapClientIDs($setHash, $change->client_id);


    my $class = $tableDef->{class};

    # If we're going to insert a record with a unique key, we need to see if a record
    # with that key already exists!
    # If it does, then we just want to re-use that id, and not try to create a new record.
    #
    if ($tableDef->{uniqueKey} && defined $setHash->{$tableDef->{uniqueKey}})
    {
        my $previousRecord = $class->Lookup($tableDef->{uniqueKey} => $setHash->{$tableDef->{uniqueKey}});
	if ($previousRecord)
	{
            my $cmIDValue = $previousRecord->$autoFieldName;

            my $newIDMapping = Common::DB::Item::ClientCMIDMap->Create
            (
                client_id => $change->client_id,
                client_site_id => $autoFieldValue,
                cm_site_id => $cmIDValue,
                id_type => $autoFieldName,
            );
            $newIDMapping->save();

    	    $change->status(Common::DB::Item::TableChangeLog::kStatusDuplicate);
    	    $change->save();
	    return;
	}
    }


    # Create the new metadata record.
    #
    my $newItem = $class->Create(%$setHash);
    $newItem->save();
    

    # If there is an autoincrement primary key on this table, get the new id
    # and add that to the mapping table.
    #
    if (defined $autoFieldName)
    {
        my $cmIDValue = $newItem->$autoFieldName;

        my $newIDMapping = Common::DB::Item::ClientCMIDMap->Create
        (
            client_id => $change->client_id,
            client_site_id => $autoFieldValue,
            cm_site_id => $cmIDValue,
            id_type => $autoFieldName,
        );
        $newIDMapping->save();
    }

    # All done, we can get rid of the change entry
    #
#    $change->delete();
    $change->status(Common::DB::Item::TableChangeLog::kStatusProcessed);
    $change->save();
}


sub handleUpdate
{
    my ($change, $tableDef) = @_;

    # Parse both pair strings into hashes.
    #
    my $setHash = parsePairString($change->set_pairs);
    my $whereHash = parsePairString($change->where_pairs);


    # remap the ids.
    # Note!  All these ids should already exist in the table!
    #
    eval
    {
        remapClientIDs($whereHash, $change->client_id);
        remapClientIDs($setHash, $change->client_id);
    };
    if ($@)
    {
        logError("ERROR ON UPDATE - problem finding id map for log entry " . $change->table_change_log_id . " - $@ - marking as bad");
        $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
        $change->save();
        return;
    }

    # Fetch the current metadata record.
    #
    my $class = $tableDef->{class};
    my $data = $class->Lookup(%$whereHash);
    if (! $data)
    {
        logError("ERROR ON UPDATE - could not find matching record for log entry " . $change->table_change_log_id . " - marking as bad");
        $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
        $change->save();
        return;
    }

    
    # Now update the record, and save the changes.
    #
    # !!! Going to be paranoid, and do this in an eval, just in case we somehow
    # get a bogus field name and the DB::Item throws an exception.
    # 
    eval
    {
        foreach my $key (keys %$setHash)
        {
            $data->$key($setHash->{$key});
        }

        $data->save();
    };
    if ($@)
    {
        logError("ERROR ON UPDATE - id:" . $change->table_change_log_id . " threw this exception: $@ - marking as bad");
        $change->status(Common::DB::Item::TableChangeLog::kStatusBad);
        $change->save();
        return;
    }

    # Done with the change log item.
    #
#    $change->delete();
    $change->status(Common::DB::Item::TableChangeLog::kStatusProcessed);
    $change->save();
}
    


sub remapClientIDs
{
    my ($hash, $clientID) = @_;

	my $assetID;

    foreach my $keyName (keys %$hash)
    {
        # Want to limit hitting the database to recognized id types
        #
        next unless $gIDsToRemap{$keyName};


		# Need to handle asset_id differently!
		# I will need to get the product_type_id from the product table.
		# This might be tricky.
		#
		if ('asset_id' eq $keyName)
		{
		    # Let's wait until the rest of the mapping is complete.
			# Then (in theory) we'll have a valid, re-mapped product_id.
			#
			$assetID = $hash->{$keyName};
			next;
		}


        my $mapping = Common::DB::Item::ClientCMIDMap->Lookup
        (
            client_id => $clientID,
            id_type => $keyName,
            client_site_id => $hash->{$keyName},
        );
        if ($mapping)
        {
            $hash->{$keyName} = $mapping->cm_site_id;
        }
        else
        {
            die "ERROR - unable to map $keyName : " . $hash->{$keyName};
        }
    }

	if ($assetID)
	{
		my $productTypeID = $hash->{product_type_id};
		if (! $productTypeID)
		{
			# Get the product id.
			# This really _must_ work, or we're hosed.
			#
			my $productID = $hash->{product_id};
			die "ERROR - attempting to remap asset_id, but cannot find product_id!" unless $productID;

			my $cmSiteProduct = RPS::DB::Item::Product->Lookup(product_id => $productID);
			die "ERROR - attempting to remap asset_id, but cannot find product with product_id $productID" unless $cmSiteProduct;
			$productTypeID = $cmSiteProduct->product_type_id;
		}

		my $assetType = 'album_id';
		if (RPS::DB::Item::Product::kProductTypeDigitalTrack == $productTypeID)
		{
			$assetType = 'track_id';
		}

        my $mapping = Common::DB::Item::ClientCMIDMap->Lookup
        (
            client_id => $clientID,
            id_type => $assetType,
            client_site_id => $assetID,
        );
        if ($mapping)
        {
            $hash->{'asset_id'} = $mapping->cm_site_id;
        }
	}
}


sub parsePairString
{
    my ($string) = @_;

    my %resultHash;
    my @pairs = Text::ParseWords::quotewords(',',0,$string);
    foreach my $pair (@pairs)
    {
        my ($key, $value) = Text::ParseWords::quotewords('=',0,$pair);
        $resultHash{$key} = $value;
    }

    return \%resultHash;
}

sub logError
{
    my ($error) = @_;

    Common::Log::Print($error);
    $gErrors .= $error . "\n";
}
