use Mojo::Base -strict;

use Galileo::DB::Deploy;

use Test::More;
use Test::Mojo;
use Mojo::JSON 'j';
use Mojo::DOM;
use Mojo::JSON qw/true false/;
use Mojo::Util 'trim';

my $t = Galileo::DB::Deploy->create_test_object({test => 1});
$t->ua->max_redirects(2);

my $trimmed_text_is = sub {
  my ($t, $selector, $expect, $desc) = @_;
  $desc //= qq[exact match for selector "$selector"];
  local $Test::Builder::Level = $Test::Builder::Level + 1;
  my $text = trim($t->tx->res->dom->at($selector)->text || '');
  $t->success(is $text, $expect, $desc);
};

subtest 'Anonymous User' => sub {

  # landing page
  $t->get_ok('/')
    ->status_is(200)
    ->text_is( h1 => 'Galileo CMS' )
    ->text_is( h2 => 'Welcome to your Galileo CMS site!' )
    ->content_like( qr/very modern/ )
    ->element_exists( 'form#login' )
    ->element_exists_not( '#user-menu' );

  # attempt to get non-existant page
  $t->get_ok('/page/doesntexist')
    ->status_is(404);

  # attempt to edit page
  $t->get_ok('/edit/home')
    ->status_is(200)
    ->content_like( qr/Not Authorized/ );

  # attempt to menu admin page
  $t->get_ok('/admin/menu')
    ->status_is(200)
    ->content_like( qr/Not Authorized/ );

  # attempt to user admin page
  $t->get_ok('/admin/users')
    ->status_is(200)
    ->content_like( qr/Not Authorized/ );

};

subtest 'Do Login' => sub {

  # fail username
  $t->post_ok( '/login' => form => {from => '/page/home', username => 'wronguser', password => 'pass' } )
    ->status_is(200)
    ->content_like( qr/Sorry try again/ )
    ->element_exists( 'form#login' )
    ->element_exists_not( '#user-menu' );

  # fail password
  $t->post_ok( '/login' => form => {from => '/page/home', username => 'admin', password => 'wrongpass' } )
    ->status_is(200)
    ->content_like( qr/Sorry try again/ )
    ->element_exists( 'form#login' )
    ->element_exists_not( '#user-menu' );

  # successfully login
  $t->post_ok( '/login' => form => {from => '/page/home', username => 'admin', password => 'pass' } )
    ->status_is(200)
    ->content_like( qr/Welcome Back/ )
    ->element_exists_not( 'form#login' )
    ->text_like( '#user-menu li' => qr/Hello admin/ )
    ->element_exists( '#page-modal #new-page-link' )
    ->element_exists( '#user-modal #new-username' );

};

subtest 'Edit Page' => sub {

  # page editor
  $t->get_ok('/edit/home')
    ->status_is(200)
    ->text_like( '#wmd-input' => qr/Welcome to your Galileo CMS site!/ )
    ->element_exists( '#wmd-preview' );

  # save page
  my $text = 'I changed this text ☃';
  my $data = {
    name  => 'home',
    title => 'New Home',
    html  => "<p>$text</p>",
    md    => $text,
  };
  $t->websocket_ok( '/store/page' )
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => true, message => 'Changes saved' } )
    ->finish_ok;

  # see that the changes are reflected
  $t->get_ok('/page/home')
    ->status_is(200)
    ->text_is( h1 => 'New Home' )
    ->text_like( '#content p' => qr/$text/ );

  # save page without title (error)
  my $data_notitle = {
    name  => 'notitle',
    title => '',
    html  => '<p>Hmmm no title</p>',
    md    => 'Hmmm no title',
  };
  $t->websocket_ok( '/store/page' )
    ->send_ok({ json => $data_notitle })
    ->message_ok
    ->json_message_is( { success => false, message => 'Not saved! A title is required!' } )
    ->finish_ok;

};

