#!perl
use v5.36;
use Test::More;

do_tests();
done_testing();

#######################################################################
# We test the three "single-file" app examples here                   #
#######################################################################

sub do_tests {

  #
  # Hello World app
  #
  {
    my $app = do './examples/single-file-apps/hello-world.psgi';
    my ($test_env, $response);

    ok(
      ($app and ref $app eq 'CODE'),
      'app loaded correctly and is a coderef'
    );

     $test_env = test_env();
    $response = $app->($test_env);

    ok(
      (ref $response eq 'ARRAY' and scalar @$response == 3),
      'app execution returns PSGI response arrayref'
    );
    is(
      $response->[0] => 200,
      'response status correct'
    );

    is_deeply(
      $response->[1] => ['Content-Type', 'text/html; charset=utf8'],
      'response content-type correct'
    );

    is(
      $response->[2][0] => 'Hello World!',
      'response body is correct'
    );

    # Test different query strings
    $test_env = test_env();
    $test_env->{QUERY_STRING} = 'name=Larry%20Wall';
    $response = $app->($test_env);
    is(
      $response->[2][0] => 'Hello Larry Wall',
      'response body is correct, different query string'
    );

    foreach my $wrong_path (qw|hello-world helo word helllo helloo worlld wworld worldd|, 'hello/world', 'world/hello') {
      $test_env = test_env();
      $test_env->{PATH_INFO} = "/$wrong_path";
      $response = $app->($test_env);
      is(
        $response->[0] => 404,
        "404 issued for non-existant route $wrong_path"
      );
    }

    foreach my $right_path (qw|hello world|) {
      $test_env = test_env();
      $test_env->{PATH_INFO} = "/$right_path";
      $response = $app->($test_env);
      is(
        $response->[2][0] => "You said ~$right_path~",
        "response body is correct with route param $right_path"
      );
    }
  }

  #
  # Form app
  #
  {
    my $app = do './examples/single-file-apps/form-app.psgi';
    my ($test_env, $response);

    ok(
      ($app and ref $app eq 'CODE'),
      'app loaded correctly and is a coderef'
    );

    $test_env = test_env();
    $response = $app->($test_env);

    ok(
      (ref $response eq 'ARRAY' and scalar @$response == 3),
      'app execution returns PSGI response arrayref'
    );
    is(
      $response->[0] => 200,
      'response status correct'
    );

    is_deeply(
      $response->[1] => ['Content-Type', 'text/html; charset=utf8'],
      'response content-type correct'
    );

    my $body;
    $body = join '', $response->[2]->@*;
    ok(
      $body =~ m|<html>|,
      'body has html open tag - prefilter works'
    );
    ok(
      $body =~ m|</html>|,
      'body has html close tag - postfilter works'
    );
    ok(
      $body =~ m|--Generated by|,
      '"Generated by" - postfilter works'
    );
    ok(
      $body =~ m|Please.+to continue|,
      'body has correct content'
    );

    # Test /login
    $test_env = test_env();
    $test_env->{PATH_INFO} = '/login';
    $response = $app->($test_env);
    $body = join '', $response->[2]->@*;

    ok(
      $body =~ m|<form|,
      '/login page has form'
    );

    $test_env = test_env();
    $test_env->{PATH_INFO} = '/login/submit';
    $response = $app->($test_env);
    $body = join '', $response->[2]->@*;

    is(
      $response->[0] => 405,
      'GET a page only accessible by POST should give 405 error'
    );

    ok(
      $body =~ m|cannot login|,
      'Correct error message'
    );

    # Post request
    use Plack::Test;
    use HTTP::Request::Common;
    test_psgi $app, sub ($server) {
        my $res = $server->(POST "/login/submit", { username => "mary", password => "lamb" });
        ok(
          $res->content =~ m/Incorrect username/,
          'Login with wrong username'
        );
    };

    test_psgi $app, sub ($server) {
        my $res = $server->(POST "/login/submit", { username => "larry", password => "perl" });
        ok(
          $res->content =~ m/Welcome back/,
          'Login with correct username'
        );
    };

    # Flash set
    my $message = 'hello-' . int(rand() * 10_000);
    $test_env = test_env();
    $test_env->{PATH_INFO} = "/flash/set/$message";
    $response = $app->($test_env);

    my $cookie_name;
    my $cookie_value;
    while (my $header = shift $response->[1]->@*) {
      next unless $header =~ m/^set-cookie$/i;
      my $value = shift $response->[1]->@*;
      ok(
        $value =~ m|$message|,
        'Cookie is set'
      );
      ($cookie_name, $cookie_value) = $value =~ m|^(.+?)=(.+?);|;
      last;
    }

    # Flash read
    $test_env = test_env();
    $test_env->{PATH_INFO} = "/flash/view";
    $test_env->{HTTP_COOKIE} = "$cookie_name=$cookie_value";
    $response = $app->($test_env);
    $body = join '', $response->[2]->@*;
    ok(
      $body =~ m|$message|,
      'Flash value is retrieved from client'
    );

    # Flash should be unset
    ok(
      (grep { $_ =~ m|^$cookie_name=;| } $response->[1]->@*),
      'Cookie set to blank value on next request'
    );

    # Cleanup callback
    {
      # silence used only once warning
      my $xyz = $PXF::Example::FormApp::Routes::counter;
    }
    $test_env = test_env();
    $test_env->{PATH_INFO} = "/callback";
    $app->($test_env) for 1..3;
    is(
      $PXF::Example::FormApp::Routes::counter => 3,
      'Cleanup callback works'
    );

    # Test non-dsl route and global filter
    $test_env = test_env();
    $test_env->{PATH_INFO} = "/page/12/view";
    $response = $app->($test_env);
    $body = join '', $response->[2]->@*;
    ok(
      $body =~ m|viewing page 12|,
      'Non dsl route with route param'
    );
    ok(
      $body =~ m|Generated by PXF|,
      'Global filter applied outside of package'
    );
  }

  #
  # Streaming app
  #
  {
    # PXF::Handler is already loaded, so knows streaming is off
    my $app = do './examples/single-file-apps/streaming-app.psgi';
    my ($test_env, $response, $body);

    $test_env = test_env();
    $test_env->{PATH_INFO} = "/stream-example";
    $response = $app->($test_env);
    $body = join '', $response->[2]->@*;

    my $should_match = join('.+', map { "Hello $_" } 0..5);
    ok(
      $body =~ m/$should_match/s,
      'Streaming body lines sent in correct order (streaming off)'
    );

    # With streaming
    {
      $test_env = test_env();
      $test_env->{PATH_INFO} = "/stream-example";
      $test_env->{'psgix.streaming'} = !!1;
      {
        no warnings; # used only once
        $PlackX::Framework::Handler::psgix_streaming = !!1;
      }
      $response = $app->($test_env);
      is(
        ref $response => 'CODE',
        'Response under streaming is a coderef'
      );

      require Plack::Util;
      $body = '';
      my $responder = sub ($partial_response) {
        is(
          ref $partial_response => 'ARRAY',
          'Streaming response is arrayref'
        );
        is(
          @$partial_response => 2,
          'Streaming response arrayref has 2 elements'
        );
        is(
          $partial_response->[0] => 200,
          'Streaming response has HTTP status 200'
        );
        my $writer = Plack::Util::inline_object(
          write => sub { $body .= $_[0] },
          close => sub {                },
        );
      };
      $response->($responder);
      ok(
        $body =~ m/$should_match/s,
        'Streaming body lines sent in correct order (streaming on)'
      );
    }
  }
}

###############################################################################
sub test_env {
  return {
    'psgi.version'     => [1, 1],
    'psgi.errors'      => '',
    'psgi.nonblocking' => '',
    'psgi.run_once'    => '',
    'psgi.streaming'   => 0,
    'psgi.url_scheme'  => 'http',
    'PATH_INFO'        => '/',
    'QUERY_STRING'     => '',
    'REMOTE_ADDR'      => '127.0.0.1',
    'REMOTE_PORT'      => 99999,
    'REQUEST_METHOD'   => 'GET',
    'REQUEST_URI'      => '/',
    'SCRIPT_NAME'      => '',
    'SERVER_NAME'      => 0,
    'SERVER_PORT'      => 99999,
    'SERVER_PROTOCOL'  => 'HTTP/1.0',
    'HTTP_ACCEPT'      => 'text/html,text/plain',
    'HTTP_USER_AGENT'  => 'Test',
    'HTTP_DNT'         => '1',
    'HTTP_HOST'        => 'localhost:99999',
  };
}
