package BookPub::Sale::Report::Royalty::MacmillanUK;

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

use lib '/app/tools/common/lib';
use Common::Log;
use Common::Client;
use Common::Email;
use Common::DB::ItemCollection;
use Common::DB::Item;
use Common::Util qw(formatFixedPoint);

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::BookProduct;
use BookPub::DB::Item::ClientService;
use BookPub::DB::Item::InputConversionRate;
use BookPub::DB::Item::Period;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::File;
use BookPub::DB::Item::Service;
use BookPub::DB::Item::PriceType;
use BookPub::Tracker::Service;

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

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

my %HEADER_MAIN = (
    _order => [
        qw(doc_type san service filename sales_month isbn title author units list_price list_currency purchase_price purchase_currency discount conversion_rate revenue currency_code gbp_revenue gbp_currency alt_revenue alt_currency country_code product_type report_month price_type purchase_type)
    ],
    doc_type          => 'Doc Type',
    san               => 'Vendor Number',
    service           => 'Vendor Name',
    filename          => 'Filename',
    sales_month       => 'Transaction Month',
    isbn              => 'ISBN/EAN',
    title             => 'Title',
    author            => 'Author',
    units             => 'Demand Qty',
    list_price        => 'List Price',
    list_currency     => 'List Price Currency',
    purchase_price    => 'Purchase Price',
    purchase_currency => 'Purchase Currency',
    discount          => 'Discount-%',
    conversion_rate   => 'Exchange Rate',
    revenue           => 'Revenue Paid',
    currency_code     => 'Currency',
    gbp_revenue       => 'GBP Revenue',
    gbp_currency      => 'GBP Currency',
    alt_revenue       => 'Alternate Price',
    alt_currency      => 'Alternate Currency Code',
    country_code      => 'Country Code',
    product_type      => 'Product Type',
    report_month      => 'Reporting Period',
    price_type        => 'Price Type',
    purchase_type     => 'Purchase Type',
);

my %serviceNames;
my %fileNames;
my %fileServices;
my %fileSANs;
my %altConversions;
my %isbns;
my %titles;
my %authors;

