package BookPub::Sale::Report::Royalty::MacmillanAU;

use strict;
use Template;
use Data::Dumper;
use Text::CSV;

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::Period;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::File;
use BookPub::DB::Item::Imprint;
use BookPub::DB::Item::Publisher;
use BookPub::DB::Item::Service;
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/';
use constant kZipProg    => '/usr/bin/zip';

sub new {
    my $class = shift;

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

    $self->_init(@_);

    return $self;
}

sub _baseDir {
    my ($self) = @_;

    if ( !$self->{_baseDir} ) {
        my $config = BookPub::Config->new();
        my $client = Common::Client::Current();

        $self->{_baseDir} = $config->get('sale_report_dir');
        $self->{_baseDir} .= '/' . $client->ClientNameClean();
        $self->{_baseDir} .= '/' . $self->{_periodID};
    }
    return $self->{_baseDir};
}


sub filePath {
    # stub
}

sub zipFileName {
    my ($self) = @_;
    if ( !$self->{_zipFileName} ) {
        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();
        my $endDate = $self->{_periodEndDate};
        if (!$endDate) {
            $endDate = $period->end_date;
            $endDate =~ s/-//g;
            $self->{_periodEndDate} = $endDate;
        }
        $self->{_zipFileName} = $endDate . "_MMAU_RoyaltyReport_" . $periodRef . ".zip";
    }
    return $self->{_zipFileName};
}

