#------------------------------------------------------------
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package Distribution::Test::Command;
use strict;
use warnings;

use Data::Dumper;

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

use lib '/app/tools/rps/lib';
use RPS::Product::Product;
use RPS::DB::Item::Product;
use RPS::Catalog::DistributionCache;
use RPS::Catalog::Album;
use RPS::Distribution::ParameterFactory;

use lib '/app/tools/distribution/lib';
use Distribution::Util;
use Distribution::DistributionProcess;
use Distribution::Test::PackageCompare;
use Distribution::DB::Item::DistributionService;
use Distribution::DB::Item::DistributionProcess;

use base 'Common::Script';

sub _album_id_config_name { assert( 0, "Must be overloaded" ) }

sub _init {
    my $self = shift;
    my %args = @_;

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

    my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() );
    my $date = sprintf( "%04d%02d%02d_%02d%02d%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec );

    $self->{_albumIDs} =
      $args{album_id}
      ? [ $args{album_id} ]
      : Common::RSApp::GetConfig( 'distribution', $self->_album_id_config_name() );

    $self->{_testConfDir} = $args{test_conf_dir}
      || Common::RSApp::GetConfig( 'distribution', 'test_conf_dir' );

    $self->{_testOutputBase} = $args{test_output_dir}
      || Common::RSApp::GetConfig( 'distribution', 'test_output_dir' );
    $self->{_testOutputBase} .= "/$date";

    $self->{_clientID} = Common::RSApp::GetConfig( 'distribution', 'test_client_id' );

    $self->{_jobWrapper} = Common::RSApp::GetConfig( 'distribution', 'test_job_wrapper' );

    $self->{_serviceID}    = $args{service_id};
    $self->{_report_email} = $args{report_email};
    $self->{_noClean}      = $args{no_clean};
    $self->{_force}        = $args{force};

    assert( $self->{_clientID}, "Client ID Required" );

    assert( $self->{_jobWrapper},    "Job Wrapper Required" );
    assert( -e $self->{_jobWrapper}, "$self->{_jobWrapper} is not executable" );

    assert( $self->{_albumIDs}, "Album IDs Required" );

    return $self->SUPER::_init(@_);
}

sub run_process {
    my $self = shift;

    $self->log("Starting Test Process, Client ID: $self->{_clientID}");
    $self->_process_client( $self->{_clientID} );

    my $baseDir = $self->{_testOutputBase};

}

sub _process {
    my $self      = shift;
    my $client_id = $self->{_clientID};
    my @services  = $self->_getServices();

    my $found;

    assert($client_id);

    my $services = Distribution::DB::Item::DistributionService->GetAllClientServices(
        $client_id,
        status     => [ 'active', 'development', 'maintenance' ],
        service_id => $self->{_serviceID}
    );

    if (@services) {
        $self->_requeueAlbums();
    } else {
        $self->log(" - No services found for this client");
    }

    foreach my $service (@services) {
        $self->_process_service( $client_id, $service ) if ( $service->{configured} );
    }

    $self->_cleanBase();
}

sub _process_service {
    my $self      = shift;
    my $client_id = shift;
    my $service   = shift;

    $self->_test($service);
}

sub _test {
    my $self    = shift;
    my $service = shift;

    if (   $self->_buildPackage($service)
        && $self->_testPackage($service)
        && $self->_cleanup($service) ) {

        $self->log("Package validated successfully!");
        return 1;
    } else {
        $self->{_noClean} = 1;
        $self->log("****** Package validation failed ******");
        return;
    }

}

sub _getServices {
    my $self      = shift;
    my $client_id = $self->{_clientID};

    my $found;
    my @services;

    assert($client_id);

    my $services = Distribution::DB::Item::DistributionService->GetAllClientServices(
        $client_id,
        status     => [ 'active', 'development', 'maintenance' ],
        service_id => $self->{_serviceID}
    );

    while ( $services->hasNext ) {
        $found = 1;
        my $service = $services->next;
        push( @services, $service );
    }

    return @services;
}

####
#    Requeue albums
####
sub _requeueAlbums {
    my $self     = shift;
    my @services = $self->_getServices();
    my @albums   = @{ $self->{_albumIDs} };
    my @service_ids;

    foreach my $service (@services) {
        push( @service_ids, $service->service_id );
    }

    $self->debug("Marking albums for redelivery: @albums");
    $self->debug("Processing services: @service_ids");

    foreach my $album_id (@albums) {
        $self->debug("  - product lookup for album_id: $album_id");
        my $productDB = RPS::DB::Item::Product->GetProductsByAlbumID( $album_id, RPS::DB::Item::Product::kProductTypeDigital );

        if ($productDB) {
            my $product = new RPS::Product::Product( _dbItem => $productDB->next );
            $product->requeueDelivery(@service_ids) if ( @service_ids && $product );
        }
    }

    # Refresh the validation cache
    my $cache = new RPS::Catalog::DistributionCache;
    $cache->refreshCache();
}

