#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#------------------------------------------------------------
package RPS::Publisher::US::Publisher;

use strict;
use warnings;
use Carp;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::Timer;
use Common::FormObject;
use Common::DB::Item::PayeeType;

use lib '/app/tools/api/lib';
use API::DB::Item::PortalInvite;
use API::DB::Item::PortalPayee;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::Publisher;
use RPS::DB::Item::PublisherStatementRecipient;
use RPS::DB::Item::PublisherIndirectBalanceAccount;
use RPS::DB::Item::Payor;
use RPS::Publisher::US::PublisherAccount;
use RPS::Publisher::US::IndirectPublisherAccount;
use RPS::DB::Item::FormDefaults;
use RPS::Publisher::US::PublisherSimple;
use RPS::Portal::SendInvite;

use base 'RPS::Publisher::US::PublisherSimple';

my $DISTRIBUTION_MANUAL = RPS::DB::Item::Publisher::kStatementDistributionManual;
my $DISTRIBUTION_ONLINE = RPS::DB::Item::Publisher::kStatementDistributionOnline;
my $PAYEE_TYPE          = Common::DB::Item::PayeeType::kPayeeTypePublisherUS;

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

    my $timer = Common::Timer->new();

    # First, call the inherited method to get it's fields.
    $self->SUPER::_propertiesFromDBItem( $hrProperties, $dbItem );

    # Now we fill in the rest of the stuff.
    $hrProperties->{street_address}    = $dbItem->street_address;
    $hrProperties->{street_address_2}  = $dbItem->street_address_2;
    $hrProperties->{street_address_3}  = $dbItem->street_address_3;
    $hrProperties->{city}              = $dbItem->city;
    $hrProperties->{state_province}    = $dbItem->state_province;
    $hrProperties->{country_code}      = $dbItem->country_code;
    $hrProperties->{postal_code}       = $dbItem->postal_code;
    $hrProperties->{email}             = $dbItem->email;
    $hrProperties->{phone_number}      = $dbItem->phone_number;
    $hrProperties->{fax_number}        = $dbItem->fax_number;
    $hrProperties->{tax_id}            = $dbItem->tax_id;
    $hrProperties->{statement_type_id} = $dbItem->statement_type_id;
    $hrProperties->{affiliate_id}      = $dbItem->affiliate_id;
    $hrProperties->{cycle_code}        = $dbItem->cycle_code;
    $hrProperties->{min_payment}       = $dbItem->min_payment;
    $hrProperties->{begin_month_code}  = $dbItem->begin_month_code;
    $hrProperties->{comments}          = $dbItem->comments;
    $hrProperties->{client_account_id} = $dbItem->client_account_id;
    $hrProperties->{status}            = $dbItem->status;
    $hrProperties->{distribution}      = $dbItem->distribution;
    $hrProperties->{date_invited}      = $dbItem->date_invited;
    $hrProperties->{date_confirmed}    = $dbItem->date_confirmed;
    $hrProperties->{date_created}      = $dbItem->date_created;
    $hrProperties->{date_modified}     = $dbItem->date_modified;
    $hrProperties->{created_by}        = $dbItem->created_by;
    $hrProperties->{modified_by}       = $dbItem->modified_by;
}