sub zipFilePath {
    my ($self) = @_;
    if ( !$self->{_zipFilePath} ) {
        my $baseDir = $self->_baseDir();
        $self->{_zipFilePath} = $baseDir . '/' . $self->zipFileName();
    }
    return $self->{_zipFilePath};
}

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;

    # 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 $sales = BookPub::DB::Item::Sale->GetFinalSalesByPeriod($periodID);

    my $period     = BookPub::DB::Item::Period->Lookup( period_id => $periodID );
    my $outputDate = $period->end_date;
    my $year       = substr( $outputDate, 2, 2 );
    my $month      = substr( $outputDate, 5, 2 );

    $outputDate = $year . $month;

    my %rows;

    # Gonna cache some things so that we don't have to look them up in the DB every time.
    my %titles;
    my %fileNames;
    my %imprintNames;
    my %publisherNames;
    my %serviceNames;

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

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

        # Aldi is the only service we use these two for,
        # so leave them blank for everyone else.
        my $rComments;
        my $rPurchasePrice;

        if ($serviceName =~ /^Aldi$/i) {
            $rComments = formatFixedPoint( $sale->r_comments, 2 );
            $rPurchasePrice = abs( formatFixedPoint( $sale->r_purchase_price, 2 ) );
        }

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

        if ($conversionRate) {
            $revenue *= $conversionRate;
        }

        $totalUnits += $sale->units;
        $totalRevenueUnrounded += $revenue;

        my $productID = $sale->product_id;

        my $bookProduct = BookPub::DB::Item::BookProduct->Lookup( product_id => $productID );
        my $isbn13      = $bookProduct->isbn13;
        my $isbn10      = $bookProduct->isbn10;

        # get the string value of the product type
        my $productType = $self->_getProductType($sale);

        # determine product's imprint name
        my $imprintName;
        my $imprintID = $bookProduct->imprint_id;
        if ( $imprintNames{$imprintID} ) {
            $imprintName = $imprintNames{$imprintID};
        } else {
            my $imprint = BookPub::DB::Item::Imprint->Lookup( imprint_id => $imprintID );
            $imprintName = $imprint->name;
            $imprintNames{$imprintID} = $imprintName;
        }

        my $bucketName = $self->_getBucketNameForSale($sale, $productType, $imprintName);

        # Sales in the "Macquarie" bucket need to display the product type differently.
        if ($bucketName eq 'Macquarie') {
            if ($sale->service_id == BookPub::Tracker::Service::APPLE()) {
                $productType = "Book 'app' for iOS";
	    } elsif ($sale->service_id == BookPub::Tracker::Service::GOOGLE_PLAY()) {
                $productType = "Book 'app' for Android";
            } elsif ($sale->r_comments =~ /data_licen[c|s]e/i) {
                $productType = "Digital (delivered electronically)";
            } else {
                $productType = "Digital online";
            }
        }

        my $transactionType;
        if ( $units == 0 && $revenue == 0 ) {
            next;
        } elsif ( $units == 0 && $revenue != 0 ) {
            $transactionType = 'Sale';
        } elsif ( $units < 0 ) {
            $transactionType = 'Return';
        } elsif ( $units > 0 && ( $revenue == 0 || $sale->is_free eq 'Y' ) ) {
            $transactionType = 'Gratis';
        } elsif ( $units > 0 && $revenue > 0 ) {
            $transactionType = 'Sale';
        } else {
            my $error = "No transaction type set for sale: " . $sale->sale_id;
            $self->_sendErrorEmail($error);
            die $error . "\n";
        }

        my $countryCode = $sale->country_code;
        my $fileID      = $sale->file_id;

        # We need the file name to figure out the feed name for some of the services.
        my $fileName;

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

        my ( $clientService, %sanCriteria ) = $self->_getClientService( $sale, $fileName );

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

        my $customerSAN = $clientService->client_service_id;

        my $publisherName;
        my $publisherID = $bookProduct->publisher_id;
        if ( $publisherNames{$publisherID} ) {
            $publisherName = $publisherNames{$publisherID};
        }
        else {
            my $publisher = BookPub::DB::Item::Publisher->Lookup( publisher_id => $publisherID );
            $publisherName = $publisher->name;
            $publisherNames{$publisherID} = $publisherName;
        }

        my $saleDate      = $sale->date_end;
        my $saleDateYear  = substr( $saleDate, 2, 2 );
        my $saleDateMonth = substr( $saleDate, 5, 2 );
        $saleDate = $saleDateYear . $saleDateMonth;

        my $listPriceCurrency = $sale->r_list_price_currency;
        if ( !$listPriceCurrency ) {
            $listPriceCurrency = $sale->currency_code;
        }

        my $priceTypeName = $self->_getPriceTypeName($sale);
        my $purchaseType  = $self->_getPurchaseType($sale);

        my $title;
        my $bookID = $bookProduct->book_id;
        if ( $titles{$bookID} ) {
            $title = $titles{$bookID};
        } else {
            my $book = BookPub::DB::Item::Book->Lookup( book_id => $bookID );
            $title = $book->title;
            my $subtitle = $book->subtitle;
            if ($subtitle) {
                $title .= ": " . $subtitle;
            }
            $titles{$bookID} = $title;
        }

        my $listPrice     = abs( formatFixedPoint( $sale->list_price,       2 ) );
        my $purchasePrice = abs( formatFixedPoint( $sale->r_purchase_price, 2 ) );
        my $discount = formatFixedPoint( 100 * $sale->discount, 2 );
        $conversionRate = formatFixedPoint( $sale->conversion_rate, 5 );

        $rows{$bucketName}{$customerSAN}{$isbn13}{$isbn10}{$transactionType}{$countryCode}{$serviceName}{$saleDate}{$priceTypeName}{$purchaseType}{$productType}{$title}{$imprintName}{$publisherName}{$listPriceCurrency}{$listPrice}{$purchasePrice}{$discount}{$conversionRate}{units} += $units;
        $rows{$bucketName}{$customerSAN}{$isbn13}{$isbn10}{$transactionType}{$countryCode}{$serviceName}{$saleDate}{$priceTypeName}{$purchaseType}{$productType}{$title}{$imprintName}{$publisherName}{$listPriceCurrency}{$listPrice}{$purchasePrice}{$discount}{$conversionRate}{revenue} += $revenue;
    }

    # First, let's make sure it has a home.
    my $baseDir = $self->_baseDir();

    # Making sure the group and owner are set to apache,
    # even when this is manually kicked off.
    my $uid = getpwnam "apache";
    my $gid = getgrnam "apache";

    # We have to check at a couple of levels because of the way we store these reports.
    my @baseDirPath = split( '/', $baseDir );
    pop(@baseDirPath);
    my $superBaseDir = join( '/', @baseDirPath );

    unless ( -e $superBaseDir or mkdir( $superBaseDir, 0755 ) ) {
        die "Unable to create $superBaseDir\n";
    }
    chown $uid, $gid, $superBaseDir;

    unless ( -e $baseDir or mkdir( $baseDir, 0755 ) ) {
        die "Unable to create $baseDir\n";
    }

    chown $uid, $gid, $baseDir;


    # open output files for sales that have been split into buckets
    $self->_openOutputFiles($uid, $gid);

    my $csv = Text::CSV->new( { binary => 1 } );
    foreach my $bucketName ( keys %{ $self->{_output_sales_bucket} } ) {
        $self->_fillInFile($outputDate, $csv, $bucketName, \%rows);
    }

    $self->_closeOutputFiles();

    # zip files to send them to ftp
    $self->zipFiles();

    # 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 zipFiles {
    my $self = shift;

    my $zipFile = $self->zipFilePath();
    my $baseDir = $self->_baseDir();

    my $uid = getpwnam "apache";
    my $gid = getgrnam "apache";

    if ( -e $zipFile ) {
        Common::Log::Print("$zipFile exists, removing it...");
        die "\nCan't remove $zipFile\n" unless ( unlink $zipFile );
    }

    chdir($baseDir) || die "ERROR - unable to change directories to $baseDir: $!";

    my $zipCmd = kZipProg . ' -r';
    my $zipFileName = $self->zipFileName();
    system( $zipCmd . " " . $zipFileName . " ." );
    chown $uid, $gid, $zipFile;

    Common::Log::Print("Zip file created: $zipFile");

    return 1;
}

