package RPS::Import::MapFaceBase;

use strict;
use Data::Dumper;

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

use lib '/app/tools/raptor/lib/';
use Raptor::DB::Item::Sale;

use lib '/app/tools/sale_import/lib/';
use Import::Importer;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::MapFaceTempMapping;
use RPS::DB::Item::MapFaceTempMappingCriteria;
use RPS::DB::Item::SaleImportError;
use RPS::DB::Item::SaleImportErrorRawValue;
use RPS::DB::Item::SaleImportFieldMapping;
use RPS::DB::Item::SaleImportFileHeader;

# We create the sale_import_error entries before we have a sale id.
# So, we need to store their ids and circle back at the end and fill in the sale id.
my @saleImportErrorIDs;
# Same deal with some of the raw values on the sales lines.
my @saleImportErrorRawValueIDs;


sub _getOffset {
	my $self = shift;
	my $field = shift;

	my $validAccessors = $self->_getValidAccessorHash();

	assert( $field, "Field required" );

	my $map = $self->_fieldMap();
	my $unverifiedMap = $self->_unverifiedFieldMap() || {};

	if( $validAccessors->{$field} ) {
        return $self->_convertIndex($map->{$field});

    } elsif( exists( $unverifiedMap->{$field} ) ) {
        return $self->_convertIndex($unverifiedMap->{$field});

    } else {
        return undef;
    }
}


sub handleError 
{
    my ($self, $description, $type) = @_;
    
    # We can't map non-qualifying errors, so skip this step for them.
    if ($type != RPS::DB::Item::SaleImportError::kErrorNonqualifying)
    {
        # First, let's try to map this.
        my $mappedValue = $self->mapError($type);
    
        if ($mappedValue)
        {
            return $mappedValue;
        }
    }
    
    # If that doesn't work, we'll just log it.
    $self->logError($description, $type);
    return undef;
}

sub mapError 
{
    my ($self, $errorType) = @_;
    
    my @mappingCriteria;
	
    # We need to select the group based on the type of error.
    if ($errorType == RPS::DB::Item::SaleImportError::kErrorCountry) 
    {
        @mappingCriteria = $self->_mapFaceCountryCode();  
    }
    elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorService) 
    { 
        @mappingCriteria = $self->_mapFaceServiceID();  
    }    
    elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorProductType) 
    { 
        @mappingCriteria = $self->_mapFaceProductType();  
    }
    elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorFormatType) 
    { 
        @mappingCriteria = $self->_mapFaceFormatType();  
    }    
    elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorMediaType) 
    { 
        @mappingCriteria = $self->_mapFaceMediaType();  
    }    

    # So now, let's make a hash of the mapping fields and their values.
    my %saleData;
    
    foreach my $field (@mappingCriteria) 
    {
        if (defined $self->_getByFieldName($field)) 
        {
            my $value = $self->_getByFieldName($field);  	
            $saleData{$field} = $value;	
        }    
    } 	
	
    # Okay, so now we've got all the data we need.  Let's try to find a match!
    my $serviceID = $self->{_file}->ServiceID;
    my @compatibleVersions = $self->_mapFaceCompatibleVersions();  
    my $versions = join(',',@compatibleVersions);
    
    # Grabbing all of the mappings for this service and the compatible versions.
    my $mappings = Common::DB::Item::MapFaceMapping->GetByServiceAndVersion($serviceID, $versions);	
    
    my $fieldCount = @mappingCriteria;

    while ($mappings->hasNext()) 
    {
        my $mapping = $mappings->next();
        my $fieldsMatched = 0;
        
        my $mappingCriteria = Common::DB::Item::MapFaceMappingCriteria->GetByMapping($mapping->mapface_mapping_id);
        my %mappingData;
        
        while ($mappingCriteria->hasNext())
        {
            my $criteria = $mappingCriteria->next();   
            $mappingData{$criteria->field} = $criteria->value;		
        }
        
        foreach my $saleField (keys %saleData) 
        {
            # If it's blank for both, that's a match.
            if (!$saleData{$saleField} && !$mappingData{$saleField}) 
            {
                $fieldsMatched++;
                next();
            }
        
            # And if it's the same, that's a match too, of course.
            if ($saleData{$saleField} eq $mappingData{$saleField}) 
            {
                $fieldsMatched++;
                next();
            }
        }
        
        if ($fieldsMatched == $fieldCount) 
        {
        
            # Now we just have to make sure we're putting the right value in the right place. 
            # And let's make sure there is a value...
            if ($errorType == RPS::DB::Item::SaleImportError::kErrorService && $mapping->mapped_service_id) {
        
                # We also need to make sure that this service is in the client's service table.
                # We'll just try to add it, and it's smart enough to skip it if it's already there.			
                my $clientID = Common::RSApp::GetClientID();
                    Client::Service::AddService(
                    client_id => $clientID,
                    service_id => $mapping->mapped_service_id
                );				
                
                return $mapping->mapped_service_id;				
            } 
            elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorCountry && $mapping->mapped_country_code) 
            {
                return $mapping->mapped_country_code;
            } 
            elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorProductType && $mapping->mapped_product_type) 
            {
                return $mapping->mapped_product_type;	
            } 
            elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorFormatType && $mapping->mapped_format_type) 
            {
                return $mapping->mapped_format_type;	
            } 
            elsif ($errorType == RPS::DB::Item::SaleImportError::kErrorMediaType && $mapping->mapped_media_type) 
            {
                return $mapping->mapped_media_type;			
            } 
        }		
    }	
    
    # Well if we made it here, we couldn't find a match. :(
    # Just send back nothing.
    return undef;
}	