sub _initProperties {
    my ( $self, $props ) = @_;
    my $timer = Common::Timer->new();

    # Call the inherited method first, to let it get the first crack at this.
    $self->SUPER::_initProperties($props);

    # Now finish the job.
    $self->{StreetAddress}    = Common::FormObject::Scalar::String->new( value => $props->{street_address} );
    $self->{StreetAddress2}   = Common::FormObject::Scalar::String->new( value => $props->{street_address_2} );
    $self->{StreetAddress3}   = Common::FormObject::Scalar::String->new( value => $props->{street_address_3} );
    $self->{City}             = Common::FormObject::Scalar::String->new( value => $props->{city}, maxlength => 80 );
    $self->{StateProvince}    = Common::FormObject::Scalar::String->new( value => $props->{state_province}, maxlength => 80 );
    $self->{CountryCode}      = Common::FormObject::Scalar::String->new( value => $props->{country_code} );
    $self->{PostalCode}       = Common::FormObject::Scalar::String->new( value => $props->{postal_code}, maxlength => 15 );
    $self->{Email}            = Common::FormObject::Scalar::EmailAddress->new( value => $props->{email}, maxlength => 80 );
    $self->{OrigEmail}        = Common::FormObject::Scalar::EmailAddress->new( value => $props->{email}, maxlength => 80 );
    $self->{PhoneNumber}      = Common::FormObject::Scalar::PhoneNumber->new( value => $props->{phone_number}, maxlength => 30 );
    $self->{FaxNumber}        = Common::FormObject::Scalar::String->new( value => $props->{fax_number}, maxlength => 30 );
    $self->{TaxID}            = Common::FormObject::Scalar::String->new( value => $props->{tax_id},     maxlength => 30 );
    $self->{StatementTypeID}  = Common::FormObject::Scalar->new( value => $props->{statement_type_id} );
    $self->{AffiliateID}      = Common::FormObject::Scalar->new( value => $props->{affiliate_id} );
    $self->{CycleCode}        = Common::FormObject::Scalar->new( value => $props->{cycle_code} );
    $self->{MinPayment}       = Common::FormObject::Scalar::Money->new( value => $props->{min_payment} );
    $self->{BeginMonthCode}   = Common::FormObject::Scalar->new( value => $props->{begin_month_code} );
    $self->{Comments}         = Common::FormObject::Scalar::String->new( value => $props->{comments}, maxlength => 0 );
    $self->{ClientAccountID}  = Common::FormObject::Scalar::String->new( value => $props->{client_account_id} );
    $self->{Status}           = Common::FormObject::Scalar->new( value => $props->{status} );
    $self->{Distribution}     = Common::FormObject::Scalar->new( value => $props->{distribution} );
    $self->{OrigDistribution} = Common::FormObject::Scalar->new( value => $props->{distribution} );
    $self->{DateInvited}      = Common::FormObject::Scalar::DateTime->new( value => $props->{date_invited},   readOnly => 1 );
    $self->{DateConfirmed}    = Common::FormObject::Scalar::DateTime->new( value => $props->{date_confirmed}, readOnly => 1 );
    $self->{CreatedDate}      = Common::FormObject::Scalar::DateTime->new( value => $props->{date_created},  readOnly => 1 );
    $self->{ModifiedDate}     = Common::FormObject::Scalar::DateTime->new( value => $props->{date_modified}, readOnly => 1 );
    $self->{CreatedBy}        = Common::FormObject::Scalar::UserName->new( value => $props->{created_by},    readOnly => 1 );
    $self->{ModifiedBy}       = Common::FormObject::Scalar::UserName->new( value => $props->{modified_by},   readOnly => 1 );

    # We want to show the number of publishers that
    # have this publisher as an admin or as an agent
    my $adminFor = 0;
    my $agentFor = 0;
    my $coll;
    $coll = RPS::DB::Item::Publisher->GetByAdminID( $props->{publisher_id} );
    $adminFor = ( defined $coll ) ? $coll->size : 0;

    $coll = RPS::DB::Item::Publisher->GetByAgentID( $props->{publisher_id} );
    $agentFor = ( defined $coll ) ? $coll->size : 0;

    $self->{AdminFor} = Common::FormObject::Scalar->new( value => $adminFor, readOnly => 1 );
    $self->{AgentFor} = Common::FormObject::Scalar->new( value => $agentFor, readOnly => 1 );
    $self->{UpdateAttachedPublishers} = Common::FormObject::Scalar::Boolean->new();

    if ( $self->loadSubs() ) {
        if ( defined $props->{publisher_id} ) {
            my $accounts = RPS::DB::Item::PublisherAccount->GetByPublisherID( $props->{publisher_id} );
            while ( my $account = $accounts->next() ) {
                push @{ $self->{Account} }, RPS::Publisher::US::PublisherAccount->new( _dbItem => $account, readOnly => 1 );
            }

            my $indirectAccounts = RPS::DB::Item::PublisherIndirectBalanceAccount->GetByPublisherID( $props->{publisher_id} );
            while ( my $indirectAccount = $indirectAccounts->next() ) {
                push @{ $self->{IndirectAccount} },
                  RPS::Publisher::US::IndirectPublisherAccount->new( _dbItem => $indirectAccount, readOnly => 1 );
            }

            $self->{RelatedPublisherItems} = RPS::DB::Item::Publisher->IsNotManual($props->{publisher_id})
        }
    }

    # A Payee Portal stuff
    $self->_canInvitePublisher($props);
    $self->_calcStatementRecipients($props);

    return $self;
}

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

    my $valid = $self->SUPER::validate();
    $valid = $self->_validateEmailForOnlineDistribution($valid);
    $valid = $self->_validateUSStatesProvince($valid);

    return $valid;
}

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

    my $createFlag = 0;

    my $publisherDBObj;
    if ( $self->PublisherID() ) {
        $publisherDBObj = RPS::DB::Item::Publisher->Lookup( publisher_id => $self->PublisherID() );
    } else {
        $createFlag     = 1;
        $publisherDBObj = RPS::DB::Item::Publisher->Create();
    }

    my $wasAgency = $publisherDBObj->is_agency;
    my $wasAdmin  = $publisherDBObj->is_admin;

    # an agent cannot have an admin/agent set
    if ( $self->IsAgency() ) {
        $self->AgentID(undef);
        $self->AdminID(undef);
    }

    # an admin cannot have an admin set
    if ( $self->IsAdmin() ) {
        $self->AdminID(undef);
    }

    # set fields here
    $publisherDBObj->publisher_name( $self->PublisherName() );
    $publisherDBObj->is_agency( $self->IsAgency() );
    $publisherDBObj->is_admin( $self->IsAdmin() );
    $publisherDBObj->street_address( $self->StreetAddress() );
    $publisherDBObj->street_address_2( $self->StreetAddress2() );
    $publisherDBObj->street_address_3( $self->StreetAddress3() );
    $publisherDBObj->city( $self->City() );
    $publisherDBObj->state_province( $self->StateProvince() );
    $publisherDBObj->country_code( $self->CountryCode() );
    $publisherDBObj->postal_code( $self->PostalCode() );
    $publisherDBObj->email( $self->Email() );
    $publisherDBObj->phone_number( $self->PhoneNumber() );
    $publisherDBObj->fax_number( $self->FaxNumber() );
    $publisherDBObj->tax_id( $self->TaxID() );
    $publisherDBObj->agent_id( $self->AgentID() );
    $publisherDBObj->admin_id( $self->AdminID() );
    $publisherDBObj->statement_type_id( $self->StatementTypeID() );
    $publisherDBObj->affiliate_id( $self->AffiliateID() );
    $publisherDBObj->cycle_code( $self->CycleCode() );
    $publisherDBObj->min_payment( $self->MinPayment() );
    $publisherDBObj->begin_month_code( $self->BeginMonthCode() );
    $publisherDBObj->comments( $self->Comments() );
    $publisherDBObj->client_account_id( $self->ClientAccountID() );
    $publisherDBObj->status( $self->Status() );
    $publisherDBObj->distribution( $self->Distribution() );

    # If we're going to send out an invite, clear the invite date.
    # It will be reset when the invite is sent out.
    my $sendInviteFlag = $self->_needToSendInvite();
    if ( $sendInviteFlag ) {
        $publisherDBObj->date_invited(undef);
        $publisherDBObj->date_confirmed(undef);
    }

    # Do not update if Invite had been confirmed or Payee has recipients.
    if ( !$publisherDBObj->date_confirmed && !$self->{RecipientsAssigned} ) {
         $publisherDBObj->distribution( $self->Distribution() );
    }

    $publisherDBObj->save();


    my $publisherID = $publisherDBObj->publisher_id;
    $self->_processPublisherMinPayment(publisher_id => $publisherID);
    $self->_sendInvite($publisherID) if $sendInviteFlag;

    # ----------------------------------------------------------------
    # Some clean-up code used to support "fixing" a user error where
    # they mistakenly create a publisher as an agent (or admin), then
    # attach a bunch of publishers to it, then realize this publisher
    # really should have been an admin (or agent).
    # ----------------------------------------------------------------
    #
    # if we changed from being an admin or agent then
    # we need to fix any publishers that are linked to this
    # publisher via agent_id or admin_id. we will skip ones
    # we can't resolve. UupdateAttachedPublishers is a checkbox
    # the user checks to indicate they want this "fix" to occur.
    #
    # this handles the two cases:
    # 	1. Was Agency or Admin and switched to Agency or Admin
    # 	2. Was Agency or Admin is now neither
    #
    if ( $self->UpdateAttachedPublishers() ) {
        my $collection;

        # Publisher was Admin or Agent and is now neither:
        # remove this agent/admin from all attached publishers
        if ( !$self->IsAgency() && !$self->IsAdmin() ) {
            if ($wasAgency) {

                # get all publishers that have this Publisher set as their Agent
                #
                $collection = RPS::DB::Item::Publisher->GetByAgentID( $self->PublisherID() );
                while ( $collection->hasNext() ) {
                    my $dbItem = $collection->next();
                    $dbItem->agent_id(undef);
                    $dbItem->save();
                }
            } elsif ($wasAdmin) {

                # get all publishers that have this Publisher set as their Admin
                #
                $collection = RPS::DB::Item::Publisher->GetByAdminID( $self->PublisherID() );
                while ( $collection->hasNext() ) {
                    my $dbItem = $collection->next();
                    $dbItem->admin_id(undef);
                    $dbItem->save();
                }
            }
        }

        # Publisher was Agency, is now Admin:
        # switch attached publishers to use this as an admin,
        # unless they already have an admin
        elsif ( $wasAgency && $self->IsAdmin() ) {

            # get all publishers that have this Publisher set as their Agent
            $collection = RPS::DB::Item::Publisher->GetByAgentID( $self->PublisherID() );
            while ( $collection->hasNext() ) {
                my $dbItem = $collection->next();
                if ( !$dbItem->admin_id ) {
                    $dbItem->agent_id(undef);
                    $dbItem->admin_id( $self->PublisherID() );
                    $dbItem->save();
                }
            }
        }

        # Publisher was Admin, is now Agent:
        # switch attached publishers to use this as an agent,
        # unless they already have an agent
        elsif ( $wasAdmin && $self->IsAgency() ) {

            # get all publishers that have this Publisher set as their Admin
            $collection = RPS::DB::Item::Publisher->GetByAdminID( $self->PublisherID() );
            while ( $collection->hasNext() ) {
                my $dbItem = $collection->next();
                if ( !$dbItem->agent_id ) {
                    $dbItem->admin_id(undef);
                    $dbItem->agent_id( $self->PublisherID() );
                    $dbItem->save();
                }
            }
        }
    }

    # reload object
    $self->_init( _dbItem => $publisherDBObj );
}

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

    # Take some (not necessarily all) of these fields and make them defaults.
    #
    # Note that this does _not_ imply that we are actually saving this form's data.
    # Just that we are saving it's stuff as a default.
    # (We need to apply this to the liquidation list, too, I'm thinking...)
    #
    my %newDefaults;
    $newDefaults{cycle_code}  = $self->CycleCode();
    $newDefaults{min_payment} = $self->MinPayment();

    # Save these new defaults.
    #
    RPS::DB::Item::FormDefaults::ReplaceFormPropertyValues( 'RPS::Publisher::US::Publisher', \%newDefaults );
}

