#!/usr/bin/env perl

use Modern::Perl;

use CGI qw ( -utf8 );
use Test::MockModule;
use List::MoreUtils qw/all any none/;
use t::lib::Mocks;
use t::lib::TestBuilder;

use C4::Auth qw( get_session checkauth get_template_and_user );
use Koha::Database;

use Test::NoWarnings;
use Test::More tests => 28;
use Test::Warn;
use URI::Escape;
use List::Util qw( shuffle );

use C4::Context;
use Koha::DateUtils qw( dt_from_string output_pref );

my $schema = Koha::Database->new->schema;
$schema->storage->txn_begin;
my $dbh = C4::Context->dbh;

# FIXME: SessionStorage defaults to mysql, but it seems to break transaction
# handling
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );

use_ok('Koha::DateUtils');
use_ok( 'C4::Search::History', qw( add get delete get_from_session ) );

my $userid             = 123;
my $previous_sessionid = "PREVIOUS_SESSIONID";
my $current_sessionid  = "CURRENT_SESSIONID";
my $total              = 42;
my $query_cgi_b        = q{idx=kw&idx=ti&idx=au%2Cwrdl&q=word1é&q=word2è&q=word3à&do=Search&sort_by=author_az};
my $query_cgi_a =
    q{op=do_search&type=opac&authtypecode=NP&operator=start&value=Harry&marclist=match&and_or=and&orderby=HeadingAsc};

# add
my $added = add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
is( $added, 9, '9 searches are added' );

# get
my $searches_for_userid = C4::Search::History::get(
    {
        userid => $userid,
    }
);
is( scalar(@$searches_for_userid), 9, 'There are 9 searches in all' );

my $searches_for_current_session = C4::Search::History::get(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
    }
);
is( scalar(@$searches_for_current_session), 5, 'There are 5 searches for the current session' );

my $searches_for_previous_sessions = C4::Search::History::get(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
        previous  => 1,
    }
);
is( scalar(@$searches_for_previous_sessions), 4, 'There are 4 searches for previous sessions' );

my $authority_searches_for_current_session = C4::Search::History::get(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
        type      => 'authority',
    }
);
is( scalar(@$authority_searches_for_current_session), 3, 'There are 3 authority searches for the current session' );

my $authority_searches_for_previous_session = C4::Search::History::get(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
        type      => 'authority',
        previous  => 1,
    }
);
is( scalar(@$authority_searches_for_previous_session), 2, 'There are 2 authority searches for previous sessions' );

my $biblio_searches_for_userid = C4::Search::History::get(
    {
        userid => $userid,
        type   => 'biblio',
    }
);
is( scalar(@$biblio_searches_for_userid), 4, 'There are 5 searches for the current session' );

my $authority_searches_for_userid = C4::Search::History::get(
    {
        userid => $userid,
        type   => 'authority',
    }
);
is( scalar(@$authority_searches_for_userid), 5, 'There are 4 searches for previous sessions' );

delete_all($userid);

# delete
add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
        type      => 'authority',
    }
);
my $all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 6, 'There are 6 searches in all after deleting current biblio searches' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
        type      => 'biblio',
        previous  => 1,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 7, 'There are 7 searches in all after deleting previous authority searches' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
        previous  => 1,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 5, 'There are 5 searches in all after deleting all previous searches' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid    => $userid,
        sessionid => $current_sessionid,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 4, 'There are 5 searches in all after deleting all searches for a sessionid' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid => $userid,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 0, 'There are 0 search after deleting all searches for a userid' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
warning_like { C4::Search::History::delete( {} ) }
qr/^ERROR: userid, id or interval is required for history deletion/,
    'Calling delete without userid raises warning';
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 9, 'There are still 9 searches after calling delete without userid' );
delete_all($userid);

# Delete (with a given id)
add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
$all = C4::Search::History::get( { userid => $userid } );

# Delete 5 searches
my $ids = [ shuffle map { $_->{id} } @$all ];
for my $id ( @$ids[ 0 .. 4 ] ) {
    C4::Search::History::delete( { id => $id } );
}
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 4, 'There are 4 searches after calling 5 times delete with id' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
$all = C4::Search::History::get( { userid => $userid } );

# Delete 5 searches
$ids = [ shuffle map { $_->{id} } @$all ];
C4::Search::History::delete( { id => [ @$ids[ 0 .. 4 ] ] } );
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 4, 'There are 4 searches after calling delete with 5 ids' );

delete_all($userid);

# Test delete with interval
add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid   => $userid,
        interval => 10,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 9, 'There are still 9 searches after calling delete with an interval = 10 days' );
