#!/usr/bin/perl -Ilibs -I../www/libs

package API::GP::Action;

use strict;
use warnings;

use Carp;
use Data::Serializer::Raw;
use File::Copy;
use File::Temp;
use File::Slurp;
use JSON;
use Try::Tiny;
use URI;

use ECS::Config;
use ECS::DB;
use ECS::dbConfig;
use ECS::Site;
use ECS::GP;
use ECS::Purchasing::PO;
use ECS::Purchasing::PR;
use ECS::Purchasing::WR;
use ECS::Purchasing::VendorCredit;
use PerlX::Maybe;


my $configURI = URI->new($ECS::Config::c{'gpDaemonURL'});

# public variables
our $PORT = $configURI->port();

my $ERROR_FILE = "/tmp/.gp_error.$PORT";

# private variables
my %actions = (
    'invoice'              => \&sendInvoice,
    'orderPayment'         => \&sendOrderPayment,
    'orderReturn'          => \&sendOrderReturn,
    'purchaseOrder'        => \&sendPurchaseOrder,
    'purchaseReceipt'      => \&sendPurchaseReceipt,
    #'vendorReturn'         => \&sendInventoryAdjustmentAndCreditMemo,
    'vendorReturn'         => \&sendVendorReturn,
    'inventoryAdjustments' => \&sendInventoryAdjustments,
    'balanceAdjustment'    => \&sendBalanceAdjustment,
    'autoApplyPayments'    => \&sendAutoApplyPayments,
	'workReceipt'          => \&sendWorkReceipt,
    'vendor'               => \&sendVendor,
	'setCustomerClass'     => \&sendSetCustomerClass,
	'insertCustomerClass'  => \&sendInsertCustomerClass,
	'deleteCustomerClass'  => \&sendDeleteCustomerClass,
);


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

sub getErrors {
    my $serializer = Data::Serializer::Raw->new;

    return try {
        $serializer->retrieve( $ERROR_FILE );
    }
    catch { [] };
}

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

sub saveErrors {
    my ($errors) = @_;

    my $serializer = Data::Serializer::Raw->new;

    my ($of, $tempname) = File::Temp::tempfile();
    $serializer->store( $errors, $of );
    close $of;

    File::Copy::move( $tempname, $ERROR_FILE );

    return;
}

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

sub dispatch {
    my $input = shift;

    my $p = {
        'dbh'  => ECS::DB->connect({'config' => $ECS::dbConfig::db{'mysqlSlave'}, readonly => 1}),
        'input' => $input,
    };

    if(not $input->{'action'}) {
        print "Missing action\n";
        ECS::DB->dbDisconnectAllHandles();
        return -1;
    }

    my $action = $actions{ $input->{'action'} };
    if(not $action) {
        print "Invalid action: $input->{'action'}\n";
        ECS::DB->dbDisconnectAllHandles();
        return -1;
    }

    my $result = $action->($p);
    ECS::DB->dbDisconnectAllHandles();

    return $result;
}

