package Pcore::App::Selenium;

use Pcore -class;
use Pcore::Selenium;

extends qw[Pcore::App::Alien];

has '+name' => ( default => 'selenium' );
has '+ns'   => ( default => 'Pcore::Selenium' );

has selenium_ver => ( is => 'lazy', isa => Str, init_arg => undef );
has selenium_uri => ( is => 'lazy', isa => Str, init_arg => undef );
has ff_ver       => ( is => 'lazy', isa => Str, init_arg => undef );

has ff_dir      => ( is => 'lazy', isa => Str, init_arg => undef );
has ff_bin_path => ( is => 'lazy', isa => Str, init_arg => undef );

our $CFG = {
    selenium_ver    => undef,
    ff_ver          => undef,
    port            => 4444,
    linux_xdisplay  => 20,
    user_extensions => q[],
    ff_extensions   => {
        firebug   => 'https://addons.mozilla.org/firefox/downloads/latest/1843/addon-1843-latest.xpi',
        firexpath => 'https://addons.mozilla.org/firefox/downloads/latest/11900/addon-11900-latest.xpi',
        noscript  => 'https://addons.mozilla.org/firefox/downloads/latest/722/addon-722-latest.xpi',
    }
};

# CLI
around CLI => sub ( $orig, $self ) {
    my $cli = $self->$orig;

    $cli->{name} = 'selenium';

    return $cli;
};

sub _build_selenium_ver ($self) {
    return $self->cfg->{selenium_ver} // $ENV->dist('Pcore-Selenium')->cfg->{selenium_ver};
}

