#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package RPS::Product::Product;

use strict;
use warnings;
use Carp;
use Data::Dumper;

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

use lib '/app/tools/rps/lib';
use RPS::DB::Item::Product;
use lib '/app/tools/rps/lib';
use RPS::DB::Item::Product;
use RPS::DB::Item::ProductDigital;
use RPS::DB::Item::Album;
use RPS::Product::TrackList;
use RPS::Product::PriceList;
use RPS::Product::ProductDistribution;

use Common::FormObject;
use base 'Common::FormObject';

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

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

    if ( $args{_dbItem} ) {
        return $self->_initFromDBItem( $args{_dbItem} );
    } elsif ( $args{productID} ) {
        return $self->_initFromDB( $args{productID} );
    } else {
        return $self->_initWithDefaults( $args{albumID}, $args{catalogNumber} );
    }
}

sub _initFromDB {
    my ( $self, $productID ) = @_;

    my $dbItem = RPS::DB::Item::Product->Lookup( product_id => $productID );
    return $self->_initFromDBItem($dbItem);
}

sub _initFromDBItem {
    my ( $self, $dbItem ) = @_;

    my %properties = (
        product_id        => $dbItem->product_id,
        parent_product_id => $dbItem->parent_product_id,

        #	    album_id            => $dbItem->album_id,
        asset_id               => $dbItem->asset_id,
        product_code           => $dbItem->product_code,
        upc_ean                => $dbItem->upc_ean,
        upc_alt                => $dbItem->upc_alt,
        release_date           => $dbItem->release_date,
        date_created           => $dbItem->date_created,
        date_modified          => $dbItem->date_modified,
        created_by             => $dbItem->created_by,
        modified_by            => $dbItem->modified_by,
        product_type_id        => $dbItem->product_type_id,
        product_price_id       => $dbItem->product_price_id,
        product_status_id      => $dbItem->product_status_id,
        default_price_level_id => $dbItem->default_price_level_id,
        deleted                => $dbItem->deleted,
        deleted_date           => $dbItem->deleted_date,
    );

    #  Derrive the catalog_number, assuming we have an albumID
    #
    if ( RPS::DB::Item::Product::kProductTypeDigitalTrack != $dbItem->product_type_id ) {
        my $albumDBItem = RPS::DB::Item::Album->Lookup( album_id => $dbItem->asset_id );
        $properties{catalog_number} = $albumDBItem->catalog_number;
    }

    # EMI Extension.
    # Add in the 'emi' columns, if in fact they exist...
    #
    my $config = $dbItem->_config();
    if ( defined $config->{emi_release_type_code} ) {
        $properties{emi_release_type_code} = $dbItem->emi_release_type_code();
        $properties{emi_physical_upc}      = $dbItem->emi_physical_upc();
        $properties{emi_related_icpn}      = $dbItem->emi_related_icpn();
        $properties{emi_physical_icpn}     = $dbItem->emi_physical_icpn();
    }

    return $self->_initProperties( \%properties );
}

sub _initWithDefaults {
    my ( $self, $albumID, $catalogNumber ) = @_;

    # !!! This is a little odd - we'll need to set the type correctly.
    # We don't currently display track_products...
    #

    my %defaultProperties = (
        asset_id       => $albumID,
        catalog_number => $catalogNumber,
    );

    return $self->_initProperties( \%defaultProperties );
}