####
#     Build test packages
####

sub _buildPackage {
    my $self    = shift;
    my $service = shift;

    return $self->_createDistributionProcess($service)
      && $self->_runJob($service);
}

sub _createDistributionProcess {
    my $self    = shift;
    my $service = shift;
    my $dist;
    my $product;
    my $album;
    my $harness;
    my $count;
    my $dist_process;

    my $p_dists = Distribution::DB::Item::ProductDistribution->GetAllToDistribute(
        service_id => $service->service_id,
        album_ids  => $self->{_albumIDs}
    );

    $self->log("   + No products found for this service") unless ( $p_dists->size() );

    while ( $p_dists->hasNext() ) {
        $dist = $p_dists->next();

        $product = new RPS::Product::Product( productID => $dist->product_id );
        next if ( $self->{_albumID} && $self->{_albumID} != $product->AssetID() );
        $album = new RPS::Catalog::Album( albumID => $product->AssetID );

        $self->log( "    + processing album: " . $product->AssetID . " product: " . $product->ProductCode );

        # !!!  We can't run validation on CM servers because /media doesn't exist in a windows environment.
        #      that means we can't check for asset existance.  However a fix for this would be to update the
        #      system to use the global config files so the /media base path can be machine specific.
        #		unless( $album->validateAlbumAudio() ) {
        #            $self->log( "    + Failed audio validation for " . $product->ProductCode );
        #            next;
        #        }

        #        unless( $album->validateAlbumImage() ) {
        #            $self->log( "    + Failed image validation for " . $product->ProductCode );
        #            next;
        #        }

        #        unless( $album->validateAssets() ) {
        #            $self->log( "    + Failed file validation for " . $product->ProductCode );
        #            next;
        #        }

        unless ( $album->validateMetadata( harness => $harness ) ) {
            $self->log( "    + Failed metadata validation for " . $product->ProductCode );
            next;
        }

        $self->log( "    + Album validation successful " . $product->ProductCode );

        # The album is validate so lets add it to a job queue.
        unless ($dist_process) {
            $dist_process = Distribution::DB::Item::DistributionProcess->Create( created => 1 );
            $dist_process->save();
            assert($dist_process);
        }

        $dist->distribution_process_id( $dist_process->distribution_process_id );
        $dist->save();
        $dist->setQueuedDate( $dist->product_distribution_id );

        $count++;
    }

    return $self->finializeDistributionProcess( $dist_process, $service->service_id )
      if ($dist_process);

    return;
}

sub finializeDistributionProcess {
    my $self         = shift;
    my $dist_process = shift;
    my $service_id   = shift;

    assert($self);
    assert($dist_process);
    assert($service_id);

    my $dist_process_id = $dist_process->distribution_process_id;

    $self->log("    + Finalizing Distribution Process.  ID: $dist_process_id");

    assert( !$dist_process->xml_data, "This process already contains data.  I'm not going to overwrite" );

    # Generate the XML
    my $paramObj = RPS::Distribution::ParameterFactory::CreateDistributionParameters($dist_process_id);
    my $xmlString = Common::WriteXML::GetXMLString( $paramObj, undef, skip_array_indexing => 1 );

    assert($xmlString);

    $dist_process->xml_data($xmlString);
    $dist_process->save();

    my $process = Distribution::DistributionProcess->new( distributionProcessID => $dist_process_id );
    $process->enqueuePackagerTest( write_path => $self->{_testOutputBase} );

    $self->{_distProcessID} = $dist_process_id;
}

sub _runJob {
    my $self    = shift;
    my $service = shift;
    my $baseDir = $self->{_testOutputBase} . "/" . $service->service_id;
    my $logFile = $baseDir . "/output.log";

    mkdir_local($baseDir);

    my $jobID = $self->_getJobID();

    unless ($jobID) {
        $self->log("  ** No dist process found **");
        return;
    }

    my $cmd = $self->{_jobWrapper} . " -j $jobID";

    $self->debug("    - Execute: $cmd");
    my $res = system("$cmd");

    die "Failed to run command: $cmd"
      if ($res);

    my $job = Job::DB::Item::Job->Lookup( job_id => $jobID );
    $job->start_date( Common::Util::today_and_now() );
    $job->save();

    my $status = $job->exit_code;

    $self->log("    + Job ID: $jobID, Exit Status: $status");

    if ($status) {
        $self->log("Failed job for service");
        return;
    }

    return 1;
}

####
#     Test Package Structure
####
sub _packageCompareOptions { }