sub logError {
	my ($self, $description, $type) = @_;
	
    #if( $self->linenum ) {
    #    $description .= ' (sheet: ' . $self->sheetname . ' line: ' . $self->linenum . ')';
    #}	

    # Add some debugging info into the error if not running on a production server
    #unless ( Common::RSApp::IsProductionServer() )
    #{
    #    $description .= ' [failed in ' . (caller)[1] . ' line ' . (caller)[2] . ']'; 
    #}    
    
	#$description .= ": pkg=" . ref($self) . " v=" . $self->_version();
	
	my $saleImportError = RPS::DB::Item::SaleImportError->Create(
		file_id     => $self->{_file}->FileID,
		description => $description,
		error_type  => $type,
	);
	
	$saleImportError->save();
	
	push(@saleImportErrorIDs, $saleImportError->sale_import_error_id);		
	
	# Setting this flag so that we don't try to find a match for this sale later.
	$self->{_hasError} = 1;	
	
	# So now we need to store all of the values used for mapping for this sale, if we haven't done so already.
	# We'll also need to set a flag so that we don't do it more than once for a sale.
	if ( !$self->{_rawValuesStored} ) {		
		my @mappingFields = $self->_mappingFields();

    	foreach my $field (@mappingFields) {
    	    if (defined $self->_getByFieldName($field)) {
                my $value = $self->_getByFieldName($field);  		
    			my $errorRawValue = RPS::DB::Item::SaleImportErrorRawValue->Create(
    				file_id => $self->{_file}->FileID,
    				field   => $field,
    				value   => $value,
    			);
    			$errorRawValue->save();
    			push(@saleImportErrorRawValueIDs, $errorRawValue->sale_import_error_raw_value_id);   
    	    }    
		} 
		$self->{_rawValuesStored} = 1;			
	} 		
}	

# The importers must overload these with the field accessors used to derive each one.
sub _mapFaceCountryCode {
	return undef;
}

sub _mapFaceServiceID {
	return undef;
}

sub _mapFaceProductType {
	return undef;
}

sub _mapFaceFormatType {
	return undef;
}

sub _mapFaceMediaType {
	return undef;
}

# By default, the mappings made for a specific importer version will not be shared.
# Importers can override this if they want to look for mapping across multiple versions. 
sub _mapFaceCompatibleVersions {
	my $self = shift;
	return $self->{_file}->VersionNum;
}	

# Now we need a list of the distinct accessors used across all of the mapping fields
sub _mappingFields { 
	my $self = shift;
	
	my @fields;
	
	push(@fields, $self->_mapFaceCountryCode());
	push(@fields, $self->_mapFaceServiceID());
	push(@fields, $self->_mapFaceProductType());
	push(@fields, $self->_mapFaceFormatType());
	push(@fields, $self->_mapFaceMediaType());
	
	my %distinctFields;
	foreach my $field (@fields) {
		$distinctFields{$field} = 1;	
	}
	
	my @accessors;	
	foreach my $distinctField (keys %distinctFields) {
		push(@accessors, $distinctField);
	}	
	
	return @accessors;
}

