#!/usr/bin/perl

use strict;
use warnings;

use feature qw{signatures};

use Test2::V0;

use FindBin;
use Cwd qw{abs_path};
use JSON;
use Data::Dumper;

use Selenium::Client;

my $extra = '';
$extra = '/' if grep { $^O eq $_ } qw{msys MSWin32};

my $sut  = 'file://' . $extra . abs_path("$FindBin::Bin/test.html");
my $sut2 = 'file://' . $extra . abs_path("$FindBin::Bin/other.html");

#Do WinAppDriver testing if we are on windows and have it installed at all
SKIP: {
    skip( "Need JSONWire support before we can test winappdriver", 1 );
    my $winapp = eval { Selenium::Client->new( driver => 'WinApp', debug => $ENV{DEBUG} ) };
    if ($winapp) {
        my $caps = {
            app                         => 'C:\\Windows\\System32\\notepad.exe',
            platformName                => "WINDOWS",
            platform                    => "WINDOWS",
            deviceName                  => "WindowsPC",
            appArguments                => "zippy.txt",
            appWorkingDir               => '.',
            "ms:experimental-webdriver" => JSON::true,
        };

        #XXX the WC3 support is only "sorta" in that they don't support modern caps
        my @ret = $winapp->NewSession( desiredCapabilities => $caps );
        use Data::Dumper;
        print Dumper( \@ret );
        my $notepad;
        my $input = $notepad->FindElement( using => 'css selector', value => 'Edit' );
        $input->ElementSendKeys( text => 'tickle' );
        is( $input->GetElementProperty( name => 'value' ), 'tickle', "Can clear and send keys to a text input" );
    }
}