sub _getBucketNameForSale {
    my ( $self, $oSale, $productType, $imprintName ) = @_;

    if ( $imprintName && $imprintName eq 'Macquarie' ) {
        if ( $productType && $productType eq 'eBook' ) {
            my $matchedMacquarieService  = $oSale->service_id == BookPub::Tracker::Service::MACQUARIE();
            my $matchedAppleService      = $oSale->service_id == BookPub::Tracker::Service::APPLE();
	    my $matchedGoogleService     = $oSale->service_id == BookPub::Tracker::Service::GOOGLE_PLAY();
            my $matchedFormatType        = $oSale->format_type eq BookPub::DB::Item::Sale::kFormatTypeAppleMultimedia();
            if ( $matchedMacquarieService || $matchedGoogleService || ($matchedAppleService && $matchedFormatType) ) {
                $self->{_output_sales_bucket}{Macquarie}{sales}++;
                return 'Macquarie';
            }
        }
    }

    if ( $productType ) {
        if ( $productType eq 'eBook' ) {
            $self->{_output_sales_bucket}{Ebook}{sales}++;
            return 'Ebook';
        } elsif ( $productType eq 'Audio Book' ) {
            $self->{_output_sales_bucket}{Audio}{sales}++;
            return 'Audio';
        } elsif ( $productType eq 'Physical Book' ) {
            if ($oSale->service_id == BookPub::Tracker::Service::ALDI()) {
                $self->{_output_sales_bucket}{Print}{sales}++;
                return 'Print';
            } else {
                $self->{_output_sales_bucket}{POD}{sales}++;
                return 'POD';
            }
        }
    }

    return 'unspecified';
}

sub _openOutputFiles {
    my ( $self, $uid, $gid ) = @_;

    my $periodEndDate = $self->_getPeriodEndDate();

    while ( my($bucketName, $hData) = each %{ $self->{_output_sales_bucket} } ) {
        my $fileName = "$periodEndDate\_$bucketName\_Daily_Sales_AU_By_Publisher.csv";
        my $filePath = $self->_baseDir() . '/' . $fileName;
        $hData->{file_name} = $fileName;
        $hData->{file_path} = $filePath;
        unless ( open( $hData->{file_handler}, '>:utf8', $filePath ) ) {
            die "Can't open output file ($hData->{file_path}) - $!";
        }
        chown $uid, $gid, $hData->{file_path};
    }

    return 1;
}

sub _closeOutputFiles {
    my $self = shift;

    while ( my($bucketName, $hData) = each %{ $self->{_output_sales_bucket} } ) {
        close( $hData->{file_handler} );
    }

    return 1;
}

sub _getCreatedFileNames {
    my $self = shift;

    my @fileNames;
    while ( my($bucketName, $hData) = each %{ $self->{_output_sales_bucket} } ) {
        push @fileNames,
            $self->{_output_sales_bucket}{$bucketName}{file_name}
    }

    return @fileNames;
}

sub _getFileHandlerByBucketName {
    my ($self, $bucketName) = @_;

    if ( exists $self->{_output_sales_bucket}{$bucketName} ) {
        return $self->{_output_sales_bucket}{$bucketName}{file_handler};
    }

    return;
}

sub _getFilePathByBucketName {
    my ($self, $bucketName) = @_;

    if ( exists $self->{_output_sales_bucket}{$bucketName} ) {
        return $self->{_output_sales_bucket}{$bucketName}{file_path};
    }

    return;
}

sub _getPeriodEndDate {
    my $self = shift;

    my $endDate = $self->{_periodEndDate};

    if (!$endDate) {
        my $period = BookPub::DB::Item::Period->Lookup( period_id => $self->{_periodID} );
        $endDate = $period->end_date;
        $endDate =~ s/-//g;
        $self->{_periodEndDate} = $endDate;
    }

    return $endDate;
}