sub _fillInSaleID {
	my ($self, $saleID) = @_;
	
	foreach my $saleImportErrorID (@saleImportErrorIDs) {
		my $saleImportError = RPS::DB::Item::SaleImportError->Lookup(sale_import_error_id => $saleImportErrorID);
        if ($saleImportError) {
            $saleImportError->sale_id($saleID);
            $saleImportError->save();
        }
	}
	undef @saleImportErrorIDs;
	
	foreach my $saleImportErrorRawValueID (@saleImportErrorRawValueIDs) {
		my $saleImportErrorRawValue = RPS::DB::Item::SaleImportErrorRawValue->Lookup(sale_import_error_raw_value_id => $saleImportErrorRawValueID);
        if ($saleImportErrorRawValue) {
            $saleImportErrorRawValue->sale_id($saleID);
            $saleImportErrorRawValue->save();
        }
	}
	undef @saleImportErrorRawValueIDs;		
	undef $self->{_rawValuesStored};
	undef $self->{_hasError};	
}	


sub _processHeader {
    my $self = shift;

	if( $self->_isHeader() ) {
		my $fileID = $self->{_file}->FileID;
		my @mappingFields = $self->_mappingFields();
	    foreach my $field (@mappingFields)    
	    {
	        if (defined $self->_getByFieldName( $field )) {
    	        my $header = $self->_getByFieldName( $field );  		
    	        
    	        # First, let's make sure we don't already have this exact header already.
    	        my $collection = RPS::DB::Item::SaleImportFileHeader->GetAllByFieldAndFileID($field, $fileID);
    	        my $alreadyStored;
    	        
    	        while ( $collection->hasNext() ) {
    	            my $fileHeader = $collection->next();
    	            if ($fileHeader->header eq $header) {
    	                $alreadyStored = 1;
    	                last();
    	            }
    	        }
    	        
    	        if (!$alreadyStored) {
        			my $fileHeader = RPS::DB::Item::SaleImportFileHeader->Create(
        				file_id => $fileID,
        				field   => $field,
        				header  => $header,
        			);
        			
        			$fileHeader->save();   
        	    }  
    		}
		}
				
		# Let's also store a list of which fields map to which values. 
		# We really only need this for highlighting on the mapping page.	
		my @mappingsCountry = $self->_mapFaceCountryCode();
		foreach my $mappingCountry (@mappingsCountry) {
			my $mappingRecord = RPS::DB::Item::SaleImportFieldMapping->Create(
				file_id => $fileID,
				field   => 'countryCode',
				mapping => $mappingCountry,
			);		
			$mappingRecord->save();  
		}
		
		my @mappingsService = $self->_mapFaceServiceID();
		foreach my $mappingService (@mappingsService) {
			my $mappingRecord = RPS::DB::Item::SaleImportFieldMapping->Create(
				file_id => $fileID,
				field   => 'serviceID',
				mapping => $mappingService,
			);		
			$mappingRecord->save();  
		}	
		
		my @mappingsProduct = $self->_mapFaceProductType();
		foreach my $mappingProduct (@mappingsProduct) {
			my $mappingRecord = RPS::DB::Item::SaleImportFieldMapping->Create(
				file_id => $fileID,
				field   => 'productType',
				mapping => $mappingProduct,
			);		
			$mappingRecord->save();  
		}	
		
		my @mappingsFormat = $self->_mapFaceFormatType();
		foreach my $mappingFormat (@mappingsFormat) {
			my $mappingRecord = RPS::DB::Item::SaleImportFieldMapping->Create(
				file_id => $fileID,
				field   => 'formatType',
				mapping => $mappingFormat,
			);		
			$mappingRecord->save();  
		}		
		
		my @mappingsMedia = $self->_mapFaceMediaType();
		foreach my $mappingMedia (@mappingsMedia) {
			my $mappingRecord = RPS::DB::Item::SaleImportFieldMapping->Create(
				file_id => $fileID,
				field   => 'mediaType',
				mapping => $mappingMedia,
			);		
			$mappingRecord->save();  
		}						
	}
}


