package RPS::Import::ImporterMixed;

use strict;
use warnings;

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

use parent 'RPS::Import::Importer';

use constant DATA_TYPES => {
    0 => 'digital',
    1 => 'physical',
    2 => 'mixed',
};

use constant OPTION_PREFIX => 'option';

use constant STORED_SALE_FIELDS => [qw/
    albumName
    artistName
    averagePrice
    channel
    clientProductID
    comments
    configuration
    conversionRate
    countryCode
    currencyCode
    formatType
    free
    importStatus
    isrc
    labelName
    mcps_adjustment
    mediaType
    outlet
    payoutType
    price
    priceLevel
    priceType
    productType
    retail
    retailPrice
    returns
    returnsRevenue
    sales
    salesRevenue
    serviceID
    serviceProductID
    totalRevenue
    trackName
    trackNum
    units
    upc
    wholesalePrice
    wholesaleRate
    grossRevenue
/];

sub _init {
    my ($self, %args) = @_;

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

    $self->{data_type}            = '';
    $self->{data_type_option}     = '';
    $self->{field_map}            = {};
    $self->{unverified_field_map} = {};
    $self->{header_identifier}    = {};

    $self->_validateNecessaryMethods();

    return $self;
}

sub dataType {
    my $self = shift;
    $self->{data_type} = shift if @_;
    return $self->{data_type};
}

sub dataTypeOption {
    my $self = shift;
    $self->{data_type_option} = shift if @_;
    return $self->{data_type_option};
}

sub _fieldMap {
    my ($self, $hData) = @_;

    if ( ref $hData eq 'HASH' ) {
        $self->{field_map} = $hData;
    }

    return $self->{field_map};
}

sub _unverifiedFieldMap {
    my ($self, $hData) = @_;

    if ( ref $hData eq 'HASH' ) {
        $self->{unverified_field_map} = $hData;
    }

    return $self->{unverified_field_map};
}

sub _headerIdentifier {
    my ($self, $hData) = @_;

    if ( ref $hData eq 'HASH' ) {
        $self->{header_identifier} = $hData;
    }

    return $self->{header_identifier};
}

sub _validateNecessaryMethods {
    my $self = shift;

    my @methods = qw/
        _fieldMapExtended
        _unverifiedFieldMapExtended
        _headerIdentifierExtended
    /;

    foreach my $method ( @methods ) {
        unless ( $self->can($method) ) {
            $self->fail("The class must contain a method: $method.");
        }
        # validate new structures
        $method =~ s/^_([a-z])(\w+)$/_validate\u$1$2/;
        $self->$method();
    }

    return 1;
}

sub _validateFieldMapExtended {
    my $self = shift;

    my $warnPrefix = 'Field map extended';
    my $hFieldMapExt = $self->_fieldMapExtended;

    $self->_checkHashRef($warnPrefix, $hFieldMapExt);
    $self->_checkMapStructureKeys($warnPrefix, $hFieldMapExt);
    $self->_checkMapStructureOptions($warnPrefix, $hFieldMapExt);

    my $cExtraCheck = sub {
        my ($self, $warnPrefix, $hTypeOptionFields ) = @_;

        my $hValidAccessors = $self->_getValidAccessorHash();
        my @invalidAccessors = grep { !exists $hValidAccessors->{$_} } keys %$hTypeOptionFields;
        if (@invalidAccessors) {
            $self->fail("$warnPrefix contains invalid accessor's fields: '@invalidAccessors'");
        }

        return 1;
    };

    $self->_checkMapStructureFields($warnPrefix, $hFieldMapExt, $cExtraCheck);

    return 1;
}

sub _validateUnverifiedFieldMapExtended {
    my $self = shift;

    my $warnPrefix = 'Unverified field map extended';
    my $hUnverifiedFieldMapExt = $self->_unverifiedFieldMapExtended;

    $self->_checkHashRef($warnPrefix, $hUnverifiedFieldMapExt);

    # it's an optional structure
    return 1 unless %$hUnverifiedFieldMapExt;

    $self->_checkMapStructureKeys($warnPrefix, $hUnverifiedFieldMapExt);
    $self->_checkMapStructureOptions($warnPrefix, $hUnverifiedFieldMapExt);
    $self->_checkMapStructureFields($warnPrefix, $hUnverifiedFieldMapExt);

    return 1;
}

sub _validateHeaderIdentifierExtended {
    my $self = shift;

    my $warnPrefix = 'Header identifier map extended';
    my $hHeaderIdentifierMapExt = $self->_headerIdentifierExtended;

    $self->_checkHashRef($warnPrefix, $hHeaderIdentifierMapExt);

    # it's an optional structure if _noHeader true
    return 1 if !%$hHeaderIdentifierMapExt && $self->_noHeader;

    $self->_checkMapStructureKeys($warnPrefix, $hHeaderIdentifierMapExt);
    $self->_checkMapStructureOptions($warnPrefix, $hHeaderIdentifierMapExt);
    $self->_checkMapStructureFields($warnPrefix, $hHeaderIdentifierMapExt);

    return 1;
}

sub _checkHashRef {
    my ($self, $warnPrefix, $hData) = @_;

    unless ( ref $hData eq 'HASH' ) {
        $self->fail("$warnPrefix must be a HASH reference.");
    }

    return 1;
}

