package BookPub::Sale::Report::Royalty::Wiley;

use strict;
use Template;
use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::Log;
use Common::Client;
use Common::Util;
use Common::DB::ItemCollection;
use Common::DB::Item;

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::Book;
use BookPub::DB::Item::BookProduct;
use BookPub::DB::Item::ClientService;
use BookPub::DB::Item::Period;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::Service;

use base 'BookPub::Sale::Report::Royalty';

use constant kDB         => Common::DB::Item::kClientDB;
use constant OUTDIR_BASE => '/app/data/sale_report/tmp/';

# We have to change some behavior for these Canada SANs.
my %canadaSANs;
$canadaSANs{"07157480009"} = 1; # Amazon
$canadaSANs{"07157480032"} = 1; # Amazon
$canadaSANs{"07157480033"} = 1; # Amazon
$canadaSANs{"10894220000"} = 1; # Google
$canadaSANs{"10562740001"} = 1; # Kobo
$canadaSANs{"10734550000"} = 1; # Apple

my %monthNames = (
    "01" => "January",
    "02" => "February",
    "03" => "March",
    "04" => "April",
    "05" => "May",
    "06" => "June",
    "07" => "July",
    "08" => "August",
    "09" => "September",
    "10" => "October",
    "11" => "November",
    "12" => "December",
);

my %quarterNames = (
    "01" => "Q1",
    "02" => "Q1",
    "03" => "Q1",
    "04" => "Q2",
    "05" => "Q2",
    "06" => "Q2",
    "07" => "Q3",
    "08" => "Q3",
    "09" => "Q3",
    "10" => "Q4",
    "11" => "Q4",
    "12" => "Q4",
);

# Let's cache the feed id for each file, so that we don't have to figure it out for every sale.
my %fileFeedID;

# All of our client service IDs are for ebooks, but it turns out we want to use the for all product types.
# So, we'll just set this value here and use it later for client service lookups.
#my $productType = BookPub::DB::Item::Sale::kProductTypeEBook;
# !!! Actually, let's just stop using that as a part of the criteria.

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;

    $self->_init(@_);

    return $self;
}

sub fileName {
    my ($self) = @_;
    if ( !$self->{_fileName} ) {
        my $period = BookPub::DB::Item::Period->Lookup( period_id => $self->{_periodID} );
        my $periodRef = $period->name();
        $periodRef =~ s/\s+/_/g;
        $periodRef =~ s/\W+//g;
        $periodRef =~ s/__/_/g;
        $periodRef ||= $period->period_id();

        $self->{_fileName} = "Wiley_RoyaltyReport_$periodRef.xml";
    }
    return $self->{_fileName};
}

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

    $self->SUPER::_init(%args);

    $self->{clientID} = $args{clientID} ? $args{clientID} : Common::RSApp::GetClientID();
    $self->{tmplObj} = Template->new( { START_TAG => '{', END_TAG => '}' } );

}

