package API::Command::GetHistoricalStatements;
use strict;
use warnings;

use Apache2::Const qw(OK FORBIDDEN HTTP_OK HTTP_UNAUTHORIZED HTTP_NOT_FOUND HTTP_NOT_IMPLEMENTED);

use File::Temp;

use lib '/app/tools/common/lib';
use Common::RSApp;
use RSApache::Response::DownloadZipDirectory;
use Common::DB::Item::PayeeType;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::ArtistRoyaltyRun;
use RPS::DB::Item::MechanicalStatement;
use RPS::Statement::Mechanical::US::PDF;
use RPS::Statement::Mechanical::US::Text;

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


use base 'API::Command::StatementBase';

sub area { return "historical"; }
sub cmd  { return "statement"; }


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

    my $response = $self->SUPER::execute();
    return $response if $response;

    my $runType = $self->getRouteParam(":runTypeId") || '';

    # Optional filter parameters.  If omitted, we'll search for all historical
    # statements that the portal user has access to.
    my $filterClientID = $self->getParam("client") || 0;
    my $filterRunID    = $self->getParam("run")    || 0;
    my $filterPayorID  = $self->getParam("payor")  || 0;
    my $filterPayeeID  = $self->getParam("payee")  || 0;

    # This module can return statement data via JSON or a download zip file.
    # The same basic algorithm is used for gathering historical data, so
    # we'll use 'downloadMode' to identify those areas of code specifically
    # for setting up a download or for returning statement data via JSON.
    my $downloadMode = $ENV{REQUEST_URI} =~ /download/i ? 1 : 0;

    my $errorResponse = $self->_validate($runType);
    return $errorResponse if $errorResponse;

    $ENV{HOME}    = '/';                                       # TODO: move this to the download function
    my $userID    = $self->{userID};                           # derived from session (see SUPER::execute)
    my $payeeType = $self->getPayeeTypeByRunType($runType);    # convert runType to payeeType

    # The following is used to detect if an invalid filter parameter was used.
    my $foundClient;

    my %clientMap;
    my %runMap;
    my %payorMap;
    my %payeeMap;
    my @clientList;
    my %portalPayeeMap;

    my $portalCollection = API::DB::Item::PortalPayee->GetAllConfirmedByUserIDPayeeType($userID, $payeeType);
    while ( $portalCollection->hasNext() ) {
        my $oPortalPayee = $portalCollection->next();

        my $dateConfirmed     = $oPortalPayee->date_confirmed;
        my $_clientID         = $oPortalPayee->client_id;
        my $_payeeID          = $oPortalPayee->payee_id;
        my $_payeeRecipientID = $oPortalPayee->statement_recipient_id;

        $clientMap{$_clientID} = 1;
        $foundClient = 1 if $filterClientID == $_clientID;

        push @clientList, join( '-', $_clientID, $_payeeID, $_payeeRecipientID );
        $portalPayeeMap{$runType}{$_clientID}{$_payeeID}{$_payeeRecipientID} = $oPortalPayee;
    }


    if ( $filterClientID && !$foundClient ) {
        return $self->createResponse(
            status => HTTP_UNAUTHORIZED,
            data   => { msg => "invalid client filter" }
        );
    }

    # If multiple clients are available then you must use the client
    # filter in addition to any other filters.  Otherwise any payee/payor/run
    # filters will be used with the available client.
    my $filterClientRequired = ( keys %clientMap > 1 ) ? 1 : 0;
    if (   ( $filterPayeeID || $filterPayorID || $filterRunID )
        && !$filterClientID
        && $filterClientRequired ) {
        return $self->createResponse(
            status => HTTP_UNAUTHORIZED,
            data   => { msg => "client filter required" }
        );
    }

    # Make sure we have a valid filter payee
    if ( $filterPayeeID ) {
        my $valid = 0;
        my $_clientID = ($filterClientRequired) ? $filterClientID : keys %clientMap;

        foreach my $clientPayee (@clientList) {
            my ( $clientID, $payeeID ) = split( '-', $clientPayee );
            $valid = 1
              if ( ( ( $filterClientRequired && $filterClientID == $clientID ) || !$filterClientRequired ) && $filterPayeeID == $payeeID );
        }
        if ( !$valid ) {
            return $self->createResponse(
                status => HTTP_UNAUTHORIZED,
                data   => { msg => "invalid payee filter" }
            );
        }
    }

    my $tempdir;
    my $numFiles = 0;
    if ( $downloadMode ) {
        $tempdir = File::Temp::tempdir( "/tmp/" . $runType . "_$$" . "XXXXXXXX", CLEANUP => 1 );
    }

    my %historicalStatements;
    my $hClients = {};
    foreach my $clientPayee (@clientList) {
        my ( $clientID, $payeeID, $payeeRecipientID ) = split( '-', $clientPayee );

        my $appSingleton = Common::RSApp->new_temporary( clientID => $clientID );
        my $dbo          = Common::RSApp::GetClientDB();

        my $hClientData  = $self->_initClientData( $hClients, $clientID );
        my $oClient      = Common::DB::Item::Client->Lookup( client_id => $clientID );

        if ($oClient) {
            # If we're downloading statements then we need a pathname-friendly
            # client name, otherwise we'll send the client name as-is via JSON.
            my $clientName = ( $downloadMode == 1 ) ? API::Util->cleanName( $oClient->display_name ) : $oClient->client_name;

            $historicalStatements{$clientID} = [] if !exists $historicalStatements{$clientID};

            # get all of the payee's historical statements
            my $oPortalPayee = $portalPayeeMap{$runType}{$clientID}{$payeeID}{$payeeRecipientID};
            my $dateInvited  = $oPortalPayee->date_invited;

            my ($aStatements) = $self->getStatementData('historical', $oPortalPayee, $runType);

            # Add this payee's statements to the set of historical statements or download directory
            foreach my $hStatement ( @$aStatements ) {

                my $runID   = $hStatement->{run_id};
                my $payorID = $hStatement->{payor_id};
                my $payeeID = $hStatement->{payee_id};

                if ( !exists $runMap{$clientID}{$runID} ) {
                    $runMap{$clientID}{$runID} = $hStatement->{run_name};
                }

                if ( !exists $payorMap{$clientID}{$payorID} ) {
                    $payorMap{$clientID}{$payorID} = $hStatement->{payor_name};
                }

                if ( !exists $payeeMap{$clientID}{$payeeID} ) {
                    $payeeMap{$clientID}{$payeeID}{name}              = $hStatement->{payee_name};
                    $payeeMap{$clientID}{$payeeID}{client_account_id} = $hStatement->{client_account_id};
                }

                if ( $payeeRecipientID ) {
                    my $canSeeStatement = $self->_canRecipientUserSeeStatement( $dateInvited, $hStatement );
                    next unless $canSeeStatement;
                }

                if ( ( ( $filterClientRequired && $filterClientID == $clientID ) || !$filterClientRequired ) ) {

                    if (   ( $filterRunID && $filterRunID != $runID )
                        || ( $filterPayorID && $filterPayorID != $payorID )
                        || ( $filterPayeeID && $filterPayeeID != $payeeID ) ) {

                        # Skip adding statement data to JSON or download file
                        next;
                    }
                } elsif ( defined $filterClientID && $filterClientID != $clientID ) {
                    # Skip adding statement data to download file (RSD-511)
                    next if $downloadMode;
                }

                if ( !$downloadMode ) {
                    push @{$historicalStatements{$clientID}}, $hStatement;
                }
                else {
                    if ( $runType =~ /^artist$/i ) {
                        $numFiles += $self->_getArtistStatementFiles( $clientName, $clientID, $tempdir, $hStatement );
                    } elsif ( $runType =~ /^publisher$/i ) {
                        $numFiles += $self->_getPublisherStatementFiles( $clientName, $clientID, $tempdir, $hStatement );
                    }
                }
            }
        }

    }    # client-payee loop


    if ( $downloadMode ) {
        if ( $numFiles > 0 ) {
            my $oUser = AppUser::DB::Item::User->Lookup(
                user_id   => $userID
            );

            my $userName = $oUser->first_name . '_' . $oUser->last_name;
            $userName = API::Util->cleanName($userName);

            # Download it!
            my $contentType = 'application/octet-stream';
            my $zipFileName = $userName . "__$runType" . "_statement.zip";
            $response = RSApache::Response::DownloadZipDirectory->new( $tempdir, $contentType, $zipFileName );

            return $response;
        }

        return $self->createResponse(
            status => HTTP_OK,
            data   => { msg => "no statement(s) found" }
        );
    }


    # Setup the arrays containing the full set of runs, payors and payees that
    # may be used for filtering purposes.  Note: these are client-specific.
    foreach my $clientID ( keys %runMap ) {

        $hClients->{$clientID}{num_statements}       = 0 + scalar @{$historicalStatements{$clientID}};
        $hClients->{$clientID}{historical_statement} = \@{$historicalStatements{$clientID}};

        my @runList;
        my $_runData = $runMap{$clientID};
        foreach my $runID ( sort { $a <=> $b } keys %{$_runData} ) {
            my %data = ();
            $data{run_id}   = 0 + $runID;
            $data{run_name} = $_runData->{$runID};
            $data{selected} = ( $filterRunID && $filterRunID == $runID ) ? 1 : 0;
            push @runList, \%data;
        }
        $hClients->{$clientID}{run_list} = \@runList;
        $hClients->{$clientID}{num_runs} = scalar @runList;

        my @payorList;
        my $_payorData = $payorMap{$clientID};
        foreach my $payorID ( sort { $a <=> $b } keys %{$_payorData} ) {
            my %data = ();
            $data{payor_id}   = 0 + $payorID;
            $data{payor_name} = $_payorData->{$payorID};
            $data{selected}   = ( $filterPayorID && $filterPayorID == $payorID ) ? 1 : 0;
            push @payorList, \%data;
        }
        $hClients->{$clientID}{payor_list} = \@payorList;
        $hClients->{$clientID}{num_payors} = scalar @payorList;

        my @payeeList;
        my $_payeeData = $payeeMap{$clientID};
        foreach my $payeeID ( sort { $a <=> $b } keys %{$_payeeData} ) {
            my %data = ();
            $data{payee_id}          = 0 + $payeeID;
            $data{payee_name}        = $_payeeData->{$payeeID}{name};
            $data{client_account_id} = $_payeeData->{$payeeID}{client_account_id};
            $data{selected}          = ( $filterPayeeID && $filterPayeeID == $payeeID ) ? 1 : 0;
            push @payeeList, \%data;
        }
        $hClients->{$clientID}{payee_list} = \@payeeList;
        $hClients->{$clientID}{num_payees} = scalar @payeeList;
    }

    return $self->createResponse(
        status => HTTP_OK,
        data   => {
            clients => [ @$hClients{ sort keys %$hClients } ],
        },
    );
}