sub _checkMapStructureKeys {
    my ($self, $warnPrefix, $hData) = @_;

    unless ( grep { exists $hData->{$_} } values %{+DATA_TYPES} ) {
        $self->fail("$warnPrefix must contain keys: " . join(', ', sort values %{+DATA_TYPES}) . ".");
    }

    return 1;
}

sub _checkMapStructureOptions {
    my ($self, $warnPrefix, $hData) = @_;

    foreach my $type ( keys %$hData ) {
        next unless grep { $type eq $_ } values %{+DATA_TYPES};
        $self->_checkHashRef("$warnPrefix '$type' value", $hData->{$type});

        my $keyPattern = OPTION_PREFIX;
        unless ( grep { $_ =~ /^$keyPattern\d+$/ } keys %{ $hData->{$type} } ) {
            $self->fail("$warnPrefix '$type' must contain at least one '$keyPattern\\d+' key");
        }
    }

    return 1;
}

sub _checkMapStructureFields {
    my ($self, $warnPrefix, $hData, $cExtraCheck ) = @_;

    foreach my $type ( keys %$hData ) {
        my $warnPrefixPlus = $warnPrefix;
        foreach my $option ( keys %{ $hData->{$type} } ) {
            my $hTypeOptionFields = $hData->{$type}{$option};
            $warnPrefixPlus .= " '$type => $option'";
            $self->_checkHashRef($warnPrefixPlus, $hTypeOptionFields);

            $self->fail("$warnPrefixPlus doesn't contain fields") unless %$hTypeOptionFields;

            if ( ref $cExtraCheck eq 'CODE' ) {
                &$cExtraCheck($self, $warnPrefixPlus, $hTypeOptionFields);
            }
        }
    }

    return 1;
}

sub _isHeader {
    my $self = shift;
    assert( $self->line, "Line required" );

    $self->_cleanTypesAndFieldMaps();

    my $warnPrefix  = 'Header identifier map extended';
    my $hHeaderExtended = $self->_headerIdentifierExtended;
    $self->_checkHashRef($warnPrefix, $hHeaderExtended);

    foreach my $type ( keys %$hHeaderExtended ) {
        foreach my $option ( sort keys %{ $hHeaderExtended->{$type} } ) {
            $self->_setTypesAndFieldMaps($type, $option);
            if ( $self->SUPER::_isHeader() ) {
                Common::Log::Print(
                    "++ The '" . ($self->{sheetname} || 0) . "' sheet contains a header for the map "
                    . $self->dataType . " => "
                    . $self->dataTypeOption . "."
                );
                return 1;
            }
        }
    }

    return 0;
}

sub _setTypesAndFieldMaps {
    my ($self, $type, $option) = @_;

    $self->dataType($type);
    $self->dataTypeOption($option);

    my $hFieldMapExt = $self->_fieldMapExtended();
    $self->_fieldMap( $hFieldMapExt->{$type}{$option} );

    my $hUnverifiedFieldMapExt = $self->_unverifiedFieldMapExtended();
    # avoid autovivification
    if ( exists $hUnverifiedFieldMapExt->{$type} && exists $hUnverifiedFieldMapExt->{$type}{$option} ) {
        $self->_unverifiedFieldMap( $hUnverifiedFieldMapExt->{$type}{$option} );
    }

    my $hHeaderExt = $self->_headerIdentifierExtended();
    # avoid autovivification
    if ( exists $hHeaderExt->{$type} && exists $hHeaderExt->{$type}{$option} ) {
        $self->_headerIdentifier( $hHeaderExt->{$type}{$option} );
    }

    return 1;
}

sub _cleanTypesAndFieldMaps {
    my $self = shift;

    foreach (qw/data_type data_type_option/) {
        $self->{$_} = '';
    }
    foreach (qw/field_map unverified_field_map header_identifier/) {
        $self->{$_} = {};
    }

    return 1;
}

sub _saveLine {
    my $self = shift;

    my %excludeForDigital = (
        retailPrice    => 1,
        channel        => 1,
        priceLevel     => 1,
        wholesalePrice => 1,
        sales          => 1,
        salesRevenue   => 1,
        returns        => 1,
        returnsRevenue => 1,
        totalRevenue   => 1,
    );

    my %excludeForPhysical = (
        units      => 1,
        price      => 1,
        formatType => 1,
    );

    my $oSale = RPS::Import::SaleRec->new();
    my ($startDate, $endDate) = $self->saleDates();
    my $isDigital = RPS::Import::Importer->isDigitalProduct( $self->productType );

    foreach my $method ( @{+STORED_SALE_FIELDS} ) {
        if ( $self->physical && $self->physical == 2 ) {
            # digital
            next if $isDigital  && exists $excludeForDigital{$method}  && $excludeForDigital{$method};
            # physical
            next if !$isDigital && exists $excludeForPhysical{$method} && $excludeForPhysical{$method};
        }

        next if $isDigital && exists $excludeForDigital{$method};
        # fill fields to the sale object
        $method eq 'price' ? $oSale->price( $self->unitPrice ) : $oSale->$method( $self->$method );
    }

    # fill aditional fields
    $oSale->lineNum( $self->linenum );
    $oSale->dateBegin($startDate);
    $oSale->dateEnd($endDate);

    $self->_insertSale( sale => $oSale );

    return 1;
}


1;