# We're going to deal with the qualifying errors in groups, so let's go ahead
# and figure those groups out now.
sub _postProcess {
 	my $self = shift;
	my %args = @_;

	Log->info( "Starting _postProcess" );
    #Import::Importer->_postProcess(%args);   
    my $clientID = $args{client_id};
    my $servicesHashRef = $self->{services};
    foreach my $serviceID (keys %$servicesHashRef) {
        Client::Service::AddService(
            client_id => $clientID,
            service_id => $serviceID
        );
    }    
    
    # So we need to create separate groups based on the distinct combination of values 
    # for each field in the sale_import_error_raw_value table.
    
    my $fileID = $self->{_file}->FileID;
    
    # Let's make sure that we don't have an unqualified errors before even bothering with this.
    my $unqualifiedErrorCount = RPS::DB::Item::SaleImportError->GetNonQualifiedCountByFileID($fileID);
    
    if ($unqualifiedErrorCount == 0) {
    
		# This will holds some strings that we'll use to tell the mapping groups apart	
		my %flatCombos;
	    
		# As we go through and create the groups, I'm going to keep track of which one 
		# each sale belongs to with this.
		my %saleGroup;
		
		# Going to start the mapping groups at 1 instead of 0, but we'll wait to increment it until we need to.
		my $groupNum = 0;		    
	    
	    # Let's grab all of the sale_import_error records
	    my $importErrors = RPS::DB::Item::SaleImportError->GetAllQualifyingByFileID($fileID);
		while ($importErrors->hasNext() ) {
			my $importError = $importErrors->next();
			my $saleID = $importError->sale_id;
			
			# We only need to do this once per sale, not once per error.
			if (!$saleGroup{$saleID}) {

                my $mappings = RPS::DB::Item::SaleImportErrorRawValue->GetAllBySaleID($saleID);
                
                my $flatCombo;
                my %hashCombo;
                
                while ($mappings->hasNext())
                {
                    my $mapping   = $mappings->next();
                    
                    my $fieldName = $mapping->field;
                    my $value     = $mapping->value;	
                    $hashCombo{$fieldName} = $value;
                    $flatCombo .= $fieldName . $value;
                }	
    			
                # We need a little more info to figure out which sales should be grouped together.
                # We're only going to store these values in the $flatCombo, since their only purpose
                # is to help us distinguish between like sales.
                my $fieldMappings = RPS::DB::Item::SaleImportFieldMapping->GetAllByFileID($fileID);
                my %seenFieldMapping;
                my $sale = Raptor::DB::Item::Sale->Lookup(sale_id => $saleID);
                while ($fieldMappings->hasNext()) {
                    my $fieldMapping = $fieldMappings->next();
                    my $fieldName = $fieldMapping->field;
                    if (!$seenFieldMapping{$fieldName}) {
                        $flatCombo .= $fieldName;
                        
                        if ($fieldName eq 'productType') {
                            $flatCombo .= $sale->product_type;
                        } elsif ($fieldName eq 'mediaType') {
                            $flatCombo .= $sale->media_type;
                        } elsif ($fieldName eq 'formatType') {
                            $flatCombo .= $sale->format_type;
                        } elsif ($fieldName eq 'serviceID') {
                            $flatCombo .= $sale->service_id;
                        } elsif ($fieldName eq 'countryCode') {
                            $flatCombo .= $sale->country_code;
                        }      		 
                           
                        $seenFieldMapping{$fieldName} = 1;            	
                    }
                }        			
    			  			
                # So if $flatCombo is new, then we have a new group.
                if (!$flatCombos{$flatCombo}) {
                    $groupNum++;
                    
                    my $tempMapping = RPS::DB::Item::MapFaceTempMapping->Create(
                        file_id => $fileID,
                        group_num => $groupNum,
                        status  => 0,
                    );
                    
                    $tempMapping->save();	
                    
                    $flatCombos{$flatCombo} = $tempMapping->mapface_temp_mapping_id;
                    
                    # Store the fields for this mapping group
                    foreach my $fieldName (keys %hashCombo) {
                        my $value = $hashCombo{$fieldName};
                        my $mappingCriteria = RPS::DB::Item::MapFaceTempMappingCriteria->Create(
                            mapface_temp_mapping_id => $tempMapping->mapface_temp_mapping_id,
                            field => $fieldName,
                            value => $value,
                        );
                        
                        $mappingCriteria->save();	
                    }								
                }
                
                $saleGroup{$saleID} = $flatCombos{$flatCombo};
            }
        }
		
		# Ok, now we just need to fill in the mapface temp mapping id for all 
		# of the sale_import_error records for this file.
	    foreach my $saleID (keys %saleGroup)    
	    {    
	        my $mappingID = $saleGroup{$saleID};
			my $importErrors = RPS::DB::Item::SaleImportError->GetAllQualifyingBySaleID($saleID);
			while ($importErrors->hasNext() ) {
				my $importError = $importErrors->next();			
				$importError->mapface_temp_mapping_id($mappingID);
				$importError->save();
			}
	    } 	
	}       
}


###
1;# Play nicely.
###