sub _initProperties {
    my ( $self, $prop ) = @_;

    $self->{ProductID} = Common::FormObject::Scalar->new( value => $prop->{product_id}, readOnly => 1 );
    $self->{ParentProductID} = Common::FormObject::Scalar->new( value => $prop->{parent_product_id} );
    $self->{ProductCode} = Common::FormObject::Scalar::String->new( value => $prop->{product_code} );
    $self->{UPC}         = Common::FormObject::Scalar::String->new( value => $prop->{upc_ean}, required => 1, maxlength => 25 );
    $self->{UPCAlt}      = Common::FormObject::Scalar::String->new( value => $prop->{upc_alt}, maxlength => 25 );
    $self->{ReleaseDate} = Common::FormObject::Scalar::Date->new( value => $prop->{release_date} );

    $self->{CreatedDate}  = Common::FormObject::Scalar::DateTime->new( value => $prop->{date_created},  readOnly => 1 );
    $self->{ModifiedDate} = Common::FormObject::Scalar::DateTime->new( value => $prop->{date_modified}, readOnly => 1 );
    $self->{CreatedBy}    = Common::FormObject::Scalar::UserName->new( value => $prop->{created_by},    readOnly => 1 );
    $self->{ModifiedBy}   = Common::FormObject::Scalar::UserName->new( value => $prop->{modified_by},   readOnly => 1 );

    $self->{ProductTypeID}       = Common::FormObject::Scalar->new( value => $prop->{product_type_id}, required => 1 );
    $self->{ProductPriceID}      = Common::FormObject::Scalar->new( value => $prop->{product_price_id} );
    $self->{ProductStatusID}     = Common::FormObject::Scalar->new( value => $prop->{product_status_id}, required => 1 );
    $self->{DefaultPriceLevelID} = Common::FormObject::Scalar->new( value => $prop->{default_price_level_id} );

    $self->{AssetID} = Common::FormObject::Scalar->new( value => $prop->{asset_id}, required => 1 );
    $self->{CatalogNumber} = Common::FormObject::Scalar::String->new( value => $prop->{catalog_number} );
    $self->{Deleted} = Common::FormObject::Scalar->new( value => $prop->{deleted} );
    $self->{DeletedDate} = Common::FormObject::Scalar::Date->new( value => $prop->{deleted_date} );

    # JPK - EMI stuff - These will never be set by forms, so no reason to bother with FormObject::Scalars.
    #
    if ( defined $prop->{emi_release_type_code} ) {
        $self->{EMIReleaseTypeCode} = $prop->{emi_release_type_code};
        $self->{EMIPhysicalUPC}     = $prop->{emi_physical_upc};
        $self->{EMIRelatedICPN}     = $prop->{emi_related_icpn};
        $self->{EMIPhysicalICPN}    = $prop->{emi_physical_icpn};
    }

    if ( $self->loadSubs() ) {
        if ( !defined( $prop->{product_type_id} ) || RPS::DB::Item::Product::kProductTypeDigitalTrack != $prop->{product_type_id} ) {
            $self->{TrackList} = RPS::Product::TrackList->new( productID => $prop->{product_id}, albumID => $prop->{asset_id} );
        }
        $self->{PriceList} = RPS::Product::PriceList->new( productID => $prop->{product_id} );
    }

    return $self;
}

sub validate {
    my $self  = shift;
    my %args  = @_;
    my $valid = 1;

    #my $valid = $self->SUPER::validate( @_ );

    $valid = 0
      unless ( $self->_validateMetadata(%args) );

    return $valid;
}

