use warnings;
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;

BEGIN {
    require 't/test-lib.pm';
    require 't/oidc-lib.pm';
}

my $debug = 'error';
my $access_token;
my $op;
my $res;

# Initialization
ok( $op = op(), 'OP portal' );

subtest "Request RP1, wait for timeout, request RP2" => sub {

    # Request for RP1
    my $authrequest1 = buildForm( {
            scope         => "openid",
            response_type => "code",
            client_id     => "rpid",
            redirect_uri  => "http://rp1.example.com/",
        }
    );
    ok(
        $res = $op->_get(
            '/oauth2/authorize',
            accept => 'text/html',
            query  => $authrequest1
        ),
        'Authorization request to RP1'
    );

    my $pdata = expectCookie( $res, 'lemonldappdata' );

# Uncomment this to wait until issuersTimeout has expired, leading to a different error
    Time::Fake->offset("+10m");

    # Request for RP2 with previous pdata still around
    my $authrequest2 = buildForm( {
            scope         => "openid",
            response_type => "code",
            client_id     => "rp2id",
            redirect_uri  => "http://rp2.example.com/",
        }
    );
    ok(
        $res = $op->_get(
            '/oauth2/authorize',
            accept => 'text/html',
            query  => $authrequest2,
            cookie => "lemonldappdata=$pdata",

        ),
        'Authorization request to RP2'
    );

    $pdata = expectCookie( $res, 'lemonldappdata' );
    my ( $host, $url, $query ) =
      expectForm( $res, '#', undef, 'user', 'password' );

    $query =~ s/user=/user=dwho/;
    $query =~ s/password=/password=dwho/;

    # Login to OP
    ok(
        $res = $op->_post(
            '/oauth2/authorize',
            IO::String->new($query),
            query  => $authrequest2,
            accept => 'text/html',
            length => length($query),
            cookie => "lemonldappdata=$pdata",

        ),
        'Authorization request to RP2'
    );

    $pdata = expectCookie( $res, 'lemonldappdata' );

    # Process second factor
    ( $host, $url, $query ) =
      expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code' );

    ok(
        $res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code"%,
        'Found EXTCODE input'
    ) or print STDERR Dumper( $res->[2]->[0] );

    $query =~ s/code=/code=A1b2C0/;
    ok(
        $res = $op->_post(
            '/ext2fcheck',
            IO::String->new($query),
            length => length($query),
            accept => 'text/html',
            cookie => "lemonldappdata=$pdata",

        ),
        'Post code'
    );

    # We now should be logged in, but lost the original URL
    expectRedirection( $res, "http://auth.op.com/oauth2" );
    my $id = expectCookie($res);

    ok(
        $res = $op->_get(
            '/oauth2',
            accept => 'text/html',
            cookie => "lemonldap=$id; lemonldappdata=$pdata",
        ),
        'Authorization request to RP1'
    );

    # We should be redirected to RP2
    expectRedirection( $res, qr#^http://rp2.example.com/# );
};

subtest "Request RP1, wait for timeout, complete login" => sub {

    Time::Fake->reset;

    # Request for RP1
    my $authrequest1 = buildForm( {
            scope         => "openid",
            response_type => "code",
            client_id     => "rpid",
            redirect_uri  => "http://rp1.example.com/",
        }
    );
    ok(
        $res = $op->_get(
            '/oauth2/authorize',
            accept => 'text/html',
            query  => $authrequest1
        ),
        'Authorization request to RP1'
    );

    my $pdata = expectCookie( $res, 'lemonldappdata' );
    my ( $host, $url, $query ) =
      expectForm( $res, '#', undef, 'user', 'password' );
    $query =~ s/user=/user=dwho/;
    $query =~ s/password=/password=dwho/;

    # Login to OP
    ok(
        $res = $op->_post(
            '/oauth2/authorize',
            IO::String->new($query),
            query  => $authrequest1,
            accept => 'text/html',
            length => length($query),
            cookie => "lemonldappdata=$pdata",

        ),
        'Authorization request to RP2'
    );

    $pdata = expectCookie( $res, 'lemonldappdata' );

    # Process second factor
    ( $host, $url, $query ) =
      expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code' );

    ok(
        $res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code"%,
        'Found EXTCODE input'
    ) or print STDERR Dumper( $res->[2]->[0] );

    $query =~ s/code=/code=A1b2C0/;

    # Wait long enough for the issuersTimeout to expire
    Time::Fake->offset("+10m");

    ok(
        $res = $op->_post(
            '/ext2fcheck',
            IO::String->new($query),
            length => length($query),
            accept => 'text/html',
            cookie => "lemonldappdata=$pdata",

        ),
        'Post code'
    );
    $pdata = expectCookie( $res, 'lemonldappdata' );

    # We now should be logged in, but lost the original URL
    expectRedirection( $res, "http://auth.op.com/oauth2" );
    my $id = expectCookie($res);

    ok(
        $res = $op->_get(
            '/oauth2',
            accept => 'text/html',
            cookie => "lemonldap=$id; lemonldappdata=$pdata",
        ),
        'Authorization request to RP1'
    );
    expectPortalError( $res, 113 );
    expectXpath( $res, '//span[@trspan="connectedAs"]' );
    $pdata = expectCookie( $res, 'lemonldappdata' );
    ok( !$pdata, "pdata was cleared" );

};

subtest "Within timeout, abandonned access to rp1 does not interfere" => sub {

    # Request for RP1
    my $authrequest1 = buildForm( {
            scope         => "openid",
            response_type => "code",
            client_id     => "rpid",
            redirect_uri  => "http://rp1.example.com/",
        }
    );
    ok(
        $res = $op->_get(
            '/oauth2/authorize',
            accept => 'text/html',
            query  => $authrequest1
        ),
        'Authorization request to RP1'
    );

    my $pdata = expectCookie( $res, 'lemonldappdata' );

    # Request for RP2 with previous pdata still around
    my $authrequest2 = buildForm( {
            scope         => "openid",
            response_type => "code",
            client_id     => "rp2id",
            redirect_uri  => "http://rp2.example.com/",
        }
    );
    ok(
        $res = $op->_get(
            '/oauth2/authorize',
            accept => 'text/html',
            query  => $authrequest2,
            cookie => "lemonldappdata=$pdata",

        ),
        'Authorization request to RP2'
    );

    $pdata = expectCookie( $res, 'lemonldappdata' );
    my ( $host, $url, $query ) =
      expectForm( $res, '#', undef, 'user', 'password' );

    $query =~ s/user=/user=dwho/;
    $query =~ s/password=/password=dwho/;

    # Login to OP
    ok(
        $res = $op->_post(
            '/oauth2/authorize',
            IO::String->new($query),
            query  => $authrequest2,
            accept => 'text/html',
            length => length($query),
            cookie => "lemonldappdata=$pdata",

        ),
        'Authorization request to RP2'
    );

    $pdata = expectCookie( $res, 'lemonldappdata' );

    # Process second factor
    ( $host, $url, $query ) =
      expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code' );

    ok(
        $res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code"%,
        'Found EXTCODE input'
    ) or print STDERR Dumper( $res->[2]->[0] );

    $query =~ s/code=/code=A1b2C0/;
    ok(
        $res = $op->_post(
            '/ext2fcheck',
            IO::String->new($query),
            length => length($query),
            accept => 'text/html',
            cookie => "lemonldappdata=$pdata",

        ),
        'Post code'
    );

    # We now should be logged in, but lost the original URL
    expectRedirection( $res, "http://auth.op.com/oauth2" );
    my $id = expectCookie($res);

    ok(
        $res = $op->_get(
            '/oauth2',
            accept => 'text/html',
            cookie => "lemonldap=$id; lemonldappdata=$pdata",
        ),
        'Authorization request to RP1'
    );

    # We should be redirected to RP2
    expectRedirection( $res, qr#^http://rp2.example.com/# );
};

clean_sessions();
done_testing();

sub op {
    return LLNG::Manager::Test->new( {
            ini => {
                logLevel                        => $debug,
                domain                          => 'idp.com',
                sfLoginTimeout                  => "1800",
                issuersTimeout                  => "300",
                portal                          => 'http://auth.op.com/',
                authentication                  => 'Demo',
                userDB                          => 'Same',
                ext2fActivation                 => 1,
                ext2fCodeActivation             => 'A1b2C0',
                ext2FSendCommand                => '/bin/true',
                issuerDBOpenIDConnectActivation => "1",
                restSessionServer               => 1,
                oidcRPMetaDataExportedVars      => {
                    rp => {
                        email       => "mail",
                        family_name => "cn",
                        name        => "cn"
                    },
                    rp2 => {
                        email       => "mail",
                        family_name => "cn",
                        name        => "cn"
                    }
                },
                oidcServiceAllowHybridFlow            => 1,
                oidcServiceAllowImplicitFlow          => 1,
                oidcServiceAllowAuthorizationCodeFlow => 1,
                oidcRPMetaDataOptions                 => {
                    rp => {
                        oidcRPMetaDataOptionsDisplayName       => "RP",
                        oidcRPMetaDataOptionsIDTokenExpiration => 3600,
                        oidcRPMetaDataOptionsClientID          => "rpid",
                        oidcRPMetaDataOptionsIDTokenSignAlg    => "HS512",
                        oidcRPMetaDataOptionsBypassConsent     => 0,
                        oidcRPMetaDataOptionsClientSecret      => "rpsecret",
                        oidcRPMetaDataOptionsUserIDAttr        => "",
                        oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
                        oidcRPMetaDataOptionsBypassConsent          => 1,
                        oidcRPMetaDataOptionsPostLogoutRedirectUris =>
                          "http://auth.rp.com/oauth2/rlogoutreturn",
                        oidcRPMetaDataOptionsRedirectUris =>
                          'http://rp1.example.com/',
                    },
                    rp2 => {
                        oidcRPMetaDataOptionsDisplayName       => "RP",
                        oidcRPMetaDataOptionsIDTokenExpiration => 3600,
                        oidcRPMetaDataOptionsClientID          => "rp2id",
                        oidcRPMetaDataOptionsIDTokenSignAlg    => "HS512",
                        oidcRPMetaDataOptionsBypassConsent     => 0,
                        oidcRPMetaDataOptionsClientSecret      => "rp2secret",
                        oidcRPMetaDataOptionsUserIDAttr        => "",
                        oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
                        oidcRPMetaDataOptionsBypassConsent          => 1,
                        oidcRPMetaDataOptionsPostLogoutRedirectUris =>
                          "http://auth.rp2.com/oauth2/rlogoutreturn",
                        oidcRPMetaDataOptionsRedirectUris =>
                          'http://rp2.example.com/',
                    }
                },
                oidcOPMetaDataOptions           => {},
                oidcOPMetaDataJSON              => {},
                oidcOPMetaDataJWKS              => {},
                oidcServiceMetaDataAuthnContext => {
                    'loa-4' => 4,
                    'loa-1' => 1,
                    'loa-5' => 5,
                    'loa-2' => 2,
                    'loa-3' => 3
                },
                oidcServicePrivateKeySig => oidc_key_op_private_sig,
                oidcServicePublicKeySig  => oidc_cert_op_public_sig,
            }
        }
    );
}