# Accessors
#
sub Account {
    my $self = shift @_;
    if ( $self->{Advance} ) {
        return $self->{Advance}{AdvanceAccount};
    } else {
        return undef;
    }
}

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

    my $publisher_id = $args{publisher_id} || 0;
    (Common::Log::Print("Unable to set Publisher Min Payment due to missed publisher ID") and return undef) unless $publisher_id;

    my $payors = RPS::DB::Item::Payor->GetPayorsWithPublisherMinPayment();

    while ($payors->hasNext()) {
        my $payor = $payors->next;
        $self->{PublisherAccount} = RPS::Publisher::US::PublisherAccount->new( publisherID => $publisher_id, payorID => $payor->{payor_id} );
        $self->{PublisherAccount}->{MinPayment} = $payor->{us_publisher_min_payment};
        $self->{PublisherAccount}->save;
    }
}

sub _canInvitePublisher {
    my ($self, $hProps) = @_;

    $self->{CanInvite} = 0;

    if ( $hProps->{date_invited} ) {
        $self->{CanInvite} = RPS::Portal::SendInvite::_checkDateInvited( $hProps->{date_invited} );
    }

    return $self->{CanInvite};
}

sub _calcStatementRecipients {
    my ($self, $hProps) = @_;

    $self->{RecipientsAssigned}  = 0;
    $self->{RecipientsConfirmed} = 0;
    return unless $hProps->{publisher_id};

    my $oRecipients = RPS::DB::Item::PublisherStatementRecipient->GetByPublisherID( $hProps->{publisher_id} );
    while ( $oRecipients && $oRecipients->hasNext() ) {
        $self->{RecipientsConfirmed}++ if $oRecipients->next()->date_confirmed;
        $self->{RecipientsAssigned}++;
    }

    return 1;
}