sub _fillInFile {
    my ($self, $outputDate, $csv, $bucketName, $hRowsData) = @_;

    my $fhOut = $self->_getFileHandlerByBucketName($bucketName);
    my $filePath = $self->_getFilePathByBucketName($bucketName);

    my @headers = (
        "CUSTOMER_NUM", "ISBN",             "EAN",       "ORDER_DATE", "TRANSACTION_UNITS", "TRANSACTION_VALUE",
        "SALES_TYPE",   "TRANSACTION_TYPE", "PUB_VALUE", "DISCOUNT",   "COUNTRY_OF_SALE",   "COST_VALUE",
        "SALES_PERIOD",
        "Retailer", "Transaction_Period", "List_Price", "List_Price_Currency", "Purchase_Price", "Revenue_Currency",
        "Discount", "Conversion_Rate",    "Price_Type", "Purchase_Type",       "Product_Type",   "Title",
        "Imprint",  "Publisher"
    );

    print $fhOut join( ",", @headers ), "\n";

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

        foreach my $isbn13 ( sort keys %$customerSANsales ) {
            my $isbn13Sales = $customerSANsales->{$isbn13};

            foreach my $isbn10 ( sort keys %$isbn13Sales ) {
                my $isbn10Sales = $isbn13Sales->{$isbn10};

                foreach my $transactionType ( sort keys %$isbn10Sales ) {
                    my $transactionTypeSales = $isbn10Sales->{$transactionType};

                    foreach my $countryCode ( sort keys %$transactionTypeSales ) {
                        my $countrySales = $transactionTypeSales->{$countryCode};

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

                            foreach my $saleDate ( sort keys %$serviceSales ) {
                                my $saleDateSales = $serviceSales->{$saleDate};

                                foreach my $priceTypeName ( sort keys %$saleDateSales ) {
                                    my $priceTypeSales = $saleDateSales->{$priceTypeName};

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

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

                                            foreach my $title ( sort keys %$productTypeSales ) {
                                                my $titleSales = $productTypeSales->{$title};

                                                foreach my $imprintName ( sort keys %$titleSales ) {
                                                    my $imprintSales = $titleSales->{$imprintName};

                                                    foreach my $publisherName ( sort keys %$imprintSales ) {
                                                        my $publisherSales = $imprintSales->{$publisherName};

                                                        foreach my $listPriceCurrency ( sort keys %$publisherSales ) {
                                                            my $lpCurrencySales = $publisherSales->{$listPriceCurrency};

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

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

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

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

                                                                            my $units   = $row->{units};
                                                                            my $revenue = $row->{revenue};

                                                                            # Let's trim this to two decimal places now.
                                                                            $revenue = formatFixedPoint( $revenue, 2 );

                                                                            my $saleType = $transactionType eq 'Return' ? 'Return' : 'Normal Sale';

                                                                            my @outputRow = (
                                                                                $customerSAN,          # A
                                                                                $isbn10,               # B
                                                                                $isbn13,               # C
                                                                                $outputDate,           # D
                                                                                $units,                # E
                                                                                $revenue,              # F
                                                                                $saleType,             # G
                                                                                $transactionType,      # H
                                                                                $revenue,              # I
                                                                                '0',                   # J
                                                                                $countryCode,          # K
                                                                                '0',                   # L
                                                                                $outputDate,           # M
                                                                                $serviceName,          # N
                                                                                $saleDate,             # O
                                                                                $listPrice,            # P
                                                                                $listPriceCurrency,    # Q
                                                                                $purchasePrice,        # R
                                                                                'AUD',                 # S
                                                                                $discount,             # T
                                                                                $conversionRate,       # U
                                                                                $priceTypeName,        # V
                                                                                $purchaseType,         # W
                                                                                $productType,          # X
                                                                                $title,                # Y
                                                                                $imprintName,          # Z
                                                                                $publisherName         # AA
                                                                            );

                                                                            $csv->print( $fhOut, \@outputRow );
                                                                            print $fhOut "\n";
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    Common::Log::Print( "Report created: $filePath" );

    return 1;
}


sub _getClientService {
    my ( $self, $sale, $fileName ) = @_;

    my $feedID;
    my $clientService;
    my $serviceID    = $sale->service_id;
    my $countryCode  = $sale->country_code;
    my $productType  = $sale->product_type;
    my $currencyCode = $sale->currency_code;

    # We need to send a back the list of fields and their values that we used to look for the SAN for a service.
    my %sanCriteria;

    if ( $serviceID == BookPub::Tracker::Service::AMAZON() ) {
        $sanCriteria{'Feed ID'} = $feedID;

        # We need to look these up by region
        if ( $fileName =~ /^MOZEU US/ || $fileName =~ /^MOZEU[ _]digi.+?[ _]US(?:[ _]\w{3})?\./i) {
            my $region = $self->_getRegion($countryCode);
            $feedID = "MOZEU US - " . $region;
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );

            $sanCriteria{'Feed ID'} = $feedID;
        } elsif ( $fileName =~ /^(\w{5}) (?!digi)(\w{2})/i || $fileName =~ /^(\w{5})[ _]digi.+?[ _](\w\w)(?:[ _]\w{3})?\./i) {
            $feedID = $1 . " " . $2;
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );

            $sanCriteria{'Feed ID'} = $feedID;
        } elsif ( $fileName =~ /^MZIEU\.ISORP/ ) {

            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => "MZIEU.ISORP", product_type => $productType );

            $sanCriteria{'Feed ID'} = "MZIEU.ISORP";
        }

        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::APPLE() ) {
        $sanCriteria{'Feed ID'} = $feedID;

        if ( $fileName =~ /^(85028664)/ )    # Macquarie
        {
            $feedID = $1;

            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );

            $sanCriteria{'Feed ID'} = $feedID;
        } elsif ( $fileName =~ /^(86056965)/ )    # Momentum
        {
            $feedID = $1;

            # There are potentially some Macquarie Apple App sales in this Momentum file, so
            # lets check for those...
            my $vendorID = $sale->r_client_product_id;

            # vendorID is a varchar 30 and values inserted to it are truncated, so we'RE JUST comparing the first
            # 30 characters until I can update the schema
            if (   $vendorID =~ /au.com.macquariedictionary.com/i
                || $vendorID =~ /au.com.macquariedictionary.con/i
                || $vendorID =~ /10006/i
                || $vendorID =~ /au.com.macquariedictionary.sla/i
                || $vendorID =~ /au.com.macmillan.dict/i
                || $vendorID =~ /au.com.macmillan.pocket.dict/i
                || $vendorID =~ /au.com.macmillan.primary.dict/i
                || $vendorID =~ /au.com.macmillan.student.dict/i ) {

                $feedID = '85028664';    # feedID for Macquarie
            }

            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );

            $sanCriteria{'Feed ID'} = $feedID;
        } elsif ( $fileName =~ / \d{4} (\w{2})/ )    # other
        {
            $feedID = $1;

            # For EU, we need to look at the sale country code and treat DE differently.
            if ( $feedID eq 'EU' && $countryCode eq 'DE' ) {
                $feedID = 'DE';
            }

            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );

            $sanCriteria{'Feed ID'} = $feedID;
        }
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::BAKERANDTAYLOR() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $countryCode, product_type => $productType );
        $sanCriteria{'Country Code'} = $countryCode;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::BARNES_NOBLE() ) {
        my $region = $self->_getRegion($countryCode);
        if ($region) {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $region, product_type => $productType );
        }
        $sanCriteria{'Country Code'} = $countryCode;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::BOLINDA_DIGITAL() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::BOOKTOPIA() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::CATCH_GROUP() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::COPIA() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::DIRECT_EBOOKS() ) {
        my $region = $self->_getRegion($countryCode);
        if ($region) {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $region, product_type => $productType );
        }
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::EBOOK() ) {
        my $region = $self->_getRegion($countryCode);
        if ($region) {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $region, product_type => $productType );
        }
        $sanCriteria{'Country Code'} = $countryCode;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::FEEDBOOKS() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Country Code'} = $countryCode;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::GOOGLE() ) {
        $sanCriteria{'Feed ID'} = $feedID;
        if ( $productType eq 'AUDB' ) {
            if ( $countryCode eq "NZ" ) {
                $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'NEZ', product_type => $productType );
            }
            else {
                $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'AUS', product_type => $productType );
            }
            $sanCriteria{'Feed ID'} = $countryCode;
        }
        # We need to look these up by region
        elsif ( $fileName =~ /WORLD/ ) {
            my $region = $self->_getRegion($countryCode);
            if ($region) {
                $clientService =
                  BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $region, product_type => $productType );
            }
            $sanCriteria{'Feed ID'}      = "WORLD";
            $sanCriteria{'Country Code'} = $countryCode;

        } elsif ( $fileName =~ /AUD/ ) {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'AUS', product_type => $productType );
            $sanCriteria{'Feed ID'} = "AUS";
        } else {
            $clientService = BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        }
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::GOOGLE_PLAY() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::JB_HIFI() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::LSI() ) {
        $clientService = BookPub::DB::Item::ClientService->Lookup(
            service_id    => $serviceID,
            feed_id       => 'ANY',
            product_type  => $productType,
            currency_code => $currencyCode
        );
        $sanCriteria{'Currency Code'} = $currencyCode;
        $sanCriteria{'Product Type'}  = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::KOBO() ) {
        if ( my $region = $self->_getRegion($countryCode) ) {
            $region = 'AUS' if $productType eq 'AUDB' and $region ne 'NEZ';
            $clientService =
              BookPub::DB::Item::ClientService->Lookup(
                service_id   => $serviceID,
                feed_id      => $region,
                product_type => $productType
            );
        }
        $sanCriteria{'Country Code'} = $countryCode;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::MACQUARIE() ) {
        my $comments = lc( $sale->r_comments );
        if ( $comments eq 'data_license' || $comments eq 'data_licence' ) {
            if ( $countryCode eq 'AU' ) {
                $clientService =
                  BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'dl-AU', product_type => $productType );
            } else {
                $clientService =
                  BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'dl-ROW', product_type => $productType );
            }
        } elsif ( $countryCode eq 'AU' ) {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'other-AU', product_type => $productType );
        } else {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'other-ROW', product_type => $productType );
        }

        $sanCriteria{'Comments'}     = $comments;
        $sanCriteria{'Country Code'} = $countryCode;
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::MOMENTUM_BOOKS() ) {
        $sanCriteria{'Feed ID'} = $feedID;
        if ( $fileName =~ / Int /i ) {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'INT', product_type => $productType );
            $sanCriteria{'Feed ID'} = "INT";
        } elsif ( $fileName =~ / Aus /i ) {
            $clientService =
              BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'AUS', product_type => $productType );
            $sanCriteria{'Feed ID'} = "AUS";
        }
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::OVERDRIVE() ) {
        my $feedID = 'ANY';
        $feedID = $countryCode if ($productType eq 'AUDB');
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
        $sanCriteria{'Country Code'} = $countryCode;
    } elsif ( $serviceID == BookPub::Tracker::Service::PETER_PAL() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::READCLOUD() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::THREE_M() || $serviceID == BookPub::Tracker::Service::OCLC() )    # actually, now OCLC (previously Bibliotheca)
    {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::TXTR() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::WHEELERS() ) {
        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    } elsif ( $serviceID == BookPub::Tracker::Service::IN_AUDIO() ) {
        my $feedID = $countryCode =~ /^AU$/i ? 'AU' : 'NZ';
        $clientService =
            BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );
    } elsif ( $serviceID == BookPub::Tracker::Service::ALDI() ) {
        # rPromoCode = SAN
        $clientService = bless {
            client_service_id => $sale->r_promo_code,
            service_id        => $serviceID,
            product_type      => $productType,
        }, 'BookPub::DB::Item::ClientService';
    } elsif ( $serviceID == BookPub::Tracker::Service::SPOTIFY() ) {
        my $feedID = $countryCode =~ /^(AU|NZ)$/ ? $1 : "ROW";
        $clientService =
            BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );
    } elsif ( $serviceID == BookPub::Tracker::Service::LIBRO() ) {
        my $feedID = $countryCode;

        if ( $fileName =~ /\d{4}\-\d{1,2} Libro\.fm Pan Macmillan Australia Report/ ) {
            $feedID = 'AU';
        }

        $clientService =
            BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => $feedID, product_type => $productType );
    } else {
        # couldn't see any reason not to default any non-specifically handled service to the 'ANY' lookup.
        # if we add a service to their client service table and it's got ANY as the feed, now it'll just work.
        # (stated differently, it's not like adding the service in code to do the same lookup
        #    stops an import or sale report because suddenly we want to start doing something "special" for the service)
        # we'll leave the existing ones, but they should be largely unnecessary.

        $clientService =
          BookPub::DB::Item::ClientService->Lookup( service_id => $serviceID, feed_id => 'ANY', product_type => $productType );
        $sanCriteria{'Product Type'} = $productType;
    }

    return ( $clientService, %sanCriteria );
}