sub _validate {
    my ($self, $runType) = @_;

    my $allowedRunTypes = join '|', $self->getAllowedRunTypes();

    unless ( $runType =~ /^(?:$allowedRunTypes)$/ ) {
        return $self->createResponse(
            status => HTTP_NOT_IMPLEMENTED,
            data   => { msg => "Invalid runtype" }
        );
    }

    return;
}

sub _initClientData {
    my ( $self, $hClients, $clientID ) = @_;

    # return data for an existing client
    return $hClients->{$clientID} if exists $hClients->{$clientID};

    my $oClient = Common::DB::Item::Client->Lookup( client_id => $clientID );

    if ($oClient) {
        return $hClients->{$clientID} = {
            client_id            => 0 + $clientID,
            client_name          => $oClient->client_name,
            display_name         => $oClient->display_name,
            email                => $oClient->email,
            num_statements       => 0,
            historical_statement => [ ],
        };
    }
}

sub _canRecipientUserSeeStatement {
    my ($self, $dateInvited, $hStatementData) = @_;

    my $runDate = $hStatementData->{date};

    # the recipient was invited before the run was committed or at the same time
    return 1 if $runDate ge $dateInvited;

    return 0;
}

sub _getArtistStatementFiles {
    my ($self, $clientName, $clientID, $tempdir, $hStatement) = @_;

    my $stmtID  = $hStatement->{statement_id};
    my $runID   = $hStatement->{run_id};
    my $payorID = $hStatement->{payor_id};
    my $payeeID = $hStatement->{payee_id};

    my $oStatement   = RPS::DB::Item::ArtistRoyaltyStatement->Lookup( artist_royalty_statement_id => $stmtID );

    my $baseFilePath = RPS::Statement::Artist::PDF::StatementPathFromRunID($runID);
    my $clientPrefix = $clientName . '__run_' . $runID;

    my $filesCnt = 0;

    # Get the PDF statement
    my $prettyFileName = RPS::Statement::Artist::PDF::PrettyStatementFileNameFromDBItem($oStatement);
    my $uglyFileName   = RPS::Statement::Artist::PDF::StatementFileNameFromDBItem($oStatement);
    if ( -e "$baseFilePath/$uglyFileName" ) {
        $prettyFileName = $clientPrefix . '__' . $prettyFileName;
        system("ln -s $baseFilePath/$uglyFileName $tempdir/$prettyFileName");
        $filesCnt++;
    } else {
        Common::Log::Print("WARNING: client($clientID) run($runID) payee($payeeID): PDF not found");
    }

    # Get the Excel statement
    $prettyFileName = RPS::Statement::Artist::Excel::PrettyStatementFileNameFromDBItem($oStatement);
    $uglyFileName   = RPS::Statement::Artist::Excel::StatementFileNameFromDBItem($oStatement);
    if ( -e "$baseFilePath/$uglyFileName" ) {
        $prettyFileName = $clientPrefix . '__' . $prettyFileName;
        system("ln -s $baseFilePath/$uglyFileName $tempdir/$prettyFileName");
        $filesCnt++;
    } else {
        Common::Log::Print("WARNING: client($clientID) run($runID) payee($payeeID): XLS not found");
    }

    return $filesCnt;
}