sub _validateMetadata() {
    my ( $self, %args ) = @_;
    my $validator = $args{metadataValidator};

    my %validation_args;

    my $currentData = RPS::DB::Item::Product->Lookup( product_id => $self->ProductID )
      if ( $self->ProductID );

    my $validationKey;

    my %scalar_rule_map = ();

    my $valid = $self->SUPER::_validateMetadata(
        fieldMap          => \%scalar_rule_map,
        currentData       => $currentData,
        metadataValidator => $validator
    );

    if ( $self->AssetID() ) {

        # Adding some special DA only validation
        if ( $self->ProductTypeID == RPS::DB::Item::Product::kProductTypeDigital ) {

            # Enforcing a limit of 32 for DA products.
            my $daCount;
            if ( $self->ProductID ) {
                $daCount = RPS::DB::Item::Product->GetDACountByAlbumID( $self->AssetID(), $self->ProductID );
            } else {
                $daCount = RPS::DB::Item::Product->GetDACountByAlbumID( $self->AssetID() );
            }

            if ( $daCount >= 32 ) {
                $self->{ProductTypeID}->setError("product_type_id_invalid");
                $self->{ProductTypeID}->setErrorString("limit has been reached.");
                $valid = 0;
            }

            if ( RPS::DB::Item::Product->UPCExistsDA( upc => $self->UPC, albumID => $self->AssetID(), productID => $self->ProductID ) ) {
                $self->{UPC}->setError("upc_inuse");
                $self->{UPC}->setErrorString("in use by another digital album.");
                $valid = 0;
            }
        }

        # And now some validation for physical products
        elsif ( $self->ProductTypeID != RPS::DB::Item::Product::kProductTypeDigitalTrack ) {

            # Enforcing a limit of 1 for physical products.
            my $productTypeExists = RPS::DB::Item::Product->ProductTypeExistsOnAlbum(
                productTypeID => $self->ProductTypeID,
                albumID       => $self->AssetID(),
                productID     => $self->ProductID
            );
            if ($productTypeExists) {
                $self->{ProductTypeID}->setError("product_type_id_invalid");
                $self->{ProductTypeID}->setErrorString("limit has been reached.");
                $valid = 0;
            }
        }
    }

    unless ($validator) {
        Common::Log::Debug( "No validator passed to " . ref($self) . "::_validateMetadata, skipping validation!!!!!" );
        return $valid;
    }

    my $product_track_valid = $self->TrackList->validate(%args);

    # Don't test if data hasn't changed
    return $valid
      if ( ( $self->ProductID && $currentData->upc_ean eq $self->UPC ) && !$validator->{_ignoreCurrentData} );

    my $test = $validator->validate_constraint( 'digital_upc', $self->UPC, 'digital_upc' => $self->UPC );

    if ( !$test->is_valid() ) {
        $self->{UPC}->setError( $test->error_code() );
        $self->{UPC}->setErrorString( $test->error_as_string() );
        $valid = 0;
    }

    # If the UPC is valid then we want to see if any other products are using
    # the same UPC.  However, we do allow duplicate UPC is the are for the same
    # album, but a different product type.
    if ( $self->AssetID() ) {
        if ( $self->ProductTypeID == RPS::DB::Item::Product::kProductTypeDigitalTrack ) {
            $validation_args{trackID} = $self->AssetID();
        } else {
            $validation_args{albumID} = $self->AssetID();
        }
    } else {
        $validation_args{albumID} = $args{assetID};
    }

    use Data::Dumper;
    Common::Log::Debug( "VALID: " . Dumper \%validation_args );

    if ( RPS::DB::Item::Product->UPCExists( upc => $self->UPC, %validation_args ) ) {
        $self->{UPC}->setError("upc_inuse");
        $self->{UPC}->setErrorString("in use by another album.");
        $valid = 0;
    }

    if ( !$self->UPC ) {
        $self->{UPC}->setError("upc_required");
        $self->{UPC}->setErrorString("UPC Required");
        $valid = 0;
    }

    if ( !Validate::Util::is_valid_upc_or_ean( $self->UPC ) ) {
        $self->{UPC}->setError("upc_invlid");
        $self->{UPC}->setErrorString("is not valid");
        $valid = 0;
    }

    if ( $valid && $product_track_valid ) {
        Common::Log::Debug("Product _validateMetadata Passed All Tests.");
    } else {
        Common::Log::Debug("Product _validateMetadata Failed.");
    }

    #die "Validate: " . $self->{ReleaseDate}->validate() . " ReleaseDate: '" . "$self->{ReleaseDate}->{_value}" . "'";
    # Couldn't do this with standard metadata validation because we changed the
    # release date metadata validation for the importer.  Only a failure if we are
    # Checking for distribution
    if (   $self->ProductTypeID == RPS::DB::Item::Product::kProductTypeDigital
        && ( !$self->{ReleaseDate}->_getValue() || !$self->{ReleaseDate}->validate )
        && $validator->{_failState} == 0 ) {
        $self->{ReleaseDate} = Common::FormObject::Scalar::Date->new();
        $self->{ReleaseDate}->setError("release_date_required");
        $self->{ReleaseDate}->setErrorString("Required for distribution");
        $valid = 0;
    }

    Common::Log::Debug("Product Valid: $valid, Product Track Valid: $product_track_valid");
    return $valid && $product_track_valid;
}

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

    # If we don't have a ProductID, then this is a _new_ product...
    # ProductID is given to us by the database.
    #
    my $dbObj;
    my $dbObjProdDig;
    my $createProductDigital = 0;
    if ( $self->ProductID() ) {
        $dbObj = RPS::DB::Item::Product->Lookup( product_id => $self->ProductID() );
    } else {
        $dbObj = RPS::DB::Item::Product->Create();

        ## if we're creating a digital album product, need to also
        ## create an entry in the product_digital table!
        if ( $self->ProductTypeID() == 3 ) {
            $createProductDigital = 1;
            $dbObjProdDig         = RPS::DB::Item::ProductDigital->Create();
            $dbObjProdDig->product_id( $self->ProductID() );
            $dbObjProdDig->save();
        }

    }

    # The interface uses catalog number.
    # Internally, we use album_id.
    # I don't think we will need to determine the album_id in this routine -
    # that should already have been discovered.
    #
    #	$dbObj->catalog_number($self->CatalogNumber());

    #
    # !!! Some of these fields will require special encoding...
    #
    $dbObj->product_code( $self->ProductCode() );

    #	$dbObj->album_id($self->AlbumID());
    $dbObj->asset_id( $self->AssetID() );
    $dbObj->upc_ean( $self->UPC() );
    $dbObj->upc_alt( $self->UPCAlt() );
    $dbObj->release_date( $self->ReleaseDate() );
    $dbObj->product_type_id( $self->ProductTypeID() );
    $dbObj->product_price_id( $self->ProductPriceID() );
    $dbObj->product_status_id( $self->ProductStatusID() );
    $dbObj->default_price_level_id( $self->DefaultPriceLevelID() );
    $dbObj->deleted( $self->Deleted() );
    $dbObj->deleted_date( $self->DeletedDate() );

    # Did anything change?  Then save it!
    #
    if ( $dbObj->isDirty() ) {
        $dbObj->save();

        # reload object... but don't reload the subs. We'll be dealing with
        # that later.
        #
        $self->loadSubs(0);
        $self->_initProperties($dbObj);
    }

    if ( $createProductDigital == 1 ) {
        $dbObjProdDig->product_id( $self->ProductID() );
        $dbObjProdDig->save();
    }

    # Now, save the prices...
    # But first, we need to set the ProductID
    #
    my $prices = $self->{PriceList}->getPrices();
    foreach my $price (@$prices) {
        $price->ProductID( $self->ProductID() );
    }
    $self->{PriceList}->save();

    # Now, save the track list..
    # But first, we need to set the ProductID
    #
    my $tracks = $self->{TrackList}->getTracks();
    foreach my $track (@$tracks) {
        $track->ProductID( $self->ProductID() );
    }
    $self->{TrackList}->save();

    # !!! Be careful here, this data is never surfaced in the UI
    # so if something's wrong you won't know unless you look in the db. -jff-
    #
    # ok, let's handle those digital track products (the unseen, unheard products)
    #
    if ( RPS::DB::Item::Product::kProductTypeDigital == $dbObj->product_type_id ) {
        $tracks = $self->{TrackList}->getTracks();
        foreach my $trk (@$tracks) {

            # if this track is included in the digital product then we
            # need to have a digital track product for it.
            #
            my $trkProd = RPS::DB::Item::Product->GetDigitalTrackProduct( $trk->TrackID(), $dbObj->product_id );
            if ( $trk->{_inDB} ) {

                # does one already exist?
                #
                if ( !defined $trkProd ) {

                    # if not, then create one here
                    #
                    $trkProd = RPS::DB::Item::Product->Create(
                        asset_id          => $trk->TrackID(),
                        product_type_id   => RPS::DB::Item::Product::kProductTypeDigitalTrack(),
                        parent_product_id => $dbObj->product_id
                    );
                }

                # let's sync up the track product with the main digital product
                #
                # same as the digital album product
                #
                $trkProd->product_code( $dbObj->product_code );
                $trkProd->upc_ean( $dbObj->upc_ean );
                $trkProd->release_date( $dbObj->release_date );
                $trkProd->product_price_id( $dbObj->product_price_id );
                $trkProd->product_status_id( $dbObj->product_status_id );

                # save it
                #
                $trkProd->save();
            } else {

                # this track is not included in this digital product so we
                # need to make sure a track product doesn't exist either.
                #
                # does this track product exist?
                #
                # JPK - !!! NO WAY !!! We can't just nuke a product, it may have been matched to a sale!
                #				if($trkProd)
                #				{
                #					# ok, let's delete it
                #					#
                #                    assert(0, "WHAT THE FUCK, DUDE?");
                #					$trkProd->delete();
                #				}
            }
        }
    }
}

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

    return $self->{TrackList};
}

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

    # check the track_licenses for licenses that
    # are attached to specific products,
    # these products cannot be deleted
    #
    # 1. get the album
    # 2. get all track on the album
    # 3. get all track_licenses for each track
    # 4. lookup the license for each track_license
    # 5. if license.product_id matches this product then DONT DELETE
    #
    # this is a hairy process to go through :-0

    # we need to delete the track list
    #
    foreach my $trkProduct ( @{ $self->{TrackList}->getTracks() } ) {

        # !!! This looks totally redundant to me
        # !!! If I call 'delete' on a track product, why do I need to bother with this GetDigitalTrackProduct stuff?
        # !!! Has this code ever actually _run_?
        # jpk - TrackList contains RPS::Product::Track objects.  These are different - may or may not map to a product.
        #

        # delete digital track products too
        #
        if ( RPS::DB::Item::Product::kProductTypeDigital == $self->ProductTypeID() ) {
            my $trkProd = RPS::DB::Item::Product->GetDigitalTrackProduct( $trkProduct->TrackID(), $self->ProductID() );
            $trkProd->delete if ($trkProd);
        }

        $trkProduct->delete();
    }

    # delete this product
    #
    my $product = RPS::DB::Item::Product->Lookup( product_id => $self->ProductID() );
    $product->delete();

    return 1;
}

sub requeueDelivery {
    my $self       = shift;
    my @serviceIDs = @_;

    foreach my $serviceID (@serviceIDs) {
        my $productService = new RPS::Product::ProductDistribution(
            product_id => $self->ProductID,
            service_id => $serviceID
        );

        if ($productService) {
            $productService->invalidateDistribution();
            $productService->forceStageDistribution();
        }
    }
}

sub takedownDelivery {
    my $self       = shift;
    my @serviceIDs = @_;

    foreach my $serviceID (@serviceIDs) {
        my $productService = new RPS::Product::ProductDistribution(
            product_id => $self->ProductID,
            service_id => $serviceID
        );

        $productService->invalidateDistribution();
    }
}

1;