sub _parsePeriodName {
    my ( $self, $periodID ) = @_;

    # We need to turn the period name into a YYMM value for the sales period.
    my $salesPeriod;
    my $period = BookPub::DB::Item::Period->Lookup( period_id => $periodID );
    my $periodName = $period->name();

    my %monthValues = (
        "january"   => "01",
        "jan"       => "01",
        "february"  => "02",
        "feb"       => "02",
        "march"     => "03",
        "mar"       => "03",
        "april"     => "04",
        "apr"       => "04",
        "may"       => "05",
        "june"      => "06",
        "jun"       => "06",
        "july"      => "07",
        "jul"       => "07",
        "august"    => "08",
        "aug"       => "08",
        "september" => "09",
        "sept"      => "09",
        "sep"       => "09",
        "october"   => "10",
        "oct"       => "10",
        "november"  => "11",
        "nov"       => "11",
        "december"  => "12",
        "dec"       => "12",
    );

    if ( $periodName =~ /^\d{4}$/ ) {
        $salesPeriod = $periodName;
    } elsif ( $periodName =~ /(\d{2})[-\s_]\d{2}(\d{2})/ ) {
        $salesPeriod = $2 . $1;
    } elsif ( $periodName =~ /(\d{1})[-\s_]\d{2}(\d{2})/ ) {
        $salesPeriod = $2 . "0" . $1;
    } elsif ( $periodName =~ /(\w+)\.? \d{2}(\d{2})/ ) {
        my $monthName = lc($1);
        if ( $monthValues{$monthName} ) {
            $salesPeriod = $2 . $monthValues{$monthName};
        }
    }

    if ( !$salesPeriod ) {
        my $error = "Unable to find sales period in period name: $periodName";
        $self->_sendErrorEmail($error);
        die $error . "\n";
    }

    return $salesPeriod;
}

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

    my $priceTypeName;
    my $priceTypeID = $sale->price_type_id;
    if ( $priceTypeID == 1 ) {
        $priceTypeName = "Retail";
    } elsif ( $priceTypeID == 2 ) {
        $priceTypeName = "Retail Plus Tax";
    } elsif ( $priceTypeID == 41 ) {
        $priceTypeName = "Agency";
    } elsif ( $priceTypeID == 42 ) {
        $priceTypeName = "Agency Plus Tax";
    } else {
        my $error = "Unknow price type ID $priceTypeID for sale id " . $sale->sale_id;
        $self->_sendErrorEmail($error);
        die $error . "\n";
    }

    return $priceTypeName;
}

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

    my $purchaseTypeID = $sale->purchase_type;

    my $purchaseType = BookPub::DB::Item::Sale->GetPurchaseTypeString($purchaseTypeID);

    if ( !$purchaseType || lc($purchaseType) eq 'unknown' ) {
        my $error = "Unknow purchase type $purchaseTypeID for sale id " . $sale->sale_id;
        $self->_sendErrorEmail($error);
        die $error . "\n";
    }

    return $purchaseType;
}

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

    my $productTypeID = $sale->product_type;

    my $productType = BookPub::DB::Item::Sale->GetProductTypeString($productTypeID);

    if ( !$productType || lc($productType) eq 'unknown' ) {
        my $error = "Unknow product type $productTypeID for sale id " . $sale->sale_id;
        $self->_sendErrorEmail($error);
        die $error . "\n";
    }

    return $productType;
}