sub _needToSendInvite {
	my $self = shift;

	my $inviteFlag = 0;

	# New publisher
	if ( !$self->PublisherID ) {
		$inviteFlag = $self->Distribution() == $DISTRIBUTION_ONLINE ? 1 : 0;
	}

	# Existing publisher
    # During a publisher update, we'll send out an invite under the following conditions:
    #   - The distribution status has changed from manual to online; or
    #   - The email address has been changed
	if ( $self->Distribution() == $DISTRIBUTION_ONLINE ) {
        if ( $self->OrigDistribution() == $DISTRIBUTION_MANUAL ) {
            Common::Log::Print("Publisher.save: publisher update, distributioin status");
            $inviteFlag = 1;
        }

        if ( $self->Email() ne $self->OrigEmail() ) {
            $self->_deleteInvite( $self->OrigEmail() );
            $self->_deletePortalPayee( $self->OrigEmail() );
            Common::Log::Print("Publisher.save: publisher update, email changed");
            $inviteFlag = 1;
        }
	}

	if ( $self->Distribution() == $DISTRIBUTION_MANUAL ) {
		if ( $self->OrigDistribution() == $DISTRIBUTION_ONLINE ) {
			if ( !$self->_deleteInvite( $self->Email() ) ) {
				Common::Log::Print(
					"Publisher.save: distribution changed from online to manual, but confirmed invite exists for old email");
			}
		}
	}

	return $inviteFlag;
}

