package BookPub::RDAS::Request::SalesData::Apple;

use Data::Dumper;
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);

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

use lib '/app/tools/rdas/lib';
use RDAS::Fetcher::Mechanize::Authenticated;

use lib '/app/tools/bookpub/lib';
use BookPub::RDAS::Parser::SaleData::Apple::Home;
use BookPub::RDAS::Parser::SaleData::Apple::Feed;
use BookPub::RDAS::Parser::SaleData::Apple::FileList;
use BookPub::RDAS::URL::ProductPrice::Apple;
use BookPub::RDAS::Response::SalesData::Apple;

use base 'BookPub::RDAS::Request::SalesData';

use constant kMaxRetries => 3;

sub error  { shift->{_error} }
sub report { shift->{_report} }

sub _getResponseObject {
    my $self = shift;

    return new BookPub::RDAS::Response::SalesData::Apple( report => $self->report, saleFeedFileID => $self->saleFeedFileID, @_ );
}

sub fetch {
    my $self = shift;

    # Disable warnings coming from HTTP::Cookie.
    $^W = 0;

    my $success = 0;
    $self->{_error} = undef;

    for ( my $retry = 1 ; $retry <= kMaxRetries && !$success && !$self->error() ; $retry++ ) {
        $self->{_parser} = {};

        unless ( $self->{_parser}->{home} = $self->loginRequired ? $self->_loginHomePage : $self->_fetchHomePage ) {
            Log->info( "Failed to fetch homepage.  Retry ( $retry of " . kMaxRetries . ")" );
            die "Authentication failed" if ( $retry > 1 );
            next;
        }

        unless ( $self->{_parser}->{feedHome} = $self->_feedHome() ) {
            Log->info( "Failed to feed homepage.  Retry ( $retry of " . kMaxRetries . ")" );
            die "retry failed" if ( $retry > 1 );
            next;
        }

        if ( $self->_fetchFile() ) {
            $success = 1;
        } else {
            Log->error("File fetch failed");
            return;
        }
    }

    unless ($success) {
        $self->{_error} = "Service authentication failed for service" unless ( $self->error );
        Log->info( "ERROR: " . $self->error() ) if ( $self->error );
        $self->fail( failure => $self->error );
    }

    return $success;
}

sub _credentials {
    my $self = shift;

    unless ( $self->{_credentials} ) {
        my $saleFeedFile = new BookPub::Sale::Feed::SaleFeedFile( saleFeedFileID => $self->saleFeedFileID );
        my $saleFeed = new BookPub::Sale::Feed::SaleFeed( saleFeedID => $saleFeedFile->SaleFeedID );

        my $cred = new BookPub::Sale::Feed::SaleFeedService( saleFeedServiceID => $saleFeed->SaleFeedServiceID );
        if ( $cred && $cred->Username && $cred->Password ) {
            $self->{_credentials} = [ $cred->Username, $cred->Password ];
        }
    }

    if ( $self->{_credentials} ) {
        return @{ $self->{_credentials} };
    } else {
        die "No login credentials";
    }
}

sub loginRequired { shift->{_loginRequired} }

sub _isLoginPage {
    my $self = shift;
    my $parser = shift || die;

    if ( $parser->Login ) {
        $self->{_loginRequired} = 1;
        Log->info("Not authenticated!");
        return 1;
    }

    $self->{_loginRequired} = undef;

    return;
}

sub _loginHomePage {
    my $self = shift;
    my ( $uname, $passwd ) = $self->_credentials();
    my $credentials = { theAccountName => $uname, theAccountPW => $passwd };

    Log->info(" + Login to homepage");

    assert( $uname,  "Username required" );
    assert( $passwd, "Password required" );

    return $self->_fetchHomePage($credentials);
}

sub _fetchHomePage {
    my $self        = shift;
    my $credentials = shift;

    Log->info(" + Fetch homepage");
    my $fetcher = $self->_fetchPage( BookPub::RDAS::URL::ProductPrice::Apple->SiteLoginURL(), form => $credentials ) || return;

    my $parser = new BookPub::RDAS::Parser::SaleData::Apple::Home(
        content => $fetcher->content(),
        links   => $fetcher->links()
    );

    if ( $parser->Update && $parser->Continue ) {
        Log->warn("iTunes account alert seen.  Login to iTunes connect and clear the alert");

        $fetcher = $self->_fetchPage( BookPub::RDAS::URL::ProductPrice::Apple->SiteURL() . $parser->Continue ) || return;
        $parser = new BookPub::RDAS::Parser::SaleData::Apple::Home(
            content => $fetcher->content(),
            links   => $fetcher->links()
        );
    }

    return $self->_isLoginPage($parser) ? undef : $parser;
}

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

    my $fetcher = $self->_getFetchObject($url);

    if ( $fetcher->fetch(%args) ) {
        return $fetcher;
    } else {
        $self->{_error} = "Fetch failed.  HTTP Code: " . $fetcher->errorCode();
        return;
    }
}

sub _feedHome {
    my $self     = shift;
    my $parsers  = $self->parser;
    my $homepage = $parsers->{home};

    Log->info(" + FFetch feed home page");
    my $link = $homepage->link( text => 'Payments and Financial Reports' );

    $url = BookPub::RDAS::URL::ProductPrice::Apple->SiteURL . $link->url;

    my $fetcher = $self->_fetchPage($url) || return;

    my $parser = new BookPub::RDAS::Parser::SaleData::Apple::Feed( content => $fetcher->content() );

    return $self->_isLoginPage($parser) ? undef : $parser;
}