subtest 'New Page' => sub {

  # author request non-existant page => create new page
  $t->get_ok('/page/doesntexist')
    ->status_is(200)
    ->text_like( '#wmd-input' => qr/Hello World/ )
    ->element_exists( '#wmd-preview' );

  # save page
  my $text = 'Today it snowed so ☃ gets a new home';
  my $data = {
    name  => 'snow❄flake',
    title => 'New Home for ☃',
    html  => "<p>$text</p>",
    md    => $text,
  };
  $t->websocket_ok( '/store/page' )
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => true, message => 'Changes saved' } )
    ->finish_ok;

  # see that the changes are reflected
  $t->get_ok('/page/snow❄flake')
    ->status_is(200)
    ->text_is( h1 => 'New Home for ☃' )
    ->text_like( '#content p' => qr/$text/ );

};

subtest 'Edit Main Navigation Menu' => sub {
  my $title = 'About Galileo';

  # check about page is in nav
  $t->get_ok('/admin/menu')
    ->status_is(200)
    ->$trimmed_text_is( 'ul#main > li:nth-of-type(4) > a' => $title )
    ->text_is( '#list-active-pages > #pages-2 > span' => $title );

  # remove about page from list
  my $data = {
    name => 'main',
    list => [],
  };
  $t->websocket_ok('/store/menu')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( '/success' => true )
    ->json_message_is( '/message' => 'Changes saved' )
    ->json_message_has( '/content' )
    ->finish_ok;

  my @items = Mojo::DOM
    ->new(j($t->message->[1])->{content})
    ->find('#nav_menu li')
    ->map('all_text')
    ->each;
  is_deeply \@items, ['Navigation', 'Home'];

  # check that item is removed
  $t->get_ok('/admin/menu')
    ->status_is(200)
    ->element_exists_not( 'ul#main > li:nth-of-type(3) > a' )
    ->text_is( '#list-inactive-pages > #pages-2 > span' => $title );

  # put about page back
  $data = {
    name => 'main',
    list => ['pages-3', 'pages-2'],
  };
  $t->websocket_ok('/store/menu')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( '/success' => true )
    ->json_message_is( '/message' => 'Changes saved' )
    ->json_message_has( '/content' )
    ->finish_ok;

  @items = ();
  @items = Mojo::DOM
    ->new(j($t->message->[1])->{content})
    ->find('#nav_menu li')
    ->map('all_text')
    ->map(\&trim)
    ->each;
  is_deeply \@items, ['Navigation', 'Home', 'Syntax', $title];

  # check about page is back in nav (same as first test block)
  $t->get_ok('/admin/menu')
    ->status_is(200)
    ->$trimmed_text_is( 'ul#main > li:nth-of-type(4) > a' => $title )
    ->text_is( '#list-active-pages > #pages-2 > span' => $title );

  my @ids = $t->tx->res->dom->find('#list-active-pages li')->map(sub{ $_->{'id'} })->each;
  is_deeply \@ids, ['header-active', 'pages-3', 'pages-2'], 'active pages in correct order';
};

subtest 'Administrative Overview: All Users' => sub {

  # test the admin pages
  $t->get_ok('/admin/users')
    ->status_is(200)
    ->text_is( h1 => 'Administration: Users' )
    ->text_is( 'tr > td:nth-of-type(2)' => 'admin' )
    ->text_is( 'tr > td:nth-of-type(3)' => 'Joe Admin' );

};