sub _testPackage {
    my $self        = shift;
    my $service     = shift;
    my $service_id  = $service->service_id;
    my $client_id   = Common::RSApp::GetClientID();
    my $dir         = "$self->{_testOutputBase}/$service_id/$client_id";
    my $package_dir = `ls $dir` if ( -e $dir );

    unless ($package_dir) {
        $self->log("  ** No packages found to test in '$dir' ");
        return;
    }

    my $compare = new Distribution::Test::PackageCompare(
        $self->_packageCompareOptions(),
        test_dir   => $self->{_testConfDir} . "/$service_id",
        output_dir => $self->{_testOutputBase} . "/$service_id",
        source_dir => $self->{_testOutputBase} . "/$service_id/$client_id/$package_dir"
    );

    $self->{_serviceStatus}->{ $service->service_name() }->{status}  = $compare->run();
    $self->{_serviceStatus}->{ $service->service_name() }->{service} = $service;

    return $self->{_serviceStatus}->{ $service->service_name() }->{status};
}

####
#     Create Test Structure
####
sub _createTests {
    my $self       = shift;
    my $service    = shift;
    my $service_id = $service->service_id;
    my $client_id  = Common::RSApp::GetClientID();
    my $basedir    = "$self->{_testOutputBase}/$service_id/$client_id";
    my $cmd        = "ls $basedir";

    $self->log(" - Creating test configuration");
    $self->debug("    + EXECUTE: $cmd");

    my $package_dir = `$cmd` if ( -d $basedir );
    chomp $package_dir if ($package_dir);

    $self->debug("    - res: $package_dir") if ($package_dir);

    assert( $package_dir, "no packages found in dir: $basedir" );

    my $compare = new Distribution::Test::PackageCompare(
        test_dir   => $self->{_testConfDir} . "/$service_id",
        output_dir => $self->{_testOutputBase} . "/$service_id",
        source_dir => $self->{_testOutputBase} . "/$service_id/$client_id/$package_dir"
    );

    return $compare->create();
}

####
#     Run Reports
####
sub _runReport {
    my $self = shift;

    return unless ( $self->{_serviceStatus} );
    print "Package Test Results\n";

    foreach my $serviceName ( keys %{ $self->{_serviceStatus} } ) {
        printf( "  %-20s %s\n",
            "$serviceName (" . $self->{_serviceStatus}->{$serviceName}->{service}->service_id . "): ",
            $self->{_serviceStatus}->{$serviceName}->{status} ? "passed" : "FAILED!" );
    }

    return 1;
}

####
#     Clean up generated packages
####
sub _cleanup {
    my $self    = shift;
    my $service = shift;
    my $baseDir = $self->{_testOutputBase} . "/" . $service->service_id;
    my $jobID   = $self->_getJobID();

    assert( $service->service_id );

    my $job = Job::DB::Item::Job->Lookup( job_id => $jobID );
    my $status = $job->exit_code;

    if ( !$self->{_noClean} ) {
        $self->log("    + Cleaning $baseDir");

        if ( -e $baseDir ) {
            system("rm -r $baseDir ");
        } else {
            $self->log("    - not found, ignoring");
        }

    } else {
        $self->log("    + Not cleaning $baseDir");
    }

    return 1;
}

sub _cleanTestdir {
    my $self    = shift;
    my $service = shift;
    my $baseDir = "$self->{_testConfDir}/" . $service->service_id;

    $self->log("    + Checking $baseDir");

    if ( -e "$baseDir/manifest" && !$self->{_force} ) {
        $self->log("  ** Test configuration exists in '$baseDir', use -f to force overwrite ** ");
        return;
    }

    my $cmd = "find $baseDir -type f | grep -v CVS | grep -v ignore | grep -v '^$baseDir\$' | grep -v '^$baseDir/metadata\$' ";
    $self->debug("    - Execute: $cmd");

    open( FIND, "$cmd |" ) || die "Failed to execute: $cmd: $!";

    my @lines = <FIND>;

    while ( my $line = pop(@lines) ) {
        chomp($line);
        $self->_removeFile($line);
    }

    return 1;
}

sub _removeFile {
    my $self = shift;
    my $file = shift;

    $self->debug("    + Remove ($file)");

    if ( -e $file ) {
        system("rm -rf $file");
    }
}

###################################

sub _getJobID {
    my $self            = shift;
    my $dist_process_id = $self->{_distProcessID} || return;
    my $client_id       = Common::RSApp::GetClientID();

    my $distJob = Distribution::DB::Item::DistributionJob->LookupByClient( $dist_process_id, $client_id );

    return $distJob ? $distJob->job_id : undef;
}

sub _cleanBase {
    my $self    = shift;
    my $baseDir = $self->{_testOutputBase};

    unless ( !-e $baseDir || $self->{_noClean} ) {
        system("rm -r $baseDir ") if ( -e $baseDir );
    }
}

1;