sub create {
    my $self = shift;
    my %tmplVars;

    # We'll keep a separate tally of units and revenue and
    # return those at the end for validation.
    my $totalUnits;
    my $totalRevenueUnrounded;

    my $periodID = $self->periodID();

    # Returns this format: YYYY-MM-DD HH:MM:SS
    my $today = Common::Util::today_and_now();
    my $date  = substr( $today, 0, 10 );
    my $time  = substr( $today, 11 );

    my $outFilePath = $self->filePath();

    unless ( open( OUT, '>:utf8', $outFilePath ) ) {
        die "can't open output file ($outFilePath) - $!\n";
    }

    # Go ahead the print out the opening tag
    print OUT "<EBookSalesToWILEY>\n";

    # Add in the Routing block
    print OUT "\t<Routing>\n";
    print OUT "\t\t<SenderSAN>ROYALTYSHARE</SenderSAN>\n";
    print OUT "\t\t<ReceiverSAN>WILEY</ReceiverSAN>\n";
    print OUT "\t\t<TransactionBatchNumber>$periodID</TransactionBatchNumber>\n";
    print OUT "\t\t<Date>$date</Date>\n";
    print OUT "\t\t<Time>$time</Time>\n";
    print OUT "\t</Routing>\n";

    # And now for the sales
    # We want to do this in some kind of order,
    # so let's get the service list sorted alphabeticaly first.
    my $services = BookPub::DB::Item::Service->GetAllSortedByName();

    while ( $services->hasNext() ) {
        my $service   = $services->next();
        my $serviceID = $service->service_id;

        # So we're just going to have to grab all of the sales for a service,
        # and then sort them into buckets based on client service id
        my $sales = BookPub::DB::Item::Sale->GetUnconsolidatedForReport( $periodID, $serviceID );

        my %consolidatedSales;

        while ( $sales->hasNext() ) {
            my $sale = $sales->next();

            # Let's skip the sales with 0 revenue AND 0 units.
            if ( $sale->revenue == 0 && $sale->units == 0 ) {
                next;
            }

            # Extract file object for sale record
            my $oFile = BookPub::DB::Item::File->Lookup( file_id => $sale->file_id );

            my ( $clientServiceID, %sanCriteria ) = $self->_getClientServiceID($oFile, $sale);

            #  At this point, we should have somehow found the client service ID.
            if ( !$clientServiceID ) {
                my $error = "No match found in client service table for sale id " . $sale->sale_id;
                $self->_sendErrorEmail($error);
                die $error . "\n";
            }

            my $purchaseOrder = $self->_getPurchaseOrder($oFile, $sale);

            #  At this point, we should have somehow created the purchase order.
            if ( !$purchaseOrder ) {
                my $error = "No purchase order format set up for service id: $serviceID";
                $self->_sendErrorEmail($error);
                die $error . "\n";
            }

            # !!! FYI, this ISBN is actually from the book_product.
            my $isbn = $sale->r_isbn13;

            my $discount = $sale->discount * 100;
            $discount = Common::Util::formatFixedPoint( $discount, 2 );

            my $units   = $sale->units;
            my $revenue = $sale->revenue;

            # Need to add these to the validation total before we start messing with them.
            $totalUnits += $units;

            my $listPrice      = $sale->list_price;
            my $conversionRate = $sale->conversion_rate;
            my $currencyCode   = $sale->currency_code;

            my $convertedRevenue = $revenue;

            # For validation, we need to convert all foreign revenue.
            if ( $currencyCode ne 'USD' ) {
                $convertedRevenue *= $conversionRate;

                # For display in the output file, we need to convert all foreign revenue
                # EXCEPT for certain CA CAD files.
                if ( !$canadaSANs{$clientServiceID} ) {
                    $revenue *= $conversionRate;
                }
            }

            # And now we can add the converted revenue to the validation total.
            $totalRevenueUnrounded += $convertedRevenue;

            # We need to make sure the list price is in USD too.
            my $listPriceCurrency = $sale->r_list_price_currency;
            if ( !$listPriceCurrency ) {
                $listPriceCurrency = $currencyCode;
            }

            # If it's not USD, let's hope we can convert it.
            if ( $listPriceCurrency ne 'USD' && !$canadaSANs{$clientServiceID} ) {
                if ( $listPriceCurrency eq $currencyCode ) {
                    $listPrice *= $conversionRate;
                } else {

                    # Well, we can't convert this so let's throw an error.
                    my $error = "Unable to convert list price to USD - sale id:" . $sale->sale_id;

                    $self->_sendErrorEmail($error);
                    die $error . "\n";
                }
            }

            # Now that's been converted, go ahead and round it off.
            $listPrice = Common::Util::formatFixedPoint( $listPrice, 2 );

            # Adding a check here for case 5328.
            # If the list price / unit / discount calculation doesn't reconcile,
            # we're going to use the unit price as list price and set the discount to 0.
            # If we have revenue AND 0 units, we need to set units to 1 or -1.
            # We'll do this again with the aggregated numbers, to try to make the final
            # report as accurate as we can.
            if ( $revenue > 0 && $units == 0 ) {
                $units = 1;
            } elsif ( $revenue < 0 && $units == 0 ) {
                $units = -1;
            }

            my $calculatedRevenue = $units * $listPrice * ( ( 100 - $discount ) / 100 );
            $calculatedRevenue = Common::Util::formatFixedPoint( $calculatedRevenue, 2 );
            my $roundedRevenue = Common::Util::formatFixedPoint( $revenue, 2 );

            my $reportedListPrice = "-";
            my $reportedDiscount  = "-";

            if ( $calculatedRevenue != $roundedRevenue ) {
                $reportedListPrice = $listPrice;
                $reportedDiscount  = $discount;

                $listPrice = $revenue / $units;
                $listPrice = Common::Util::formatFixedPoint( $listPrice, 2 );
                $discount  = 0;
            }

            $consolidatedSales{$clientServiceID}{$purchaseOrder}{$isbn}{$listPrice}{$discount}{$reportedListPrice}{$reportedDiscount}
              {revenue} += $revenue;
            $consolidatedSales{$clientServiceID}{$purchaseOrder}{$isbn}{$listPrice}{$discount}{$reportedListPrice}{$reportedDiscount}{units}
              += $units;

        }

        foreach my $clientServiceID ( sort keys %consolidatedSales ) {
            my $clientServiceSales = $consolidatedSales{$clientServiceID};

            foreach my $purchaseOrder ( sort keys %$clientServiceSales ) {
                my $purchaseOrderSales = $clientServiceSales->{$purchaseOrder};

                # Create the EBookSales block
                print OUT "\t<EBookSales>\n";

                my $accountNumber = substr( $clientServiceID, 0, 7 );
                print OUT "\t\t<AccountNumber>$accountNumber</AccountNumber>\n";

                my $shipTo = substr( $clientServiceID, 7, 4 );
                print OUT "\t\t<ShipTo>$shipTo</ShipTo>\n";

                print OUT "\t\t<PurchaseOrder>$purchaseOrder</PurchaseOrder>\n";

                foreach my $isbn ( sort keys %$purchaseOrderSales ) {
                    my $productSales = $purchaseOrderSales->{$isbn};

                    foreach my $price ( sort keys %$productSales ) {
                        my $priceSales = $productSales->{$price};

                        foreach my $discount ( sort keys %$priceSales ) {
                            my $discountSales = $priceSales->{$discount};

                            foreach my $reportedListPrice ( sort keys %$discountSales ) {
                                my $reportedListPriceSales = $discountSales->{$reportedListPrice};

                                foreach my $reportedDiscount ( sort keys %$reportedListPriceSales ) {
                                    my $sales = $reportedListPriceSales->{$reportedDiscount};

                                    my $quantity = $sales->{units};
                                    my $revenuePaidAmount = Common::Util::formatFixedPoint( $sales->{revenue}, 4 );

                                    if ( $revenuePaidAmount != 0 || $quantity != 0 ) {

                                        # Adding a check here for case 5328.
                                        # If the list price / unit / discount calculation doesn't reconcile,
                                        # we're going to use the unit price as list price and set the discount to 0.
                                        # If we have revenue AND 0 units, we need to set units to 1 or -1.
                                        if ( $revenuePaidAmount > 0 && $quantity == 0 ) {
                                            $quantity = 1;
                                        } elsif ( $revenuePaidAmount < 0 && $quantity == 0 ) {
                                            $quantity = -1;
                                        }

                                        my $calculatedRevenue = $quantity * $price * ( ( 100 - $discount ) / 100 );
                                        $calculatedRevenue = Common::Util::formatFixedPoint( $calculatedRevenue, 2 );
                                        my $roundedRevenue = Common::Util::formatFixedPoint( $revenuePaidAmount, 2 );

                                        if ( $calculatedRevenue != $roundedRevenue ) {

                                            # We may have already set these at the sale level,
                                            # and we don't want to overwrite that data.
                                            if ( $reportedListPrice eq "-" ) {
                                                $reportedListPrice = $price;
                                                $reportedDiscount  = $discount;
                                            }
                                            $price    = $revenuePaidAmount / $quantity;
                                            $price    = Common::Util::formatFixedPoint( $price, 2 );
                                            $discount = 0;
                                        }

                                        # If list price is negative, make it positive
                                        # and make sure units are negative.
                                        if ( $price < 0 ) {
                                            $price *= -1;
                                            if ( $quantity > 0 ) {
                                                $quantity *= -1;
                                            }
                                        }

                                        # And now a little hack to make things work better with Wiley's system.
                                        # If the revenue is 0, we need to set the discount rate to 100 (if it's not already).
                                        # We also need to populate the reported discount field, IF we haven't already.
                                        if ( $revenuePaidAmount == 0 && $discount != 100 ) {
                                            if ( $reportedDiscount eq "-" ) {
                                                $reportedDiscount = $discount;
                                            }
                                            $discount = 100;
                                        }

                                        print OUT "\t\t<ItemsStructure>\n";
                                        print OUT "\t\t\t<ProductID>$isbn</ProductID>\n";
                                        print OUT "\t\t\t<Quantity>$quantity</Quantity>\n";
                                        print OUT "\t\t\t<Price>$price</Price>\n";
                                        print OUT "\t\t\t<Discount>$discount</Discount>\n";
                                        print OUT "\t\t\t<RevenuePaidAmount>$revenuePaidAmount</RevenuePaidAmount>\n";

                                        if ( $reportedListPrice ne "-" ) {
                                            print OUT "\t\t\t<ReportedListPrice>$reportedListPrice</ReportedListPrice>\n";
                                        }
                                        if ( $reportedDiscount ne "-" ) {
                                            print OUT "\t\t\t<ReportedDiscount>$reportedDiscount</ReportedDiscount>\n";
                                        }

                                        print OUT "\t\t</ItemsStructure>\n";
                                    }
                                }
                            }
                        }
                    }
                }

                # All done for this client service ID / purchase order combination, close it up.
                print OUT "\t</EBookSales>\n";

            }

        }

    }

    # All done
    print OUT "</EBookSalesToWILEY>";

    close(OUT);

    # Going to send everything back in a hash so that's it's
    # easy to add more stuff in later.
    #
    my %returnValues;
    $returnValues{totalUnits}            = $totalUnits;
    $returnValues{totalRevenueUnrounded} = $totalRevenueUnrounded;

    return (%returnValues);
}