sub _sendInvite {
    my ($self, $publisherID) = @_;

    RPS::Portal::SendInvite->_sendInvite(
        payee_id   => $publisherID,
        payee_type => $PAYEE_TYPE
    );
    Common::Log::Print( "Publisher.save: Sending invite to publisherID $publisherID" );

    return 1;
}

sub _deleteInvite {
    my ($self, $email) = @_;

    my $oInvites = API::DB::Item::PortalInvite->GetByInvitee($email);
    while ( $oInvites->hasNext() ) {
        my $oInvite = $oInvites->next();
        if (   $oInvite->client_id == Common::RSApp::GetClientID()
            && $oInvite->payee_id == $self->PublisherID
            && $oInvite->payee_type == $PAYEE_TYPE ) {

            $oInvite->delete;
            return 1;
        }
    }

    return 0;
}

sub _deletePortalPayee {
    my ($self, $email) = @_;

    my $oPortalPayees = API::DB::Item::PortalPayee->GetByInvitee($email);
    while ( $oPortalPayees->hasNext() ) {
        my $oPortalPayee = $oPortalPayees->next();
        if (   $oPortalPayee->client_id == Common::RSApp::GetClientID()
            && $oPortalPayee->payee_id == $self->PublisherID
            && $oPortalPayee->payee_type == $PAYEE_TYPE ) {

            $oPortalPayee->delete;
            return 1;
        }
    }

    return 0;
}


sub _validateEmailForOnlineDistribution {
    my ($self, $valid) = @_;

    if ( $self->Distribution() == $DISTRIBUTION_ONLINE && !$self->Email() ) {
        $self->{Email}->setError(Common::FormObject::kErrRequiredForDistribution);
        $valid = 0;
    }

    # Harry Fox Agency is not supported at this time
    if ( $self->Distribution() == $DISTRIBUTION_ONLINE && $self->Email() =~ /harryfox\.com$/i ) {
        $self->{Email}->setError(Common::FormObject::kErrEmailUnsupported);
        $valid = 0;
    }

    return $valid;
}

sub _validateUSStatesProvince {
    my ($self, $valid) = @_;

    if ( $self->StateProvince() && $self->CountryCode() ) {
        if ( $self->CountryCode() eq 'US' ) {
            if ( !RPS::StateList::IsValidState( $self->StateProvince() ) ) {
                $self->{StateProvince}->setError(Common::FormObject::kErrInvalidState);
                $valid = 0;
            }
        }
    }

    return $valid;
}

1;