sub _getRegion {
    my ( $self, $countryCode ) = @_;

    # For some of these, we're gonna convert the country code to a region.
    # We don't have room in the feed id column to store the whole name,
    # so I'm going to use three character abbreviations (which will also
    # differentiate them from the regular two character country codes).

    my %countryCodeToRegion = (

        # Africa
        "AO" => "AFR",
        "BF" => "AFR",
        "BI" => "AFR",
        "BJ" => "AFR",
        "BW" => "AFR",
        "CD" => "AFR",
        "CF" => "AFR",
        "CG" => "AFR",
        "CI" => "AFR",
        "CM" => "AFR",
        "CV" => "AFR",
        "DJ" => "AFR",
        "DZ" => "AFR",
        "EG" => "AFR",
        "EH" => "AFR",
        "ER" => "AFR",
        "ET" => "AFR",
        "GA" => "AFR",
        "GF" => "AFR",
        "GH" => "AFR",
        "GM" => "AFR",
        "GN" => "AFR",
        "GQ" => "AFR",
        "GW" => "AFR",
        "KE" => "AFR",
        "LR" => "AFR",
        "LS" => "AFR",
        "MA" => "AFR",
        "MG" => "AFR",
        "ML" => "AFR",
        "MR" => "AFR",
        "MU" => "AFR",
        "MW" => "AFR",
        "MZ" => "AFR",
        "NA" => "AFR",
        "NE" => "AFR",
        "NG" => "AFR",
        "RW" => "AFR",
        "SC" => "AFR",
        "SD" => "AFR",
        "SL" => "AFR",
        "SN" => "AFR",
        "SO" => "AFR",
        "SR" => "AFR",
        "SS" => "AFR",
        "ST" => "AFR",
        "SZ" => "AFR",
        "TD" => "AFR",
        "TG" => "AFR",
        "TZ" => "AFR",
        "UG" => "AFR",
        "ZA" => "AFR",
        "ZM" => "AFR",
        "ZW" => "AFR",

        # Asia except China & India
        "BN" => "ASI",
        "BT" => "ASI",
        "ID" => "ASI",
        "JP" => "ASI",
        "KH" => "ASI",
        "KP" => "ASI",
        "KR" => "ASI",
        "LA" => "ASI",
        "LK" => "ASI",
        "MM" => "ASI",
        "MN" => "ASI",
        "MV" => "ASI",
        "MY" => "ASI",
        "NP" => "ASI",
        "PH" => "ASI",
        "SG" => "ASI",
        "TH" => "ASI",
        "TL" => "ASI",
        "TW" => "ASI",
        "VN" => "ASI",

        # Australia
        "AQ" => "AUS",
        "AU" => "AUS",
        "CC" => "AUS",
        "CK" => "AUS",
        "CX" => "AUS",
        "FJ" => "AUS",
        "FM" => "AUS",
        "HM" => "AUS",
        "KI" => "AUS",
        "MH" => "AUS",
        "MP" => "AUS",
        "NC" => "AUS",
        "NF" => "AUS",
        "NR" => "AUS",
        "NU" => "AUS",
        "PG" => "AUS",
        "PN" => "AUS",
        "PW" => "AUS",
        "SB" => "AUS",
        "TF" => "AUS",
        "TK" => "AUS",
        "TO" => "AUS",
        "TV" => "AUS",
        "VU" => "AUS",
        "WF" => "AUS",
        "WS" => "AUS",
        "YT" => "AUS",

        # Canada
        "CA" => "CAN",

        # China
        "CN" => "CHI",
        "HK" => "CHI",

        # Europe
        "AD" => "EUR",
        "AM" => "EUR",
        "AN" => "EUR",
        "AT" => "EUR",
        "AX" => "EUR",
        "AZ" => "EUR",
        "BA" => "EUR",
        "BE" => "EUR",
        "BG" => "EUR",
        "BV" => "EUR",
        "BY" => "EUR",
        "CH" => "EUR",
        "CS" => "EUR",
        "CY" => "EUR",
        "CZ" => "EUR",
        "DK" => "EUR",
        "EE" => "EUR",
        "ES" => "EUR",
        "FI" => "EUR",
        "FO" => "EUR",
        "FR" => "EUR",
        "GE" => "EUR",
        "GG" => "EUR",
        "GI" => "EUR",
        "GL" => "EUR",
        "GR" => "EUR",
        "GS" => "EUR",
        "HR" => "EUR",
        "HU" => "EUR",
        "IE" => "EUR",
        "IS" => "EUR",
        "IT" => "EUR",
        "JE" => "EUR",
        "KG" => "EUR",
        "KZ" => "EUR",
        "LI" => "EUR",
        "LT" => "EUR",
        "LU" => "EUR",
        "LV" => "EUR",
        "MC" => "EUR",
        "MD" => "EUR",
        "ME" => "EUR",
        "MF" => "EUR",
        "MK" => "EUR",
        "MS" => "EUR",
        "MT" => "EUR",
        "NL" => "EUR",
        "NO" => "EUR",
        "PL" => "EUR",
        "PM" => "EUR",
        "PT" => "EUR",
        "RO" => "EUR",
        "RS" => "EUR",
        "RU" => "EUR",
        "SE" => "EUR",
        "SI" => "EUR",
        "SJ" => "EUR",
        "SK" => "EUR",
        "SK" => "EUR",
        "TJ" => "EUR",
        "TM" => "EUR",
        "TN" => "EUR",
        "TR" => "EUR",
        "UA" => "EUR",
        "UZ" => "EUR",
        "VA" => "EUR",
        "YU" => "EUR",

        # Germany
        "DE" => "GER",
        "MO" => "GER",

        # India
        "BD" => "IND",
        "IN" => "IND",
        "PK" => "IND",

        # Middle East
        "AE" => "MDE",
        "AF" => "MDE",
        "BH" => "MDE",
        "IL" => "MDE",
        "IQ" => "MDE",
        "IR" => "MDE",
        "JO" => "MDE",
        "KW" => "MDE",
        "LB" => "MDE",
        "LY" => "MDE",
        "OM" => "MDE",
        "PS" => "MDE",
        "QA" => "MDE",
        "SA" => "MDE",
        "SY" => "MDE",
        "YE" => "MDE",

        # New Zealand
        "NZ" => "NEZ",

        # South America
        "AG" => "SAM",
        "AI" => "SAM",
        "AR" => "SAM",
        "AW" => "SAM",
        "BB" => "SAM",
        "BL" => "SAM",
        "BM" => "SAM",
        "BO" => "SAM",
        "BQ" => "SAM",
        "BR" => "SAM",
        "BS" => "SAM",
        "BZ" => "SAM",
        "CL" => "SAM",
        "CO" => "SAM",
        "CR" => "SAM",
        "CU" => "SAM",
        "CW" => "SAM",
        "DM" => "SAM",
        "DO" => "SAM",
        "EC" => "SAM",
        "FK" => "SAM",
        "GD" => "SAM",
        "GP" => "SAM",
        "GT" => "SAM",
        "GU" => "SAM",
        "GY" => "SAM",
        "HN" => "SAM",
        "HT" => "SAM",
        "JM" => "SAM",
        "KM" => "SAM",
        "KN" => "SAM",
        "KY" => "SAM",
        "LC" => "SAM",
        "MQ" => "SAM",
        "MX" => "SAM",
        "NI" => "SAM",
        "PA" => "SAM",
        "PE" => "SAM",
        "PF" => "SAM",
        "PR" => "SAM",
        "PY" => "SAM",
        "RE" => "SAM",
        "SH" => "SAM",
        "SV" => "SAM",
        "SX" => "SAM",
        "TC" => "SAM",
        "TT" => "SAM",
        "UY" => "SAM",
        "VC" => "SAM",
        "VE" => "SAM",
        "VG" => "SAM",
        "VI" => "SAM",

        # United Kingdom
        "GB" => "UNK",
        "IM" => "UNK",
        "IO" => "UNK",

        # United States
        "AL" => "USA",
        "AS" => "USA",
        "UM" => "USA",
        "US" => "USA",
    );

    my $region = $countryCodeToRegion{$countryCode};

    return $region;
}

sub _normalizeFileName {
    my ( $self, $fileName ) = @_;
    $fileName =~ s/_/ /g;

    return $fileName;
}

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 $file = BookPub::DB::Item::File->Lookup( file_id => $fileID );
    my $fileName = $self->_normalizeFileName( $file->orig_file_name );

    # Now we need to check for the SAN, which is assigned at the sale level.
    my $sales = BookPub::DB::Item::Sale->GetAllByFileID($fileID);

    my $i = 1;
    my %seen;

    while ( my $sale = $sales->next() ) {
        my ( $clientService, %sanCriteria ) = $self->_getClientService( $sale, $fileName );

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

            if (%sanCriteria) {

                # We need to trim this down to the unique sets of SAN criteria.
                my $seenString;
                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;
                }
            } else {

                # It would also be good to know if we don't have any criteria set up for this service yet.
                $validation{"SAN"} = "NONE";
            }
        }
    }

    return %validation;
}

1;