my @browsers = qw{firefox chrome};
push( @browsers, 'MicrosoftEdge' ) if grep { $^O eq $_ } qw{MSWin32 msys};
push( @browsers, 'safari' )        if $^O eq 'darwin';
foreach my $browser (@browsers) {
    my @drivers = qw{Gecko Auto SeleniumHQ::Jar};
    @drivers = qw{Chrome Auto SeleniumHQ::Jar} if $browser eq 'chrome';
    @drivers = qw{Edge Auto SeleniumHQ::Jar}   if $browser eq 'MicrosoftEdge';
    @drivers = qw{Safari Auto SeleniumHQ::Jar} if $browser eq 'safari';

    #@drivers = qw{SeleniumHQ::Jar};

    foreach my $driver (@drivers) {
        subtest "$browser (using $driver): Spec compliance" => sub {
            my %options = ( driver => $driver, browser => $browser, debug => $ENV{DEBUG}, headless => !$ENV{NO_HEADLESS} );

            my $driver = Selenium::Client->new(%options);

            my $status = $driver->Status();
            ok( $status->{ready}, "Driver up and running" );

            my ( $capabilities, $session ) = $driver->NewSession();
            isa_ok( $capabilities, "Selenium::Capabilities" );
            isa_ok( $session,      "Selenium::Session" );

            is( dies { $session->SetTimeouts( script => 1000, implicit => 1000, pageLoad => 1000 ) }, undef, "Can set timeouts" );

            #XXX GetTimeouts like every other thing *chokes* on data not being *just right* despite spec having undefined behavior here
            my $expected = { script => 1000, implicit => 1000, pageLoad => 1000 };

            my $t = $session->GetTimeouts( script => undef, implicit => undef, pageLoad => undef );
            is( $t, $expected, "GetTimeouts works" );

            is( dies { $session->NavigateTo( url => $sut ) }, undef, "Can open page" );

            #Alerts
            alertify( $session, $browser );

            is( $session->GetCurrentURL(),                       $sut,                                             "Can get current URL" );
            is( $session->GetTitle(),                            'Test Page',                                      "Can get page title" );
            is( dies { $session->NavigateTo( 'url' => $sut2 ) }, undef,                                            "Can open other page" );
            is( $session->GetPageSource(),                       "<html><head></head><body>ZIPPY\n</body></html>", "Can get page source" );
            is( dies { $session->Back() },                       undef,                                            "Can navigate to the last page visited with back()" );

            alertify( $session, $browser ) unless $browser eq 'safari' || $browser eq 'firefox';
            is( dies { $session->Forward() }, undef, "Can navigate back to previously visited page with forward()" );

            $session->Back();

            #XXX webkit re-issues alerts on back()
            alertify( $session, $browser ) if grep { $browser eq $_ } qw{chrome MicrosoftEdge};

            is( dies { $session->Refresh() }, undef, "Can refresh the page" );
            alertify( $session, $browser );

            my $handle = "" . $session->GetWindowHandle();
            ok( $handle, "Can get window handle" );
            my $link = $session->FindElement( using => 'css selector', value => '#linky' );
            $link->ElementClick();

            my @newhandles = map { "" . $_ } $session->GetWindowHandles();

            my ($newhandle) = grep { $_ ne $handle } @newhandles;
            die("Could not get existing handle from getwindowhandles") unless $newhandle;
            is( dies { $session->SwitchToWindow( handle => $newhandle ) }, undef, "Can switch to new window" );

            #XXX This fails on firefoxdriver directly for whatever reason
            my $src = eval { $session->GetPageSource() }                          || '';
            like( $src, qr/ZIPPY/i, "Got right window" ) if $browser ne 'firefox' || $driver eq 'SeleniumHQ::Jar';
            todo "Get page source after back() sometimes fails on firefox direct driver" => sub {
                like( $src, qr/ZIPPY/i, "Got right window" );
              }
              if $browser eq 'firefox' && $driver ne 'SeleniumHQ::Jar';

            is( dies { $session->SwitchToWindow( handle => $handle ) }, undef, "Can switch to old window" );

            like( $session->GetPageSource(), qr/Howdy/i, "Switched window correctly" );
            like( $session->GetPageSource(), qr/🥰/i,     "Unicode handled properly" );
            $session->SwitchToWindow( handle => $newhandle );
            is( dies { $session->CloseWindow() }, undef, "CloseWindow closes current window context" );
            $session->SwitchToWindow( handle => $handle );

            #move it around
            my %erekt = ( height => 100, width => 500, x => 50, y => 50 );
            is( dies { $session->SetWindowRect(%erekt) }, undef, "Can set window rect" );
            my $rekt = $session->GetWindowRect();

          SKIP: {
                skip( "Window rect set is basically never correctly obeyed, and actually fucks shit up that you wouldn't expect", 1 );
                is( $rekt, \%erekt, "Can get window rect" );
            }

            #Frames
            #my $frame = $session->FindElement( using => 'css selector', value => '#frame' );
            #is( dies { $session->SwitchToFrame( id => $frame->{elementid} ) }, undef, "Can switch into frame");
            #XXX the above actually does not do anything, only switching by window.frames index actually works lol
            # Actually doing that straight up fucks shit up too
            $session->SwitchToFrame( id => 0 );

            # Check that the driver yanno *actually did something*
            my $fbody = $session->FindElement( using => 'css selector', value => 'body' );
            my $ftext = $fbody->GetElementText();
            is( $ftext,                                   'ZIPPY', "Can do things in iframes" );
            is( dies { $session->SwitchToParentFrame() }, undef,   "Can travel up the frame stack" );

            #Maximize etc
            is( dies { $session->MaximizeWindow() },   undef, "Can maximize window" );
            is( dies { $session->MinimizeWindow() },   undef, "Can minimize window" );
            is( dies { $session->FullscreenWindow() }, undef, "Can Fullscreen window" );

            #Element Method Testing
            my $element = $session->FindElement( using => 'css selector', value => 'input[name=text]' );
            isa_ok( $element, 'Selenium::Element' );
            my $prop = $element->GetElementProperty( name => 'title' );
            is( $prop, 'default', "Can get element properties" );

            my @inputs = $session->FindElements( using => 'css selector', value => 'input' );
            is( scalar(@inputs), 5, "Can find multiple elements correctly" );

            my $finder = $session->FindElement( using => 'css selector', value => 'form' );
            my $found  = $finder->FindElementFromElement( using => 'css selector', 'value' => 'label' );
            is( $found->GetElementAttribute( name => 'for' ), 'text', "Can find child properly" );

            my @radios = $finder->FindElementsFromElement( using => 'css selector', 'value' => 'input[type=radio]' );
            is( scalar(@radios), 2, "Can find child elements properly" );

            my ( $unselected, $selected ) = @radios;
            ok( !$unselected->IsElementSelected(), "IsElementSelected works" );
            ok( $selected->IsElementSelected(),    "IsElementSelected works" );

            my @checked = $session->FindElements( using => 'css selector', value => 'input:checked' );
            is( scalar(@checked), 1, "But we can at least work around that using css :checked pseudoselector" );

            is( dies { $session->GetActiveElement() }, undef, "Can get active element" );

            my $invisible = $session->FindElement( using => 'css selector', value => '#no-see-em' );
            is( $invisible->GetElementCSSValue( propertyname => 'display' ), 'none',   "Can get CSS values for elements" );
            is( lc( $invisible->GetElementTagName() ),                       'button', "Can get element tag name" );

            my $hammertime = $session->FindElement( using => 'css selector', value => '#hammertime' );
            ok( !$hammertime->IsElementEnabled(), "IsElementEnabled works" );

            my $clickme = $session->FindElement( using => 'css selector', value => '#clickme' );
            is( $clickme->GetElementText(), 'PARTY HARD', "Can get element text" );

            $driver->{fatal} = 0;
            is( dies { $session->FindElement( using => 'css selector', value => 'bogus' ) }, undef, "Turning off fatality works" );
            $driver->{fatal} = 1;

            my $rkt = $clickme->GetElementRect();
            ok( defined $rkt->{x}, "GetElementRect appears to function" );

            my $input = $session->FindElement( using => 'css selector', value => 'input[name=text]' );
            $input->ElementClear();
            $input->ElementSendKeys( text => "tickle" );
            is( $input->GetElementProperty( name => 'value' ), 'tickle', "Can clear and send keys to a text input" );

            is( $session->ExecuteScript( script => qq/ return document.querySelector('input').value /, args => [] ),                                       'tickle', "ExecuteScript works" );
            is( $session->ExecuteAsyncScript( script => qq/ return arguments[arguments.length - 1](document.querySelector('input').value) /, args => [] ), 'tickle', "ExecuteAsyncScript works" );

            # Screenshots
            ok( $session->TakeScreenshot(),      "Can take screenshot" );
            ok( $input->TakeElementScreenshot(), "Can take element screenshot" );

            # Perform / Release Actions
            is(
                dies {
                    $session->PerformActions( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyDown', value => 'a' } ] } ] )
                },
                undef,
                "Can perform general actions"
            );
            is( dies { $session->ReleaseActions() },           undef,     "Can release general actions" );
            is( $input->GetElementProperty( name => 'value' ), 'ticklea', "Key sent worked" );

            # Cookies -- Browsers don't allow cookies for local stuff, so let's do it against CPAN
            # XXX lol this site is slow
            $session->SetTimeouts( script => 1000, implicit => 1000, pageLoad => 10000 );

            $session->NavigateTo( url => 'http://cpan.org' );
            $session->AddCookie( cookie => { name => 'tickle', value => 'hug' } );
            my @jar = $session->GetAllCookies();
            ok( scalar( grep { $_->{name} eq 'tickle' } @jar ), "Can set cookies and read them" );
            ok( $session->GetNamedCookie( name => 'tickle' ),   "Can GetNamedCookie" );
            $session->DeleteCookie( name => 'tickle' );
            isnt( dies { $session->GetNamedCookie( name => 'tickle' ) }, undef, "DeleteCookie works" );
            $session->AddCookie( cookie => { name => 'tickle', value => 'hug' } );
            $session->DeleteAllCookies();
            isnt( dies { $session->GetNamedCookie( name => 'tickle' ) }, undef, "DeleteAllCookies works" );

            is( dies { $session->DeleteSession( session_id => $session->{sessionId} ) }, undef, "Can delete session" );

        };
    }
}

sub alertify ( $session, $browser ) {
    is( eval { $session->GetAlertText() } // $@, 'BEEE DOOO',                        "Can get alert text" );
    is( dies { $session->AcceptAlert() },        undef,                              "Can dismiss alert" );
    is( eval { $session->GetAlertText() } // $@, 'Are you a fugitive from Justice?', "Can get alert text on subsequent alert" ) if $browser ne 'firefox';
    todo "Get alert text on subsequent alert fails on firefox" => sub {
        is( eval { $session->GetAlertText() } // $@, 'Are you a fugitive from Justice?', "Can get alert text on subsequent alert" );
      }
      if $browser eq 'firefox';
    is( dies { $session->SendAlertText( text => "HORGLE" ) }, undef, "send_keys_to_prompt works" );
    is( dies { $session->DismissAlert() },                    undef, "Can accept alert" );
}

done_testing();