sub _getPublisherStatementFiles {
    my ($self, $clientName, $clientID, $tempdir, $hStatement) = @_;

    my $stmtID  = $hStatement->{statement_id};
    my $runID   = $hStatement->{run_id};
    my $payorID = $hStatement->{payor_id};
    my $payeeID = $hStatement->{payee_id};

    my $oStatement = RPS::DB::Item::MechanicalStatement->Lookup( mechanical_statement_id => $stmtID );

    my $baseFilePath = RPS::Statement::Mechanical::US::PDF->StatementPathFromRunID($runID);
    my $clientPrefix = $clientName . '__run_' . $runID;

    my $filesCnt = 0;

    # Get the PDF statement
    my $prettyFileName = RPS::Statement::Mechanical::US::PDF->PrettyStatementFileNameFromDBItem($oStatement);
    my $uglyFileName   = RPS::Statement::Mechanical::US::PDF->StatementFileNameFromDBItem($oStatement);
    if ( -e "$baseFilePath/$uglyFileName" ) {
        $prettyFileName = $clientPrefix . '__' . $prettyFileName;
        system("ln -s $baseFilePath/$uglyFileName $tempdir/$prettyFileName");
        $filesCnt++;
    } else {
        Common::Log::Print("WARNING: client($clientID) run($runID) payee($payeeID): PDF not found");
    }

    # Get the Text statement
    $prettyFileName = RPS::Statement::Mechanical::US::Text->PrettyStatementFileNameFromDBItem($oStatement);
    $uglyFileName   = RPS::Statement::Mechanical::US::Text->StatementFileNameFromDBItem($oStatement);
    if ( -e "$baseFilePath/$uglyFileName" ) {
        $prettyFileName = $clientPrefix . '__' . $prettyFileName;
        system("ln -s $baseFilePath/$uglyFileName $tempdir/$prettyFileName");
        $filesCnt++;
    } else {
        Common::Log::Print("WARNING: client($clientID) run($runID) payee($payeeID): XLS not found");
    }

    return $filesCnt;
}


1;