sub _getPurchaseOrder {
    my ( $self, $oFile, $sale ) = @_;

    my $purchaseOrder;

    my $fileName  = $oFile->orig_file_name;

    # Now, let's figure out the purchase order based on the date and the service.
    #
    my $dateBegin   = $sale->date_begin;
    my $dateEnd     = $sale->date_end;
    my $serviceID   = $sale->service_id;
    my $countryCode = $sale->country_code;

    # If we're dealing with a date range, let's normalize it.
    if ( $dateBegin ne $dateEnd ) {
        ( $dateBegin, $dateEnd ) = Common::Util::normalize_sales_month_dates( $dateBegin, $dateEnd );
    }

    my $date = $dateEnd;

    my $year  = substr( $date, 2, 2 );
    my $month = substr( $date, 5, 2 );

    # So the purchase order is going to include either the month or the quarter,
    # depending on which service we're dealing with.

    # Should we set a default?  For now, let's just throw an error.
    if ( $serviceID == BookPub::Tracker::Service::AMAZON() ) {
        if ( my ($fileCode) = $fileName =~ /^(\w{5})[ _.]/i ) {
            # The first five characters of the file name + MMM + YY + CC (see RSD-11111)
            $purchaseOrder = uc( $fileCode . substr($monthNames{$month}, 0, 3) . $year . $countryCode );
        } else {
            # leave as it was before
            $purchaseOrder = $year . " " . $monthNames{$month};
        }
    } elsif ( $serviceID == BookPub::Tracker::Service::APPLE()
        || $serviceID == BookPub::Tracker::Service::BAKERANDTAYLOR()
        || $serviceID == BookPub::Tracker::Service::BARNES_NOBLE()
        || $serviceID == BookPub::Tracker::Service::GOOGLE()
        || $serviceID == BookPub::Tracker::Service::INGRAM_DIGITAL()
        || $serviceID == BookPub::Tracker::Service::KOBO()
        || $serviceID == BookPub::Tracker::Service::LSI()
        || $serviceID == BookPub::Tracker::Service::ODILO()
        || $serviceID == BookPub::Tracker::Service::OREILLY()
        || $serviceID == BookPub::Tracker::Service::SONY() ) {

        # Use the month
        $purchaseOrder = $year . " " . $monthNames{$month};
    } elsif ( $serviceID == BookPub::Tracker::Service::EBOOK()
        || $serviceID == BookPub::Tracker::Service::EBRARY()
        || $serviceID == BookPub::Tracker::Service::EBSCO()
        || $serviceID == BookPub::Tracker::Service::OVERDRIVE()
        || $serviceID == BookPub::Tracker::Service::PROQUEST()
        || $serviceID == BookPub::Tracker::Service::PROQUEST_SUBSCRIPTION() ) {

        # Use the quarter
        $purchaseOrder = $year . " " . $quarterNames{$month};
    }

    return $purchaseOrder;
}