sub _fetchFile {
    my $self    = shift;
    my $parsers = $self->parser;
    my $feeds   = $parsers->{feedHome};

    Log->info(" + Fetch feed Accounts");

    my $accounts = $feeds->Accounts()
      || [ {
            'feed'  => 'APPLE',
            'value' => '0',
            'name'  => 'Main Feed',
        }
      ];

    foreach my $account (@$accounts) {
        my $parser = $self->_fetchFileListByAccount($account);
        return 1 if ( $self->_saveFile( account => $account, parser => $parser ) );
    }

    return;
}

sub _fetchFileListByAccount {
    my $self = shift;
    my $account = shift || die "account required";
    my %formArgs;

    my $parsers = $self->parser;
    my $feeds   = $parsers->{feedHome};

    my $url = BookPub::RDAS::URL::ProductPrice::Apple->SiteURL . $feeds->FormAction;

    Log->info(" + Fetch feed file list");

    Log->debug( "Account: " . Dumper $account );

    Log->debug( "Action: " . $feeds->FormAction );

    Log->debug( "Submit: " . $feeds->SubmitName );

    $formArgs{ $account->{select} } = $account->{value} if ( $account->{select} );
    $formArgs{ $feeds->SubmitName } = 'Earnings';

    my $fetcher = $self->_fetchPage($url);
    $fetcher->post( form => \%formArgs );

    return new BookPub::RDAS::Parser::SaleData::Apple::FileList( content => $fetcher->content() );

}

sub _parseFiles {
    my $self    = shift || return;
    my %args    = @_;
    my $account = $args{account} || die "Account required";
    my $parser  = $args{parser} || return;

    my $files = $parser->Files() || return;

    my @files;

    die "Account feed not defined." unless ( $account->{feed} );

    my $regionInput = $parser->RegionInput();
    my $monthInput  = $parser->MonthInput();
    my $yearInput   = $parser->YearInput();

    foreach my $file (@$files) {
        next unless ( $file->{region} );
        next unless ( $file->{date} );

        my $country = Common::Country->Get( $file->{region} );
        my $code = $country ? $country->alpha2 : $file->{region};
        $code =~ s/\s/_/g;

        my $date = new Common::Date( $file->{date} );
        $file->{filename} = $account->{feed} . "_" . $date->asString("%m%y") . "_" . $code . ".txt";

        $file->{regionInput} = $regionInput;
        $file->{monthInput}  = $monthInput;
        $file->{yearInput}   = $yearInput;

        push( @files, $file );
    }

    return @files;
}

sub _saveFile {
    my $self = shift || die;
    my %args = @_;

    my $account = $args{account} || die;
    my $parser  = $args{parser}  || die;

    my $url = BookPub::RDAS::URL::ProductPrice::Apple->SiteURL . $parser->FormAction;
    my @files = $self->_parseFiles( account => $account, parser => $parser );

    unless (@files) {
        Log->info("No files for this account.");
        return;
    }

    for my $file (@files) {
        Log->info( "F: " . $self->SaleFeedFile->Filename . " eq A: $file->{filename}" );

        if ( $self->SaleFeedFile->Filename eq $file->{filename} ) {
            Log->info(" -- Matched file $file->{filename}");
            my %formArgs = $self->_downloadFormArgs( parser => $parser, account => $account, file => $file );

            my $fetcher = $self->_fetchPage($url);
            $fetcher->post( form => \%formArgs );

            unless ( $fetcher->mechanize()->response()->filename() =~ /.gz$/ ) {
                die("Failed to download file: $file->{filename}");
                return;
            }

            my $filename = $fetcher->mechanize()->response()->filename();
            $filename =~ s/\.gz//;

            $self->SaleFeedFile->Filename($filename);
            $self->SaleFeedFile->save();

            $self->{_report} = $self->_unzipFile( $fetcher->content() );
            return 1;
        }
    }

    return;
}

sub _unzipFile {
    my $self = shift;
    my $content = shift || return;
    my $uncompressed;

    gunzip( \$content, \$uncompressed )
      or die "gunzip failed: $GunzipError\n";

    return $uncompressed;
}

sub _downloadFormArgs {
    my $self    = shift;
    my %args    = @_;
    my $file    = $args{file};
    my $account = $args{account};
    my $parser  = $args{parser};
    my %form;

    my ( $monthName,     $monthValue )     = $self->_getMonthValue($file);
    my ( $yearName,      $yearValue )      = $self->_getYearValue($file);
    my ( $accountIDName, $accountIDValue ) = ( $account->{select}, $account->{value} ) if ( $account->{select} );

    $form{$accountIDName}         = $accountIDValue if ($accountIDName);
    $form{$monthName}             = $monthValue;
    $form{$yearName}              = $yearValue;
    $form{ $file->{regionInput} } = 'WONoSelectionString';

    my $xname = $file->{name} . '.x';
    my $yname = $file->{name} . '.y';

    $form{$xname} = 14;
    $form{$yname} = 9;

    Log->info("Account info: $accountIDName => $accountIDValue");
    Log->debug( "File Object: " . Dumper $file );

    Log->info( "Form Args: " . Dumper \%form );

    return %form;
}

sub _getMonthValue {
    my $self      = shift;
    my $file      = shift || return;
    my $monthHash = $file->{monthInput} || return;

    my ($month) = $file->{start} =~ /\d+-(\d+)-\d+/;

    return $month ? ( $monthHash->{name}, $month - 1 ) : undef;
}

sub _getYearValue {
    my $self     = shift;
    my $file     = shift || return;
    my $yearHash = $file->{yearInput} || return;

    my ($year) = $file->{start} =~ /(\d+)-\d+-\d+/;

    foreach my $y ( keys %{ $yearHash->{values} } ) {
        return ( $yearHash->{name}, $yearHash->{values}->{$y} ) if ( $y == $year );
    }

    return;
}

1;