sub _build_selenium_uri ($self) {
    my $major_ver = $self->selenium_ver =~ s/\A(\d+[.]\d+)[.].*/$1/smr;

    return qq[https://selenium-release.storage.googleapis.com/$major_ver/selenium-server-standalone-] . $self->selenium_ver . q[.jar];
}

sub _build_ff_ver ($self) {
    return $self->cfg->{ff_ver} // $ENV->dist('Pcore-Selenium')->cfg->{ff_ver};
}

sub _build_alien_bin_path ($self) {
    return $self->app_dir . 'selenium-server-standalone-' . $self->selenium_ver . q[.jar];
}

sub _build_ff_dir ($self) {
    return $self->app_dir . 'firefox-' . ( $MSWIN ? 'win' : 'nix' ) . q[-] . $self->ff_ver;
}

sub _build_ff_bin_path ($self) {
    return $self->ff_dir . ( $MSWIN ? '/firefox.exe' : '/firefox-bin' );
}

sub _build_selenium ($self) {
    if ( -f $self->alien_bin_path ) {
        $self->_appx_report_warn( q["] . $self->alien_bin_path . q[" already exists. Remove it manually to rebuild] );
    }
    else {
        # NOTE on linux don't forget to install dependencies: yum install Xvfb xorg-x11-fonts*
        $self->_appx_report_info( q[Downloading: selenium-server-standalone-] . $self->selenium_ver . q[.jar] );

        my $res = P->http->mirror( $self->alien_bin_path, $self->selenium_uri, on_progress => 1 );

        $self->_appx_report_fatal(q[Selenium deployment error]) if !-e $self->alien_bin_path;
    }

    return;
}

sub _build_ff ($self) {
    if ( -d $self->ff_dir ) {
        $self->_appx_report_warn( q["] . $self->ff_dir . q[" already exists. Remove it manually to rebuild] );
    }
    else {
        my $uri = 'https://ftp.mozilla.org/pub/mozilla.org/firefox/releases/' . $self->ff_ver . ( $MSWIN ? '/win64' : '/linux-x86_64' ) . '/en-US/';

        $uri .= $MSWIN ? 'Firefox%20Setup%20' . $self->ff_ver . '.exe' : 'firefox-' . $self->ff_ver . '.tar.bz2';

        my $store_to = $MSWIN ? $ENV->{TEMP_DIR} . 'firefox-install.exe' : $ENV->{TEMP_DIR} . 'firefox-install.tar.bz2';

        $self->_appx_report_info( q[Downloading: ] . $uri );

        my $res = P->http->mirror( $store_to, $uri, on_progress => 1 );

        P->file->mkpath( $self->ff_dir );

        if ($MSWIN) {
            P->pm->run_proc( qq[winrar x -o+ -apcore -df -ibck "$store_to" core "@{[$self->ff_dir]}"], stdout => 1, stderr => 2 ) or die;
        }
        else {
            P->pm->run_proc( qq[tar --overwrite --strip-components=1 -xvf "$store_to" -C "@{[$self->ff_dir]}"], stdout => 1, stderr => 2 ) or die;
        }

        if ( !-e $self->ff_bin_path ) {
            P->file->rmtree( $self->ff_dir );

            $self->_appx_report_fatal(q[Firefox deployment error]);
        }

        $self->_build_ff_extensions;
    }

    return;
}

sub _build_ff_extensions ($self) {
    state $init = !!require Archive::Zip;

    my $cv = AE::cv;

    for my $ext ( sort keys $self->cfg->{ff_extensions}->%* ) {
        $self->_appx_report_info(qq[Downloading: $ext]);

        $cv->begin;

        P->http->get(
            $self->cfg->{ff_extensions}->{$ext},
            buf_size    => 1,
            on_progress => 1,
            on_finish   => sub ($res) {
                if ( $res->status == 200 ) {
                    my $zip = Archive::Zip->new;

                    die q[Error reading .zip archive] unless $zip->readFromFileHandle( $res->body, $res->body->path ) == Archive::Zip::AZ_OK();

                    my $id = P->data->from_xml( $zip->contents('install.rdf') =~ s/0$//smr )->{RDF}->{Description}->[0]->{'em:id'}->[0]->{content};

                    P->file->copy( $res->body->path, $self->ff_dir . qq[/browser/extensions/$id.xpi] );
                }
                else {
                    die qq[Error downloading FF extension];
                }

                $cv->end;

                return;
            }
        );
    }

    $cv->recv;

    return;
}

sub _cmd ($self) {

    # timeout - Controls how long the client is is allowed to be gone before the session is reclaimed (value in seconds)
    # browserTimeout -Controls how long the browser is allowed to hang (value in seconds)
    # Please note that the "browserTimeout" is intended as a backup timeout mechanism when the ordinary timeout mechanism fails, which should be used mostly in grid/server environments to ensure that crashed/lost processes do not stay around for too long, polluting the runtime environment.

    my $cmd = q[java -Xrs -jar "] . $self->alien_bin_path . q["];
    $cmd .= q[ -port ] . $self->cfg->{port};
    $cmd .= q[ -timeout ] . ( 5 * 60 );
    $cmd .= q[ -browserTimeout ] . ( 6 * 60 );
    $cmd .= q[ -userExtensions ] . $self->cfg->{user_extensions} if $self->cfg->{user_extensions};    # indicates a JavaScript file that will beloaded into selenium
    $cmd .= q[ -ensureCleanSession];
    $cmd .= q[ -trustAllSSLCertificates];
    $cmd .= q[ -proxyInjectionMode];
    $cmd .= q[ -Dwebdriver.firefox.bin="] . $self->ff_bin_path . q["];
    $cmd .= q[ -Dwebdriver.accept.untrusted.certs=true];
    $cmd .= q[ -Dwebdriver.assume.untrusted.issuer=true];
    $cmd .= q[ -Dwebdriver.enable.native.events=true];

    return $cmd;
}

# APP
around _build_cfg => sub ( $orig, $self ) {
    return P->hash->merge( $self->$orig, $CFG );
};

around _create_local_cfg => sub ( $orig, $self ) {
    my $local_cfg = {
        selenium_ver => $self->cfg->{selenium_ver},
        ff_ver       => $self->cfg->{ff_ver},
        port         => $self->cfg->{port},
    };

    return P->hash->merge( $self->$orig, $local_cfg );
};

around app_build => sub ( $orig, $self ) {
    $self->$orig;

    $self->_build_selenium;
    $self->_build_ff;

    return;
};

sub master_proc ($self) {
    return;
}

sub alien_proc ($self) {
    if ($MSWIN) {
        P->pm->run_proc( $self->_cmd ) or die;

        exit 0;
    }
    else {
        exec q[xvfb-run -n ] . $self->cfg->{linux_xdisplay} . q[ -l -s "-fp /usr/share/X11/fonts/misc -screen 0 1024x768x24" ] . $self->_cmd() or die;
    }

    return;
}

1;
## -----SOURCE FILTER LOG BEGIN-----
##
## PerlCritic profile "pcore-script" policy violations:
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+
## | Sev. | Lines                | Policy                                                                                                         |
## |======+======================+================================================================================================================|
## |    3 | 123                  | References::ProhibitDoubleSigils - Double-sigil dereference                                                    |
## |------+----------------------+----------------------------------------------------------------------------------------------------------------|
## |    3 | 143                  | ValuesAndExpressions::ProhibitInterpolationOfLiterals - Useless interpolation of literal string                |
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+
##
## -----SOURCE FILTER LOG END-----
__END__
=pod

=encoding utf8

=head1 NAME

Pcore::App::Selenium

=head1 SYNOPSIS

=head1 DESCRIPTION

=head1 ATTRIBUTES

=head1 METHODS

=head1 SEE ALSO

=cut