sub _getClientServiceID {
    my ( $self, $oFile, $sale ) = @_;

    # We need to send a back the list of fields and their values that we used to look for the SAN for a service.
    # Service ID is always a criteria, so you don't need to add that in here _unless_ it is the only criteria.
    my %sanCriteria;

    my $clientService;
    my $clientServiceID;
    my $feedID;

    my $saleID       = $sale->sale_id;
    my $serviceID    = $sale->service_id;
    my $fileID       = $sale->file_id;
    my $currencyCode = $sale->currency_code;
    my $countryCode  = $sale->country_code;
    my $fileName     = $oFile->orig_file_name;

    if (   $serviceID == BookPub::Tracker::Service::EBOOK()
        || $serviceID == BookPub::Tracker::Service::AMAZON()
        || $serviceID == BookPub::Tracker::Service::BAKERANDTAYLOR()
        || $serviceID == BookPub::Tracker::Service::BARNES_NOBLE() ) {

        if ( $fileFeedID{$fileID} ) {
            $feedID = $fileFeedID{$fileID};
        } else {
            if ( $serviceID == BookPub::Tracker::Service::EBOOK() ) {
                $feedID = lc( substr( $fileName, 0, 3 ) );

                # We're going to default the feed id to 'ebc', because older versions of the sales
                # files did not include the feed id prefix.
                if ( $feedID ne 'ebc' && $feedID ne 'ebl' ) {
                    $feedID = 'ebc';
                }
            } elsif ( $serviceID == BookPub::Tracker::Service::AMAZON() ) {
                $feedID = uc( substr( $fileName, 0, 8 ) );
                if ($fileName =~ /^(\w{5}).*(?:agency|eBooksPaymentPlusTaxReport)/i) {
                    my $prefix = $1;
                    if ( $countryCode =~ /^(?:AU|NZ)$/i && $fileName =~ /eBooksPaymentPlusTaxReport/i ) {
                        $feedID = uc($prefix);
                    } else {
                        $feedID = uc($prefix) . " AGENCY";
                    }
                } elsif ( $fileName =~ /^(\w{5})[ _](?!digi)(\w\w)/i || $fileName =~ /^(\w{5})[ _]digi.+?[ _](\w\w)(?:[ _]\w{3})?\./i) {
                    $feedID = "$1 $2";
                } elsif ( $fileName =~ /^(\w{5})[ _]dig.*?[ _]\d{8}-\d{8}[ _](\w{2})[ _]?/i ) {
                    $feedID = "$1 $2";
                } elsif ( $countryCode eq 'IN' && $feedID =~ /^BACQQ/i ) {
                    $feedID = "IN";
                }
            } elsif ( $serviceID == BookPub::Tracker::Service::BARNES_NOBLE() ) {
                $fileName =~ m/[^\d](\d{6})?\.csv$/;
                $feedID = $1 || '074129';
            } elsif ( $serviceID == BookPub::Tracker::Service::BAKERANDTAYLOR() ) {
                if ( $fileName =~ m/agency/i ) {
                    $feedID = 'agency';
                } else {
                    $feedID = 'rrp';
                }
            }
        }

        $sanCriteria{'Feed ID'} = 'missing';

        if ($feedID) {
            $sanCriteria{'Feed ID'} = $feedID;
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID );
        }
    } elsif ( $serviceID == BookPub::Tracker::Service::APPLE() ) {

        # For Apple, we need to use the feed ID for the Apple Apps sales,
        # and the currency code for the rest.
        # Actually, we now have to use the country code to pick the Brazil sales
        # out of the US file.
        if ( $fileFeedID{$fileID} ) {
            $feedID = $fileFeedID{$fileID};
        } else {
            my $file = BookPub::DB::Item::File->Lookup( file_id => $fileID );
            my $fileName = $file->orig_file_name;
            $feedID = substr( $fileName, 0, 8 );
            $fileFeedID{$fileID} = $feedID;
        }

        if ( $feedID =~ /^80127007$/ && $countryCode =~ /^BR$/i && $currencyCode =~ /^BRL$/i ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => $currencyCode, feed_id => $countryCode );
        } elsif ( $feedID == 80127007 ) {
            $sanCriteria{'Feed ID'} = $feedID;
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID );
        } elsif ( $currencyCode eq 'USD' && ( $countryCode eq 'BR' || $countryCode eq 'JP' || $countryCode eq 'MX' ) ) {
            $sanCriteria{'Currency Code'} = $currencyCode;
            $sanCriteria{'Country Code'}  = $countryCode;
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => $currencyCode, feed_id => $countryCode );
        } else {
            $sanCriteria{'Currency Code'} = $currencyCode;
            $sanCriteria{'Country Code'}  = $countryCode;
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => $currencyCode, feed_id => 'ANY' );
        }
    }

    elsif ( $serviceID == BookPub::Tracker::Service::GOOGLE() && $currencyCode eq 'USD' ) {

        # For Google, we need to use the country code from _some_ of the USD sales.
        # This list will probably grow as Wiley adds more SANs.
        # !!! We now have a separate SAN for Google Higher Education, so we need to check the file name first.
        if ( $fileFeedID{$fileID} ) {
            $feedID = $fileFeedID{$fileID};
        } else {
            my $file = BookPub::DB::Item::File->Lookup( file_id => $fileID );
            my $fileName = $file->orig_file_name;
            if ( $fileName =~ /Higher_Ed/i ) {
                $feedID = "HE";
                $fileFeedID{$fileID} = $feedID;
            } elsif ( $fileName =~ /Wholesale/i ) {
                $feedID = "WHOLESALE";
                $fileFeedID{$fileID} = $feedID;
            } else {
                $feedID = "N/A";
            }
        }

        if ( $feedID eq 'HE' ) {
            $sanCriteria{'Feed ID'}       = $feedID;
            $sanCriteria{'Imprint'}       = $sale->r_imprint;
            $sanCriteria{'Currency Code'} = $currencyCode;

            # We need to split the higher education sales up based on imprint.
            if ( $sale->r_imprint =~ /^Wiley Education$/i || $sale->r_imprint =~ /^Wiley Global Education$/i ) {
                $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID . "_WILEY_ED" );
            } elsif ( $sale->r_imprint =~ /^Wiley$/i || $sale->r_imprint =~ /^John Wiley & Sons$/i ) {
                $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID . "_WILEY" );
            }
        } elsif ( $feedID eq 'WHOLESALE' ) {
            $sanCriteria{'Feed ID'}       = $feedID;
            $sanCriteria{'Currency Code'} = $currencyCode;
            if ( $countryCode eq 'IN' ) {
                $sanCriteria{'Country Code'} = $countryCode;
                $clientService =
                  BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => 'USD', feed_id => 'WHOLESALE-IN' );
            } else {

                $clientService =
                  BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => 'USD', feed_id => $feedID );
            }
        } elsif ( $countryCode eq 'BR' || $countryCode eq 'HK' || $countryCode eq 'JP' || $countryCode eq 'MX' || $countryCode eq 'SG' ) {
            $sanCriteria{'Country Code'}  = $countryCode;
            $sanCriteria{'Currency Code'} = $currencyCode;
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => 'USD', feed_id => $countryCode );
        } else {
            $sanCriteria{'Currency Code'} = $currencyCode;
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => 'USD', feed_id => 'ANY' );
        }

    } else {
        $sanCriteria{'Currency Code'} = $currencyCode;

        # First, try to find a client service match using the service id, product type, and currency code.
        $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => $currencyCode );

        # If that didn't work, try just using the service id and product type.
        if ( !$clientService ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID );
        }
    }

    if ($clientService) {
        $clientServiceID = $clientService->client_service_id;
    }

    return ( $clientServiceID, %sanCriteria );
}