subtest 'Administrative Overview: All Pages' => sub {

  $t->get_ok('/admin/pages')
    ->status_is(200)
    ->text_is( h1 => 'Administration: Pages' )
    ->text_is( 'tr > td:nth-of-type(2)' => 'home' );

  # attempt to remove home page
  $t->websocket_ok('/remove/page')
    ->send_ok({ json => {id => 1} })
    ->message_ok
    ->json_message_is( { success => false, message => 'Cannot remove home page' } )
    ->finish_ok;

  # attempt to remove invalid page
  $t->websocket_ok('/remove/page')
    ->send_ok({ json => {id => 5} })
    ->message_ok
    ->json_message_is( { success => false, message => 'Could not access page (id 5)' } )
    ->finish_ok;

  # remove page
  $t->websocket_ok('/remove/page')
    ->send_ok({ json => {id => 2} })
    ->message_ok
    ->json_message_is( { success => true, message => 'Page removed' } )
    ->finish_ok;

};

subtest 'Administer Users' => sub {

  $t->get_ok('/admin/user/admin')
    ->status_is(200)
    ->element_exists( 'input#name[placeholder=admin]' )
    ->element_exists( 'input#full[value="Joe Admin"]' )
    ->element_exists( 'input#is_author:checked' )
    ->element_exists( 'input#is_admin:checked' );

  # change name
  my $data = {
    name => "admin",
    full => "New Name",
    is_author => true,
    is_admin => true,
  };
  $t->websocket_ok('/store/user')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => true, message => 'Changes saved' } )
    ->finish_ok;

  # check that the name change is reflected
  $t->get_ok('/admin/user/admin')
    ->status_is(200)
    ->element_exists( 'input#name[placeholder=admin]' )
    ->element_exists( 'input#full[value="New Name"]' );

  # attempt to change password, incorrectly
  $data = {
    name => "admin",
    full => "New Name",
    pass1 => 'newpass',
    pass2 => 'wrongpass',
    is_author => true,
    is_admin => true,
  };
  $t->websocket_ok('/store/user')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => false, message => 'Not saved! Passwords do not match' } )
    ->finish_ok;

  ok( $t->app->get_user('admin')->check_password('pass'), 'Password not changed on non-matching passwords');

  # change password, correctly
  $data = {
    name => "admin",
    full => "New Name",
    pass1 => 'newpass',
    pass2 => 'newpass',
    is_author => true,
    is_admin => true,
  };
  $t->websocket_ok('/store/user')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => true, message => 'Changes saved' } )
    ->finish_ok;

  ok( $t->app->get_user('admin')->check_password('newpass'), 'New password checks out');

};

subtest 'Create New User' => sub {

  # attempt to create a user without providing a password (fails)
  my $data = {
    name => "someone",
    full => "Jane ☃ Dow",
    is_author => true,
    is_admin => false,
  };
  $t->websocket_ok('/store/user')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => false, message => 'Cannot create user without a password' } )
    ->finish_ok;

  # create a user
  $data = {
    name => "someone",
    full => "Jane ☃ Doe",
    pass1 => 'mypass',
    pass2 => 'mypass',
    is_author => true,
    is_admin => false,
  };
  $t->websocket_ok('/store/user')
    ->send_ok({ json => $data })
    ->message_ok
    ->json_message_is( { success => true, message => 'Changes saved' } )
    ->finish_ok;

  # check the new user
  $t->get_ok('/admin/user/someone')
    ->status_is(200)
    ->element_exists( 'input#name[placeholder=someone]' )
    ->element_exists( 'input#full[value="Jane ☃ Doe"]' )
    ->element_exists( 'input#is_author:checked' )
    ->element_exists( 'input#is_admin:not(:checked)' );

};

subtest 'Extra CSS/JS' => sub {
  my $app = $t->app;
  local $app->config->{extra_css} = ['mytest.css'];
  local $app->config->{extra_js}  = ['mytest.js' ];
  $t->get_ok('/')
    ->status_is(200)
    ->element_exists( 'link[href=mytest.css]' )
    ->element_exists( 'script[src=mytest.js]' );
};

subtest 'Logging Out' => sub {
  # This is essentially a repeat of the first test
  $t->get_ok('/logout')
    ->status_is(200)
    ->text_is( h1 => 'New Home' )
    ->element_exists( 'form' );
};

done_testing();

