#------------------------------------------------------------
# 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/raptor/lib';
use Raptor::DB::Item::ProductInputMap;

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

    $self->{_page}     = $args{page};
    $self->{_pageSize} = $args{pageSize};
    $self->{_filterTrackList} = $args{filterTrackList};

    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,
        asset_id               => $dbItem->asset_id,
        title                  => $dbItem->title,
        title_clean            => $dbItem->title_clean,
        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,
    );

    #  Derive the catalog_number, assuming we have an albumID
    #  Might as well grab the album name too.
    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;
        $properties{album_name}     = $albumDBItem->title;
        $properties{artist_id}      = $albumDBItem->artist_id;

    }

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

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

    # Need to look up the album title to use as a default for the product title.
    my $title;
    if ($albumID) {
        my $albumDBItem = RPS::DB::Item::Album->Lookup( album_id => $albumID );
        $title = $albumDBItem->title;
    }

    my %defaultProperties = (
        title          => $title,
        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->{Title} = Common::FormObject::Scalar::String->new( value => $prop->{title}, required => 1, maxlength => 255 );
    $self->{TitleClean} = Common::FormObject::Scalar::String->new( value => $prop->{title_clean}, readOnly => 1 );
    $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->{AlbumName} = Common::FormObject::Scalar::String->new( value => $prop->{album_name}, readOnly => 1 );
    $self->{Deleted} = Common::FormObject::Scalar->new( value => $prop->{deleted} );
    $self->{DeletedDate} = Common::FormObject::Scalar::Date->new( value => $prop->{deleted_date} );

    # We need to fetch the artist name, given an artist id.
    #
    my $artistName;
    if ( $prop->{artist_id} ) {
        my $artistDBItem = RPS::DB::Item::Artist->Lookup( artist_id => $prop->{artist_id} );
        $artistName = $artistDBItem->name;
    }
    $self->{ArtistName} = Common::FormObject::Scalar::String->new( value => $artistName );

    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},
                page      => $self->{_page},
                pageSize  => $self->{_pageSize},
                filterTrackList => $self->{_filterTrackList}
            );
        }
        $self->{PriceList} = RPS::Product::PriceList->new( productID => $prop->{product_id} );
    }

    return $self;
}

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

    my $valid = 1;

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

    # checking required product fields
    unless ( $self->ProductTypeID ) {
        $self->{ProductTypeID}->setError("product_type_id_invalid");
        $self->{ProductTypeID}->setErrorString("must be selected.");
        $valid = 0;
    }

    unless ( $self->Title ) {
        $self->{Title}->setError( "title_required" );
        $self->{Title}->setErrorString( "is required." );
        $valid = 0;
    }

    unless ( $self->UPC ) {
        $self->{UPC}->setError( "upc_required" );
        $self->{UPC}->setErrorString( "is required." );
        $valid = 0;
    }

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

        }
    }

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

    }

    #
    # !!! Some of these fields will require special encoding...
    #
    $dbObj->product_code( $self->ProductCode() );
    $dbObj->asset_id( $self->AssetID() );
    $dbObj->title( $self->Title() );
    $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() );

    # Derrive the clean title from title
    #
    $dbObj->title_clean( Common::Util::clean_name_catalog( $self->Title() ) );

    # 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
        # and product_input_map rows
        if ( RPS::DB::Item::Product::kProductTypeDigital == $self->ProductTypeID() ) {
            my $trkProd = RPS::DB::Item::Product->GetDigitalTrackProduct( $trkProduct->TrackID(), $self->ProductID() );
            if ($trkProd) {
                Raptor::DB::Item::ProductInputMap->Delete( $trkProd->product_id );
                $trkProd->delete();
            }
        }

        $trkProduct->delete();
    }

    # delete this product
    # and any product_input_map rows
    my $product = RPS::DB::Item::Product->Lookup( product_id => $self->ProductID() );
    $product->delete();
    Raptor::DB::Item::ProductInputMap->Delete( $self->ProductID() );

    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;