sub validateFile {
    my ( $self, $fileID ) = @_;

    # This is the hash we'll send back all of the data in.
    # We'll need to know exactly which fields are missing.
    my %validation;

    # If everything is good, this is all we'll send back.
    $validation{isValid} = 1;

    my $i = 1;
    my %seen;

    # We need to check each sale for a SAN.
    my $sales = BookPub::DB::Item::Sale->GetAllByFileID($fileID);
    # Extract file object
    my $oFile = BookPub::DB::Item::File->Lookup( file_id => $fileID );

    # We also need to check for the purchase order, but we only need to do that once per file.
    my $checkedPurchaseOrder = 0;

    while ( my $sale = $sales->next() ) {
        my ( $customerSAN, %sanCriteria ) = $self->_getClientServiceID($oFile, $sale);

        if ( !$customerSAN ) {
            $validation{isValid} = undef;

            # We need to trim this down to the unique sets of SAN criteria.
            my $seenString = $sale->service_id . "-";
            foreach my $criteria ( sort keys %sanCriteria ) {
                $seenString .= $criteria . "-" . $sanCriteria{$criteria} . "-";
            }

            if ( !$seen{$seenString} ) {
                foreach my $criteria ( sort keys %sanCriteria ) {
                    $validation{"MultipleSANs"}{$i}{$criteria} = $sanCriteria{$criteria};
                }

                $i++;
                $seen{$seenString} = 1;
            }
        }

        if ( $checkedPurchaseOrder == 0 ) {
            my $purchaseOrder = $self->_getPurchaseOrder($oFile, $sale);

            if ( !$purchaseOrder ) {
                $validation{"Reporting Period"} = "missing";
            }

            $checkedPurchaseOrder = 1;
        }

    }

    return %validation;
}

1;