###############################################################################
# actions                                                                     #
###############################################################################
# requires: orderID
# optional: dropShip
sub sendInvoice {
    my $p = shift;

    return try {
        my $order = ECS::Order->load({
            'DBH'     => $p->{'dbh'},
            'orderID' => $p->{'input'}->{'orderID'},
        });
		my $invoice;
		if ( (my $invoiceID = $p->{input}{invoiceID}) && $order->getInvoices) {
			$invoice = List::Util::first { $_->orderInvoiceID == $invoiceID } $order->invoices->@*
		}
		my $dropship = $p->{input}{dropShip} || 0;
        ECS::GP::invoice({
            'order' => $order,
			$dropship ? ( dropShip => 1 ) : (),
			maybe invoiceCount => $p->{input}{invoiceCount},
			maybe invoiceID => $p->{input}{invoiceID},
			maybe invoice   => $invoice,
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: orderID, orderPaymentID
sub sendOrderPayment {
    my $p = shift;

    return try {
        my $order = ECS::Order->load({
            'DBH'   => $p->{'dbh'},
            'orderID' => $p->{'input'}->{'orderID'},
        });
        my $orderPayment;
        for my $otherPayment ($order->getPayments) {
            $orderPayment = $otherPayment 
                if $otherPayment->orderPaymentID
                    == $p->{'input'}->{'orderPaymentID'};
        }
        ECS::GP::orderPayment({
            'orderPayment' => $orderPayment,
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: poID
sub sendPurchaseOrder {
    my $p = shift;
	return -1 unless $p->{input}{poID};
    
    return try {
        ECS::Purchasing::PO::pushToGP({
            'DBH'  => $p->{'dbh'},
            'poID' => $p->{'input'}->{'poID'},
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: prID
sub sendPurchaseReceipt {
    my $p = shift;

    return try {
        ECS::Purchasing::PR::pushToGP({
            'DBH'  => $p->{'dbh'},
            'prID' => $p->{'input'}->{'prID'},
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: orderID, orderReturnCreditID
sub sendOrderReturn {
    my $p = shift;

    return try {
        my $order = ECS::Order->load({
            'DBH'     => $p->{'dbh'},
            'orderID' => $p->{'input'}->{'orderID'},
        });
        my $orderReturn = ECS::Order::Return::Credit->load({
            'DBH'                 => $p->{'dbh'},
            'orderReturnCreditID' => $p->{'input'}->{'orderReturnCreditID'},
        });
        ECS::GP::orderReturnMulti({
            'dbh'         => $p->{'dbh'},
            'order'       => $order,
            'orderReturn' => $orderReturn,
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# This routine takes the vendorReturn item and splits it into two transactions
# Inventory Adjustment and Credit Memo.
# requires: vendorCreditID

sub sendInventoryAdjustmentAndCreditMemo {
    my $p = shift;
    my $vendorCredit;
    return try {
        $vendorCredit = ECS::Purchasing::VendorCredit->new({
            'dbh'            => $p->{'dbh'},
            'vendorCreditID' => $p->{'input'}->{'vendorCreditID'},
        });
        ECS::GP::vendorReturnInventoryAdjustment({
            'vendorCredit' => $vendorCredit,
        });
		1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: vendorCreditID
sub sendVendorReturn {
    my $p = shift;

    return try {
        my $vendorCredit = ECS::Purchasing::VendorCredit->new({
            'dbh'            => $p->{'dbh'},
            'vendorCreditID' => $p->{'input'}->{'vendorCreditID'},
        });
        ECS::GP::vendorReturn({
            'vendorCredit' => $vendorCredit,
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: inventoryAdjustmentIDs => []
sub sendInventoryAdjustments {
    my $p = shift;


    return try {
        croak "Missing inventoryAdjustmentIDs"
            unless $p->{'input'}->{'inventoryAdjustmentIDs'};

        my $plh = join ", ",
            ("?") x @{ $p->{'input'}->{'inventoryAdjustmentIDs'} },
        ;
        my $sql = qq{
            SELECT
                productInventoryLog.itemID AS inventoryAdjustmentID,
                productInventoryLog.productID,
                productInventoryLog.newQty - productInventoryLog.oldQty AS qty,
                productInventoryLog.atCost AS cost,
                UNIX_TIMESTAMP(productInventoryLog.ts) AS tsAdjusted,
                inventoryAdjustmentReason.gpCode AS gpReasonCode,
                product.partnumber
            FROM productInventoryLog
                JOIN product USING (productID)
                JOIN inventoryAdjustment
                    ON (productInventoryLog.itemID = inventoryAdjustment.inventoryAdjustmentID)
                JOIN inventoryAdjustmentReason USING (inventoryAdjustmentReasonID)
            WHERE
                productInventoryLog.reason = 'inventoryAdjustment'
                AND productInventoryLog.itemID IN ($plh)
                AND product.type <> 'Service'
        };
        my $sth = $p->{'dbh'}->prepare($sql);
        $sth->execute(@{ $p->{'input'}->{'inventoryAdjustmentIDs'} });
        my $adjustments = $sth->fetchall_arrayref({});
        
        if(@{ $adjustments }) {
            ECS::GP::inventoryAdjustments({
                'dbh'         => $p->{'dbh'},
                'adjustments' => $adjustments,
            });
        }
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires: storeCreditTransactionID
# optional: siteID (builds ECS::Site)
# optional: batch
sub sendBalanceAdjustment {
    my $p = shift;
    my $site;
    if ( $site = $p->{input}{site} ) {
        if (ref $site) {
            unless (Scalar::Util::blessed( $site )) {
                bless $site, 'ECS::Site'; 
            }
        } else {
            $site = ECS::Site::fromName $site;
        }
    } elsif ( my $siteID = $p->{input}{siteID} ) {
        $site = ECS::Site::fromID $siteID
    } else {
        $site = ECS::Site::fromName $ECS::Site::PRIMARY_SITE;
    }

    our %TABLES = map { ($_)x2 } qw/storeCreditTransaction customer billinfo shipinfo/;
    $TABLES{ $_ } = join '.' => $site->prefix => $_ for keys %TABLES;
	

    return try {
        croak "Missing storeCreditTransactionID"
            unless $p->{'input'}->{'storeCreditTransactionID'};

        my $sql = qq{
            SELECT
                storeCreditTransaction.storeCreditTransactionID
                    AS adjustmentID,
                storeCreditTransaction.timestamp,
                storeCreditTransaction.amount,
                storeCreditTransaction.customerID,
                storeCreditTransaction.storeCreditTransactionReasonID
                    AS reasonID,
                storeCreditTransaction.reasonText,
                storeCreditTransactionReason.description AS reason,
                storeCreditTransactionReason.gpAction,
                storeCreditTransactionReason.gpPaymentID,
                storeCreditTransactionReason.gpLedger,
                TRIM(CONCAT(
                    IFNULL(billinfo.name_first, ''), ' ', IFNULL(billinfo.name_last, '')
                )) AS customerName,
                ? AS siteID
            FROM $TABLES{storeCreditTransaction}
                JOIN storeCreditTransactionReason
                    USING (storeCreditTransactionReasonID)
                JOIN $TABLES{customer}
                    ON (storeCreditTransaction.customerID = customer.customerID)
                LEFT JOIN $TABLES{billinfo}
                    ON (customer.billID = billinfo.billID)
            WHERE
                storeCreditTransaction.storeCreditTransactionID = ?
        };
        my $balanceAdjustment = try { 
			my $sth = $p->{'dbh'}->prepare($sql);
            $sth->execute($site->siteID, $p->{'input'}->{'storeCreditTransactionID'});
            my $balanceAdjustment = $sth->fetchrow_hashref;
        };

        return 0
            unless $balanceAdjustment;
        $balanceAdjustment->{ site } = $site;
        $balanceAdjustment->{ batch } = $p->{input}{batch} if $p->{input}{batch};

        ECS::GP::newBalanceAdjustment({
            'balanceAdjustment' => $balanceAdjustment,
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

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

sub sendAutoApplyPayments {
    my $p = shift;
    
    return try {
        ECS::GP::autoApplyPayments();
        1;
    }
    catch {
        carp $_;
        0;
    };
}

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

sub sendWorkReceipt {
	my $p = shift;
	
	return 1 unless $p->{input} && $p->{input}{wrID};
    return try {
        ECS::Purchasing::WR::pushToGP({
            'DBH'  => $p->{'dbh'},
            'wrID' => $p->{'input'}->{'wrID'},
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# requires vendorID
sub sendVendor {
	my $p = shift;

	return 1 unless $p->{input} && $p->{input}{vendorID};
	return try {
		my $vendor = ECS::Vendor->load({
			'DBH'      => $p->{'dbh'},
			'vendorID' => $p->{'input'}->{'vendorID'},
		});
		ECS::GP::inventoryVendor({
			vendor	=> $vendor,
		});
		1;
	} catch {
		carp $_;
		0;
	};
}

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

sub sendSetCustomerClass {
	my $p = shift;

	return 1 unless $p->{input} && $p->{input}{customerID};
    return try {
        ECS::GP::setCustomerClass({
         'customerID' => $p->{'input'}->{'customerID'},
         'customerClass' => $p->{'input'}->{'classID'},
         'siteName' => $p->{'input'}->{'siteName'},
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

###############################################################################
# This inserts a NEW customer class, it doesn't change a customers current class
#
sub sendInsertCustomerClass {
	my $p = shift;
	return 1 unless $p->{input} && $p->{input}{classID};
    return try {
        ECS::GP::insertCustomerClass({
         'customerClass' => $p->{'input'}->{'classID'},
         'customerClassDesc' => $p->{'input'}->{'classDesc'},
         'siteName' => $p->{'input'}->{'siteName'},
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

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

sub sendDeleteCustomerClass {
	my $p = shift;

	return 1 unless $p->{input} && $p->{input}{classID};
    return try {
        ECS::GP::deleteCustomerClass({
         'customerClassID' => $p->{'input'}->{'classID'},
         'siteName' => $p->{'input'}->{'siteName'},
        });
        1;
    }
    catch {
        carp $_;
        0;
    };
}

1;