sub new {
    my $class = shift;

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

    $self->_init(@_);

    return $self;
}

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

        $self->{_fileName} = "MacmillanUK_" . $periodName . "_" . $periodID . ".csv";
    }
    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 $csv = Text::CSV->new( { binary => 1 } );

    # 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();
    my $period     = BookPub::DB::Item::Period->Lookup( period_id => $periodID );
    my $periodName = $period->name;

    my $sales = BookPub::DB::Item::Sale->GetFinalSalesByPeriod( $self->periodID() );

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

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

    # print the main header
    my @headers = @HEADER_MAIN{ @{ $HEADER_MAIN{_order} } };
    print $outFileStd join( ",", @headers ), "\n";

    my %rows;

    my $reportMonth;

    while ( my $sale = $sales->next() ) {
        my $fileID    = $sale->file_id;
        my $saleID    = $sale->sale_id;
        my $productID = $sale->product_id;

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

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

        my $conversionRate = $sale->conversion_rate;
        if ( !$conversionRate ) {
            $conversionRate = 1;
        }

        # Storing these away now for the validation at the end
        $totalUnits += $sale->units;
        $totalRevenueUnrounded += $revenue * $conversionRate;

        # Now that we've updated the totals for validation, we need to look for sales with revenue but no units.
        # We'll add one unit to match the revenue.
        if ( $units == 0 && $revenue > 0 ) {
            $units = 1;
        } elsif ( $units == 0 && $revenue < 0 ) {
            $units = -1;
        }

        my $currencyCode = $sale->currency_code;

        my $productType = $self->_determineProductType($sale);

        # Absolute values for everything!
        my $listPrice     = abs( $sale->list_price );
        my $purchasePrice = abs( $sale->r_purchase_price );

        # Need to trim this to two decimal places.
        if ($listPrice) {
            $listPrice = formatFixedPoint( $listPrice, 2 );
        } else {
            $listPrice = 0;
        }

        if ($purchasePrice) {
            $purchasePrice = formatFixedPoint( $purchasePrice, 2 );
        } else {
            $purchasePrice = 0;
        }

        # Use revenue currency if list price currency was not specified.
        my $listCurrency = $sale->r_list_price_currency;
        if ( !$listCurrency && $listPrice ) {
            $listCurrency = $currencyCode;
        }

        # Same thing with purchase price currency
        my $purchaseCurrency = $sale->r_purchase_price_currency;
        if ( !$purchaseCurrency && $purchasePrice ) {
            $purchaseCurrency = $currencyCode;
        }

        my $discount = $self->_determineDiscount($sale);

        my $docType = $self->_getDocType( $units, $revenue, $listPrice, $sale->is_free, $saleID );

        my $countryCode = $sale->country_code;

        my $fileService = $self->_getFileService($fileID);
        my $fileName    = $self->_getFileName($fileID);
        my $salesMonth;

        # Apple sale dates can sometimes span multiple months,
        # so we're just going to grab the date from the file name for them.
        if ( $fileService == 9 ) {
            $salesMonth = $self->_getDateFromAppleFileName($fileName);
            if ( !$salesMonth ) {
                my $error = "Unable to find date in " . $fileName;
                $self->_sendErrorEmail($error);
                die $error . "\n";
            }
        } else {
            $salesMonth = substr( $sale->date_end, 0, 7 );
        }

        # Now, let's get the report date by normalizing the transaction dates
        #
        my $dateBegin = $sale->date_begin;
        my $dateEnd   = $sale->date_end;

        # 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 );
        }

        # trim off the date, leaving YYYY-MM
        $reportMonth = substr( $dateBegin, 0, 7 );

        my $serviceID   = $sale->service_id;
        my $serviceName = $self->_getServiceName($serviceID);

        my ( $customerSAN, %sanCriteria ) = $self->_getSAN($sale);

        #  If we don't have one, time to error out.
        if ( !$customerSAN ) {
            my $error = "No match found in client service table for file " . $fileName;
            $self->_sendErrorEmail($error);
            die "$error\n";
        }

        my ( $altConversionRate, $altCurrencyCode, $altConversionRevenue ) = $self->_getAltConversion($fileID, $currencyCode);

        my $priceType;
        if ($sale->price_type_qualifier_id =~ /^0?6$/) {
            $priceType = "06 = Library";
        }
        else {
            my $priceTypeData = BookPub::DB::Item::PriceType->Lookup( price_type_id => $sale->price_type_id );
            $priceType = $priceTypeData->onix_price_code() . " = " . $priceTypeData->display();
            if ($priceType =~ /tax/i) {
                $priceType =~ s/t/T/;
            }
        }
        my $purchaseType = BookPub::DB::Item::Sale->GetPurchaseTypeString($sale->purchase_type);

        $rows{$docType}{$customerSAN}{$serviceName}{$fileName}{$salesMonth}{$productID}{$productType}{$listPrice}{$listCurrency}
          {$purchasePrice}{$purchaseCurrency}{$discount}{$conversionRate}{$currencyCode}{$altConversionRate}{$altCurrencyCode}
          {$priceType}{$purchaseType}{$countryCode}{units} += $units;

        $rows{$docType}{$customerSAN}{$serviceName}{$fileName}{$salesMonth}{$productID}{$productType}{$listPrice}{$listCurrency}
          {$purchasePrice}{$purchaseCurrency}{$discount}{$conversionRate}{$currencyCode}{$altConversionRate}{$altCurrencyCode}
          {$priceType}{$purchaseType}{$countryCode}{revenue} += $revenue;
    }

    foreach my $docType ( sort keys %rows ) {
        my $docTypeSales = $rows{$docType};

        foreach my $customerSAN ( sort keys %$docTypeSales ) {
            my $customerSANsales = $docTypeSales->{$customerSAN};

            foreach my $serviceName ( sort keys %$customerSANsales ) {
                my $serviceSales = $customerSANsales->{$serviceName};

                foreach my $fileName ( sort keys %$serviceSales ) {
                    my $fileNameSales = $serviceSales->{$fileName};

                    foreach my $salesMonth ( sort keys %$fileNameSales ) {
                        my $monthSales = $fileNameSales->{$salesMonth};

                        foreach my $productID ( sort keys %$monthSales ) {
                            my $productSales = $monthSales->{$productID};

                            foreach my $productType ( sort keys %$productSales ) {
                                my $productTypeSales = $productSales->{$productType};

                                foreach my $listPrice ( sort keys %$productTypeSales ) {
                                    my $listPriceSales = $productTypeSales->{$listPrice};

                                    foreach my $listCurrency ( sort keys %$listPriceSales ) {
                                        my $listCurrencySales = $listPriceSales->{$listCurrency};

                                        foreach my $purchasePrice ( sort keys %$listCurrencySales ) {
                                            my $purchasePriceSales = $listCurrencySales->{$purchasePrice};

                                            foreach my $purchaseCurrency ( sort keys %$purchasePriceSales ) {
                                                my $purchaseCurrencySales = $purchasePriceSales->{$purchaseCurrency};

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

                                                    foreach my $conversionRate ( sort keys %$discountSales ) {
                                                        my $conversionSales = $discountSales->{$conversionRate};

                                                        foreach my $currencyCode ( sort keys %$conversionSales ) {
                                                            my $currencySales = $conversionSales->{$currencyCode};

                                                            foreach my $altConversionRate ( sort keys %$currencySales ) {
                                                                my $altConversionSales = $currencySales->{$altConversionRate};

                                                                foreach my $altCurrencyCode ( sort keys %$altConversionSales ) {
                                                                    my $altCurrencySales = $altConversionSales->{$altCurrencyCode};

                                                                    foreach my $priceType ( sort keys %$altCurrencySales ) {
                                                                        my $priceTypeSales = $altCurrencySales->{$priceType};

                                                                        foreach my $purchaseType ( sort keys %$priceTypeSales ) {
                                                                            my $purchaseTypeSales = $priceTypeSales->{$purchaseType};

                                                                            foreach my $countryCode ( sort keys %$purchaseTypeSales ) {
                                                                                my $row = $purchaseTypeSales->{$countryCode};

                                                                                    my ( $isbn, $title, $author ) = $self->_getProductData($productID);

                                                                                    my %tmplVars;

                                                                                    $tmplVars{doc_type}          = $docType;
                                                                                    $tmplVars{san}               = $customerSAN;
                                                                                    $tmplVars{service}           = $serviceName;
                                                                                    $tmplVars{filename}          = $fileName;
                                                                                    $tmplVars{sales_month}       = $salesMonth;
                                                                                    $tmplVars{isbn}              = $isbn;
                                                                                    $tmplVars{title}             = $title;
                                                                                    $tmplVars{author}            = $author;
                                                                                    $tmplVars{units}             = $row->{units};
                                                                                    $tmplVars{list_price}        = $listPrice;
                                                                                    $tmplVars{list_currency}     = $listCurrency;
                                                                                    $tmplVars{purchase_price}    = $purchasePrice;
                                                                                    $tmplVars{purchase_currency} = $purchaseCurrency;
                                                                                    $tmplVars{discount}          = $discount;
                                                                                    $tmplVars{conversion_rate} = formatFixedPoint( $conversionRate, 4 );
                                                                                    $tmplVars{revenue}         = formatFixedPoint( $row->{revenue}, 2 );
                                                                                    $tmplVars{currency_code}   = $currencyCode;
                                                                                    $tmplVars{gbp_revenue}     = formatFixedPoint( $row->{revenue} * $conversionRate, 2 );
                                                                                    $tmplVars{gbp_currency} = "GBP";

                                                                                    if ($altCurrencyCode) {
                                                                                        $tmplVars{alt_revenue}  = formatFixedPoint( $row->{revenue} * $altConversionRate, 2 );
                                                                                        $tmplVars{alt_currency} = $altCurrencyCode;
                                                                                    }
                                                                                    $tmplVars{price_type}     = $priceType;
                                                                                    $tmplVars{purchase_type}  = $purchaseType;
                                                                                    $tmplVars{country_code} = $countryCode;
                                                                                    $tmplVars{product_type} = $productType;
                                                                                    $tmplVars{report_month} = $reportMonth;

                                                                                    my @detailRow = @tmplVars{ @{ $HEADER_MAIN{_order} } };
                                                                                    $csv->print( $outFileStd, \@detailRow );
                                                                                    print $outFileStd "\n";
                                                                            }
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    close($outFileStd);

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

    return (%returnValues);
}

sub _getServiceName {
    my $self      = shift;
    my $serviceID = shift;

    if ( !$serviceNames{$serviceID} ) {
        my $service = BookPub::DB::Item::Service->Lookup( service_id => $serviceID );
        $serviceNames{$serviceID} = $service->service_name;
    }

    return $serviceNames{$serviceID};
}

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

    if ( !exists $altConversions{$fileID} ) {
        my $inputRates = BookPub::DB::Item::InputConversionRate->GetAllByFileID($fileID);
        if ( $inputRates->size ) {
            while ( my $item = $inputRates->next() ) {
                if ( $item->is_alternate ) {
                    my $parentCurrency = $item->parent_currency;
                    $altConversions{$fileID}{$parentCurrency}{currencyCode}      = $item->currency;
                    $altConversions{$fileID}{$parentCurrency}{conversionRate}    = $item->conversion_rate;
                    $altConversions{$fileID}{$parentCurrency}{conversionRevenue} = $item->revenue;
                }
            }
        }
    }

    if ( exists $altConversions{$fileID} && exists $altConversions{$fileID}{$currencyCode} ) {
        return (
            $altConversions{$fileID}{$currencyCode}{conversionRate},
            $altConversions{$fileID}{$currencyCode}{currencyCode},
            $altConversions{$fileID}{$currencyCode}{conversionRevenue}
        );
    }

    # for this currency the alternative currency rate was not set
    return (0, '', 0);
}

sub _getFileName {
    my $self   = shift;
    my $fileID = shift;

    if ( !$fileNames{$fileID} ) {
        my $file = BookPub::DB::Item::File->Lookup( file_id => $fileID );
        $fileNames{$fileID} = $file->orig_file_name;
    }

    return $fileNames{$fileID};
}

sub _getFileService {
    my $self   = shift;
    my $fileID = shift;

    if ( !$fileServices{$fileID} ) {
        my $file = BookPub::DB::Item::File->Lookup( file_id => $fileID );
        $fileServices{$fileID} = $file->service_id;
    }

    return $fileServices{$fileID};
}

sub _getSAN {
    my $self = shift;
    my $sale = shift;

    my $serviceID   = $sale->service_id;
    my $fileID      = $sale->file_id;
    my $fileName    = $self->_getFileName( $fileID );
    my $productType = $sale->product_type;

    my $clientService;
    my $clientServiceID;

    # 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;

    # For Bolinda Digital eAudio products we use specific SAN (RSD-2149)
    if ( $serviceID == BookPub::Tracker::Service::BOLINDA_DIGITAL() ){
        if ( $sale->product_type eq BookPub::DB::Item::Sale::kProductTypeAudioBook ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, product_type => 'AUDB' );
        }
        else {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, product_type => 'EBOK' );
        }
        $clientServiceID = $clientService->client_service_id if $clientService;
        $sanCriteria{'Product Type'} = $sale->product_type;
    }
    elsif ( $serviceID == BookPub::Tracker::Service::ONECLICKDIGITAL() ) {
        $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, product_type =>  $sale->product_type );
        $clientServiceID = $clientService->client_service_id;
    }
    # For Audible, the SAN is assigned at the sale level.
    elsif ( $serviceID == BookPub::Tracker::Service::AUDIBLE() ) {
        if ( $sale->country_code eq 'GB' ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'GB' );
        } else {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY' );
        }
        $clientServiceID = $clientService->client_service_id;
        $sanCriteria{'Country Code'} = $sale->country_code;
    }
    elsif ( $serviceID == BookPub::Tracker::Service::SPOTIFY() ) {
        $sanCriteria{'Currency Code'} = "missing";
        my $currencyCode = $sale->currency_code;
        $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, currency_code => $currencyCode );
        if ( $clientService ) {
            $clientServiceID = $clientService->client_service_id;
            $sanCriteria{'Currency Code'} = $clientService->currency_code;
        }
    }
    elsif ( $serviceID == BookPub::Tracker::Service::AMAZON() && $fileName =~ /^MTCEU_DigitalEBooksPaymentReport_[0-9-]+_GB\./i ) {
        my $feedID = "missing";
        if ( !$sale->r_channel || $sale->r_channel =~ /^(?:(?:Rewards )?Vendor Funded Sales Promotion(?: ?; Bulk)?|Bulk)$/i ) {
            $feedID = "MTCEU GB";
        } elsif ( $sale->r_channel =~ /^Lending( - Promo Period)?$/i ) {
            $feedID = "MTCEU GB 05-06";
        }

        $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID );
        $clientServiceID = $clientService->client_service_id if $clientService;
        $sanCriteria{'Feed ID'} = $feedID;
    } elsif ( $serviceID == BookPub::Tracker::Service::PAN_AUSTRALIA() ) {
        if ( $productType eq 'AUDB' ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, product_type => $productType );
        } else {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'NOT_AUDB' );
        }
        $clientServiceID = $clientService->client_service_id if $clientService;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::ZEBRALUTION() ) {
        if ( $sale->r_comments && $sale->r_comments =~ /^\d+$/ ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $sale->r_comments );
            $clientServiceID = $clientService->client_service_id;
        }
        $sanCriteria{'Feed ID'} = $sale->r_comments;
    } elsif ( $serviceID == BookPub::Tracker::Service::VIVLIO() ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, product_type => $sale->product_type );
            $clientServiceID = $clientService->client_service_id if $clientService;
    } else {

        # For every other service (so far), the SAN is assigned at the file level.
        if ( !$fileSANs{$fileID} ) {
            my $file = BookPub::DB::Item::File->Lookup( file_id => $fileID );
            my $fileName = $file->orig_file_name;

            # Most services has only 1 SAN.
            # But Amazon and Apple have multiple, so we have to look at the file name to find the right one.
            if ( $serviceID == BookPub::Tracker::Service::AMAZON() ) {
                $sanCriteria{'Feed ID'} = "missing";
                # eBooksPrimeReadingReport-MTCEU-2023-08_GB || MTCEU_eBooks Prime Reading Report_20230731-20230831_GB
                if ( $fileName =~ /-(\w{5})-\d{4}-\d{2}_(\w{2})\./ || $fileName =~ /^(\w\wCEU).+?prime.+?_\d{8}-\d{8}_(\w{2})\./i ) {
                    my $feedID = $1 . " $2 PRIME";
                    $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID );
                    $sanCriteria{'Feed ID'} = $feedID;
                }
                elsif ( $fileName =~ /^(\w{5})[ _](?!digi)(\w\w)/i || $fileName =~ /^(\w{5})[ _]digi.+?[ _](\w\w)(?:[ _]\w{3})?\./i) {
                    my $feedID = $1 . " " . $2;
                    $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID );
                    $sanCriteria{'Feed ID'} = $feedID;
                }
            } elsif ( $serviceID == BookPub::Tracker::Service::APPLE() ) {
                $sanCriteria{'Country Code'} = "missing";
                if ( $fileName =~ /[\s_](\w{2})\./ ) {
                    my $countryCode = $1;
                    $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $countryCode );
                    $sanCriteria{'Country Code'} = $countryCode;
                }
            } elsif ( $serviceID == BookPub::Tracker::Service::EBOOK() ) {
                $sanCriteria{'Currency Code'} = "missing";
                if ( $fileName =~ /-([A-Z]{3})-\d{4}-\d{2}/ ) {
                    my $currencyCode = $1;
                    $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $currencyCode );
                    $sanCriteria{'Currency Code'} = $currencyCode;
                }

            } elsif ( $serviceID == BookPub::Tracker::Service::IN_AUDIO() ) {
                if ( $fileName =~ /^Macmillan Publishers International \(?(\d{4})-\d{1,2} Royalty\)?/i ) {
                    $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY' );
                }

                # This is the default for everyone else.  Just look it up with the service id.
            } else {
                $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID );
                $sanCriteria{'Service'} = $serviceID;
            }

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

    return ( $clientServiceID, %sanCriteria );
}

sub _getDocType {
    my $self = shift;
    my ( $units, $revenue, $listPrice, $isFree, $saleID ) = @_;

    my $docType;
    if ( $units < 0 ) {
        $docType = 'CEBO';
    } elsif ( $units > 0 && ( $revenue == 0 || $isFree eq 'Y' ) ) {
        $docType = 'GEBO';
    } elsif ( $units > 0 && ( $revenue > 0 || $listPrice > 0 ) ) {
        $docType = 'IEBO';
    } else {
        my $error = "No doctype set for sale: " . $saleID;
        $self->_sendErrorEmail($error);
        die "No doctype set for sale: " . $saleID . "\n";
    }

    return $docType;
}

sub _getProductData {
    my $self      = shift;
    my $productID = shift;

    my ( $isbn, $title, $author );

    if ( $isbns{$productID} ) {
        $isbn   = $isbns{$productID};
        $title  = $titles{$productID};
        $author = $authors{$productID};
    } else {
        my $bookProduct = BookPub::DB::Item::BookProduct->Lookup( product_id => $productID );

        $isbn = $bookProduct->isbn13;
        if ( !$isbn ) {
            $isbn = $bookProduct->isbn10;
        }
        $isbns{$productID} = $isbn;

        # Since we have the product DB item, let's go ahead and grab the other data we need.
        my $book = BookPub::DB::Item::Book->Lookup( book_id => $bookProduct->book_id );
        $title = $book->title;
        $titles{$productID} = $title;

        $author = BookPub::DB::Item::BookContributor->GetAuthorNamesByBookID( $bookProduct->book_id );

        # We just want the first one.
        my @author = split( ";", $author );
        $author = $author[0];
        $authors{$productID} = $author;
    }

    return ( $isbn, $title, $author );
}

sub _determineDiscount {
    my $self = shift;
    my $sale = shift;

    my $discount;

    # If it's free, then we can just hard code the discount rate.
    if ( $sale->is_free eq 'Y' || $sale->revenue == 0 ) {
        $discount = 100;
    } elsif ( $sale->r_discount ) {

        # Not using net discount here, just use whatever was in the sales file.
        $discount = formatFixedPoint( $sale->r_discount * 100, 2 );
    }

    return $discount;
}

sub _determineProductType {
    my $self = shift;
    my $sale = shift;

    my $productType;

    if ( $sale->purchase_type eq BookPub::DB::Item::Sale::kPurchaseTypePrintOnDemand ) {
        $productType = "POD";
    } elsif ( $sale->product_type eq BookPub::DB::Item::Sale::kProductTypeEBook ) {
        $productType = "eBook";
    } elsif ( $sale->product_type eq BookPub::DB::Item::Sale::kProductTypeAudioBook ) {
        $productType = "eAudio";
    } else {
        my $error = "Unknown product type " . $sale->product_type . " for sale id " . $sale->sale_id;
        $self->_sendErrorEmail($error);
        die "Unknown product type " . $sale->product_type . " for sale id " . $sale->sale_id . "\n";
    }

    return $productType;
}

sub _getDateFromAppleFileName {
    my ( $self, $fileName ) = @_;
    my $salesMonth;
    if ( $fileName =~ /[\s_](\d{2})(\d{2})[\s_]\w{2}/ ) {
        $salesMonth = "20" . $2 . "-" . $1;
    }
    return $salesMonth;
}

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 $fileService = $self->_getFileService($fileID);
    my $fileName    = $self->_getFileName($fileID);

    # Apple sale dates can sometimes span multiple months,
    # so we're just going to grab the date from the file name for them.
    if ( $fileService == 9 ) {
        my $salesMonth = $self->_getDateFromAppleFileName($fileName);
        if ( !$salesMonth ) {
            $validation{isValid} = undef;
            $validation{"Reporting Period"} = "missing";
        }
    }

    # Now we need to check for the SAN, which is assigned at the sale level for some services
    # so me way as well just loop on that.
    my $sales = BookPub::DB::Item::Sale->GetAllByFileID($fileID);

    my $i = 1;
    my %seen;

    while ( my $sale = $sales->next() ) {
        my ( $customerSAN, %sanCriteria ) = $self->_getSAN($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;
            }
        }
    }

    return %validation;
}

1;