C4::Search::History::delete(
    {
        userid   => $userid,
        interval => 6,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 8, 'There are still 8 searches after calling delete with an interval = 6 days' );
C4::Search::History::delete(
    {
        userid   => $userid,
        interval => 2,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 2, 'There are still 2 searches after calling delete with an interval = 2 days' );
delete_all($userid);

add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
C4::Search::History::delete(
    {
        userid   => $userid,
        interval => 5,
        type     => 'biblio',
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 8, 'There are still 9 searches after calling delete with an interval = 5 days for biblio' );
C4::Search::History::delete(
    {
        userid   => $userid,
        interval => 5,
        type     => 'authority',
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 6, 'There are still 6 searches after calling delete with an interval = 5 days for authority' );
C4::Search::History::delete(
    {
        userid   => $userid,
        interval => -1,
    }
);
$all = C4::Search::History::get( { userid => $userid } );
is( scalar(@$all), 0, 'There is no search after calling delete with an interval = -1 days' );

# If time is null, it must be set to NOW()
my $query_desc_b1_p = q{first previous biblio search};
C4::Search::History::add(
    {
        userid     => $userid,
        sessionid  => $previous_sessionid,
        query_desc => $query_desc_b1_p,
        query_cgi  => $query_cgi_b,
        total      => $total,
        type       => 'biblio',
    }
);
my $search_history_id = $dbh->last_insert_id( undef, undef, 'search_history', undef );
my $search_history    = C4::Search::History::get( { id => $search_history_id } );
is(
    output_pref( { dt => dt_from_string( $search_history->[0]->{time} ), dateonly => 1 } ),
    output_pref( { dt => dt_from_string,                                 dateonly => 1 } ),
    "Inserting a new search history should handle undefined time"
);

delete_all($userid);

sub add {
    my ( $userid, $current_session_id, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a ) = @_;

    my $days_ago_2 = dt_from_string()->add_duration( DateTime::Duration->new( days => -2, minutes => 1 ) );
    my $days_ago_4 = dt_from_string()->add_duration( DateTime::Duration->new( days => -4, minutes => 1 ) );
    my $days_ago_6 = dt_from_string()->add_duration( DateTime::Duration->new( days => -6, minutes => 1 ) );
    my $days_ago_8 = dt_from_string()->add_duration( DateTime::Duration->new( days => -8, minutes => 1 ) );

    my $query_desc_b1_p              = q{first previous biblio search};
    my $first_previous_biblio_search = {
        userid     => $userid,
        sessionid  => $previous_sessionid,
        query_desc => $query_desc_b1_p,
        query_cgi  => $query_cgi_b,
        total      => $total,
        type       => 'biblio',
        time       => $days_ago_2,
    };

    my $query_desc_a1_p                 = q{first previous authority search};
    my $first_previous_authority_search = {
        userid     => $userid,
        sessionid  => $previous_sessionid,
        query_desc => $query_desc_a1_p,
        query_cgi  => $query_cgi_a,
        total      => $total,
        type       => 'authority',
        time       => $days_ago_2,
    };

    my $query_desc_b2_p               = q{second previous biblio search};
    my $second_previous_biblio_search = {
        userid     => $userid,
        sessionid  => $previous_sessionid,
        query_desc => $query_desc_b2_p,
        query_cgi  => $query_cgi_b,
        total      => $total,
        type       => 'biblio',
        time       => $days_ago_4,
    };

    my $query_desc_a2_p                  = q{second previous authority search};
    my $second_previous_authority_search = {
        userid     => $userid,
        sessionid  => $previous_sessionid,
        query_desc => $query_desc_a2_p,
        query_cgi  => $query_cgi_a,
        total      => $total,
        type       => 'authority',
        time       => $days_ago_4,
    };

    my $query_desc_b1_c = q{first current biblio search};

    my $first_current_biblio_search = {
        userid     => $userid,
        sessionid  => $current_sessionid,
        query_desc => $query_desc_b1_c,
        query_cgi  => $query_cgi_b,
        total      => $total,
        type       => 'biblio',
        time       => $days_ago_4,
    };

    my $query_desc_a1_c                = q{first current authority search};
    my $first_current_authority_search = {
        userid     => $userid,
        sessionid  => $current_sessionid,
        query_desc => $query_desc_a1_c,
        query_cgi  => $query_cgi_a,
        total      => $total,
        type       => 'authority',
        time       => $days_ago_4,
    };

    my $query_desc_b2_c              = q{second current biblio search};
    my $second_current_biblio_search = {
        userid     => $userid,
        sessionid  => $current_sessionid,
        query_desc => $query_desc_b2_c,
        query_cgi  => $query_cgi_b,
        total      => $total,
        type       => 'biblio',
        time       => $days_ago_6,
    };

    my $query_desc_a2_c                 = q{second current authority search};
    my $second_current_authority_search = {
        userid     => $userid,
        sessionid  => $current_sessionid,
        query_desc => $query_desc_a2_c,
        query_cgi  => $query_cgi_a,
        total      => $total,
        type       => 'authority',
        time       => $days_ago_6,
    };

    my $query_desc_a3_c                = q{third current authority search};
    my $third_current_authority_search = {
        userid     => $userid,
        sessionid  => $current_sessionid,
        query_desc => $query_desc_a3_c,
        query_cgi  => $query_cgi_a,
        total      => $total,
        type       => 'authority',
        time       => $days_ago_8,
    };

    my $r = 0;
    $r += C4::Search::History::add($first_current_biblio_search);
    $r += C4::Search::History::add($first_current_authority_search);
    $r += C4::Search::History::add($second_current_biblio_search);
    $r += C4::Search::History::add($second_current_authority_search);
    $r += C4::Search::History::add($first_previous_biblio_search);
    $r += C4::Search::History::add($first_previous_authority_search);
    $r += C4::Search::History::add($second_previous_biblio_search);
    $r += C4::Search::History::add($second_previous_authority_search);
    $r += C4::Search::History::add($third_current_authority_search);
    return $r;
}

sub delete_all {
    my $userid = shift;
    C4::Search::History::delete(
        {
            userid => $userid,
        }
    );
}

subtest 'LoadSearchHistoryToTheFirstLoggedUser working' => sub {
    plan tests => 2;

    my $query = CGI->new;

    my $schema  = Koha::Database->schema;
    my $builder = t::lib::TestBuilder->new;

    # Borrower Creation
    our $patron = $builder->build( { source => 'Borrower' } );
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
    Koha::Patrons->find( $patron->{borrowernumber} )->set_password( { password => 'password' } );

    my $session = C4::Auth::get_session("");
    $session->flush;

    sub myMockedget_from_session {
        my $expected_recent_searches = [
            {
                'time'       => dt_from_string,
                'query_cgi'  => 'cgi_test',
                'total'      => 2,
                'query_desc' => 'kw,wrdl: history, '
            }
        ];
        return @{$expected_recent_searches};

    }

    my $getfrom = Test::MockModule->new('C4::Search::History');
    $getfrom->mock( 'get_from_session', \&myMockedget_from_session );

    my $cgi = Test::MockModule->new('CGI');
    $cgi->mock(
        'cookie',
        sub {
            my ( $self, $key ) = @_;
            if ( !ref($key) && $key eq 'CGISESSID' ) {
                return 'ID';
            }
        }
    );

    sub MockedCheckauth {
        my ( $query, $authnotrequired, $flagsrequired, $type ) = @_;
        my $userid    = $patron->{userid};
        my $sessionID = 234;
        my $flags     = {
            superlibrarian   => 1, acquisition => 0,
            borrowers        => 0,
            catalogue        => 1, circulate       => 0,
            coursereserves   => 0, editauthorities => 0,
            editcatalogue    => 0,
            parameters       => 0, permissions => 0,
            plugins          => 0, reports     => 0,
            reserveforothers => 0, serials     => 0,
            staffaccess      => 0, tools       => 0,
            updatecharges    => 0
        };

        my $session_cookie = $query->cookie(
            -name     => 'CGISESSID',
            -value    => '9884013ae2c441d12e0bc9376242d2a8',
            -HttpOnly => 1
        );
        return ( $userid, $session_cookie, $sessionID, $flags );
    }

    # Mock checkauth
    my $auth = Test::MockModule->new('C4::Auth');
    $auth->mock( 'checkauth', \&MockedCheckauth );

    $query->param( 'koha_login_context', 'opac' );
    $query->param( 'userid',             $patron->{userid} );
    $query->param( 'password',           'password' );

    # Test when the syspref is disabled
    t::lib::Mocks::mock_preference( 'LoadSearchHistoryToTheFirstLoggedUser', 0 );
    my $result = $schema->resultset('SearchHistory')->search()->count;

    my ( $template, $loggedinuser, $cookies ) = get_template_and_user(
        {
            template_name => "opac-user.tt",
            query         => $query,
            type          => "opac",
            debug         => 1
        }
    );

    my $result2 = $schema->resultset('SearchHistory')->search()->count;
    is( $result2, $result, 'no new search added to borrower' );

    # Test when the syspref is enabled
    t::lib::Mocks::mock_preference( 'LoadSearchHistoryToTheFirstLoggedUser', 1 );
    $query->param( 'koha_login_context', 'opac' );
    $query->param( 'userid',             $patron->{userid} );
    $query->param( 'password',           'password' );
    $query->cookie(
        -name     => 'CGISESSID',
        -value    => $session->id,
        -HttpOnly => 1
    );

    $result = $schema->resultset('SearchHistory')->search()->count;

    ( $template, $loggedinuser, $cookies ) = get_template_and_user(
        {
            template_name => "opac-user.tt",
            query         => $query,
            type          => "opac",
            debug         => 1
        }
    );

    $result2 = $schema->resultset('SearchHistory')->search()->count;
    is( $result2, $result + 1, 'new search added to borrower' );
};
