#!/usr/bin/perl

# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.

use Modern::Perl;

# Dummy app for testing the plugin
use Mojolicious::Lite;
use Try::Tiny;

use Koha::Cities;
use Koha::Holds;
use Koha::Biblios;
use Koha::Patron::Relationship;

app->log->level('error');

plugin 'Koha::REST::Plugin::Query';

get '/empty' => sub {
    my $c = shift;
    $c->render( json => undef, status => 200 );
};

get '/query' => sub {
    my $c     = shift;
    my ( $filtered_params, $reserved_params ) = $c->extract_reserved_params($c->req->params->to_hash);
    $c->render(
        json => {
            filtered_params => $filtered_params,
            reserved_params => $reserved_params
        },
        status => 200
    );
};

get '/query_full/:id/:subid' => sub {
    my $c     = shift;
    my $params = $c->req->params->to_hash;
    $params->{id} = $c->stash->{id};
    $params->{subid} = $c->stash->{subid};
    my ( $filtered_params, $reserved_params, $path_params ) = $c->extract_reserved_params($params);
    $c->render(
        json => {
            filtered_params => $filtered_params,
            reserved_params => $reserved_params,
            path_params => $path_params
        },
        status => 200
    );
};

get '/dbic_merge_sorting' => sub {
    my $c = shift;
    my $attributes = { a => 'a', b => 'b' };
    $attributes = $c->dbic_merge_sorting(
        {
            attributes => $attributes,
            params     => { _match => 'exact', _order_by => [ 'uno', '-dos', '+tres', ' cuatro' ] }
        }
    );
    $c->render( json => $attributes, status => 200 );
};

get '/dbic_merge_sorting_single' => sub {
    my $c = shift;
    my $attributes = { a => 'a', b => 'b' };
    $attributes = $c->dbic_merge_sorting(
        {
            attributes => $attributes,
            params     => { _match => 'exact', _order_by => '-uno' }
        }
    );
    $c->render( json => $attributes, status => 200 );
};

get '/dbic_merge_sorting_result_set' => sub {
    my $c = shift;
    my $attributes = { a => 'a', b => 'b' };
    my $result_set = Koha::Cities->new;
    $attributes = $c->dbic_merge_sorting(
        {
            attributes => $attributes,
            params     => { _match => 'exact', _order_by => [ 'name', '-postal_code', '+country', ' state' ] },
            result_set => $result_set
        }
    );
    $c->render( json => $attributes, status => 200 );
};

get '/dbic_merge_sorting_date' => sub {
    my $c = shift;
    my $attributes = { a => 'a', b => 'b' };
    my $result_set = Koha::Holds->new;
    $attributes = $c->dbic_merge_sorting(
        {
            attributes => $attributes,
            params     => { _match => 'exact', _order_by => [ '-hold_date' ] },
            result_set => $result_set
        }
    );
    $c->render( json => $attributes, status => 200 );
};

get '/dbic_merge_prefetch' => sub {
    my $c = shift;
    my $attributes = {};
    my $result_set = Koha::Holds->new;
    $c->stash('koha.embed', {
            "item" => {},
            "biblio" => {
                children => {
                    "orders" => {}
                }
            }
        });

    $c->dbic_merge_prefetch({
        attributes => $attributes,
        result_set => $result_set
    });

    $c->render( json => $attributes, status => 200 );
};

get '/dbic_merge_prefetch_recursive' => sub {
    my $c = shift;
    my $attributes = {};
    my $result_set = Koha::Patron::Relationship->new;
    $c->stash('koha.embed', {
      "guarantee" => {
        "children" => {
          "article_requests" => {},
          "housebound_profile" => {
            "children" => {
              "housebound_visits" => {}
            }
          },
          "housebound_role" => {}
        }
      }
    });

    $c->dbic_merge_prefetch({
        attributes => $attributes,
        result_set => $result_set
    });

    $c->render( json => $attributes, status => 200 );
};

get '/dbic_merge_prefetch_count' => sub {
    my $c = shift;
    my $attributes = {};
    my $result_set = Koha::Patron::Relationship->new;
    $c->stash('koha.embed', {
            "guarantee_count" => {
              "is_count" => 1
            }
        });

    $c->dbic_merge_prefetch({
        attributes => $attributes,
        result_set => $result_set
    });

    $c->render( json => $attributes, status => 200 );
};

get '/merge_q_params' => sub {
  my $c = shift;
  my $filtered_params = {'biblio_id' => 1};
  my $result_set = Koha::Biblios->new;
  $filtered_params = $c->merge_q_params($filtered_params, $c->req->json->{q}, $result_set);

  $c->render( json => $filtered_params, status => 200 );
};

get '/build_query' => sub {
    my $c = shift;
    my ( $filtered_params, $reserved_params ) =
      $c->extract_reserved_params( $c->req->params->to_hash );
    my $query;
    try {
        $query = $c->build_query_params( $filtered_params, $reserved_params );
        $c->render( json => { query => $query }, status => 200 );
    }
    catch {
        $c->render(
            json => { exception_msg => $_->message, exception_type => ref($_) },
            status => 400
        );
    };
};

get '/dbic_validate_operators' => sub {
    my ( $c, $args ) = @_;

    my $query = $c->req->json->{q};

    return try {
        $c->dbic_validate_operators( { filtered_params => $query } );
        $c->render( json => { filtered_params => $query }, status => 200 );
    } catch {
        return $c->render( json => { filtered_params => $query }, status => 400 );
    };
};

get '/stash_embed' => sub {
    my $c = shift;

    try {
        $c->stash_embed(
            {
                spec => {
                    'parameters' => [
                        {
                            'in'    => 'header',
                            'name'  => 'x-koha-embed',
                            'items' => {
                                'enum' => [
                                    'checkouts', 'checkouts.item',
                                    'library',   'holds+count'
                                ]
                            }
                        }
                    ]
                }
            }
        );

        my $embed   = $c->stash('koha.embed');
        my $strings = $c->stash('koha.strings');

        $c->render(
            status => 200,
            json   => {
                strings => $strings,
                embed   => $embed
            }
        );
    }
    catch {
        $c->render(
            status => 400,
            json   => { error => "$_" }
        );
    };
};

get '/stash_embed_no_spec' => sub {
    my $c = shift;

    try {
        $c->stash_embed( { spec => {} } );

        my $embed   = $c->stash('koha.embed');
        my $strings = $c->stash('koha.strings');

        $c->render(
            status => 200,
            json   => {
                strings => $strings,
                embed   => $embed
            }
        );
    }
    catch {
        $c->render(
            status => 400,
            json   => { error => "$_" }
        );
    };
};

get '/stash_overrides' => sub {
    my $c = shift;

    $c->stash_overrides();
    my $overrides = $c->stash('koha.overrides');

    $c->render(
        status => 200,
        json   => $overrides
    );
};

get '/dbic_extended_attributes_join' => sub {
    my ( $c, $args ) = @_;

    my $filtered_params = [
        {
            '-and' => [
                [
                    {
                        'extended_attributes.attribute' => { 'like' => 'abc%' },
                        'extended_attributes.code'      => [
                            [
                                'test1',
                                'test2'
                            ]
                        ]
                    }
                ],
                [
                    {
                        'extended_attributes.code' => [
                            [
                                'test1',
                                'test2'
                            ]
                        ],
                        'extended_attributes.attribute' => { 'like' => '123%' }
                    }
                ]
            ]
        }
    ];
    my $attributes = { 'prefetch' => ['extended_attributes'] };

    my $result_set = Koha::Patrons->new;

    $c->render( json => { 'attributes' => $attributes, 'filtered_params' => $filtered_params }, status => 200 );
};

get '/dbic_extended_attributes_join_multiple_values' => sub {
    my ( $c, $args ) = @_;

    my $filtered_params = [
        {
            '-and' => [
                [
                    {
                        'extended_attributes.attribute' => { 'like' => 'abc%' },
                        'extended_attributes.code'      => 'CODE_1'
                    }
                ],
                [
                    {
                        'extended_attributes.code'      => 'CODE_2',
                        'extended_attributes.attribute' => { 'like' => '123%' }
                    }
                ]
            ]
        }
    ];
    my $attributes = { 'prefetch' => ['extended_attributes'] };

    my $result_set = Koha::Patrons->new;

    $c->render( json => { 'attributes' => $attributes, 'filtered_params' => $filtered_params }, status => 200 );
};

sub to_model {
    my ($args) = @_;
    $args->{three} = delete $args->{tres}
        if exists $args->{tres};
    return $args;
}

# The tests

use Test::More tests => 10;
use Test::Mojo;

subtest 'extract_reserved_params() tests' => sub {

    plan tests => 9;

    my $t = Test::Mojo->new;

    $t->get_ok('/query?_page=2&_per_page=3&firstname=Manuel&surname=Cohen%20Arazi')->status_is(200)
      ->json_is( '/filtered_params' =>
          { firstname => 'Manuel', surname => 'Cohen Arazi' } )
      ->json_is( '/reserved_params' => { _page => 2, _per_page => 3 } );

    $t->get_ok('/query_full/with/path?_match=exact&_order_by=blah&_page=2&_per_page=3&firstname=Manuel&surname=Cohen%20Arazi')->status_is(200)
      ->json_is(
        '/filtered_params' => {
            firstname => 'Manuel',
            surname   => 'Cohen Arazi'
        } )
      ->json_is(
        '/reserved_params' => {
            _page     => 2,
            _per_page => 3,
            _match    => 'exact',
            _order_by => 'blah'
        } )
      ->json_is(
        '/path_params' => {
            id => 'with',
            subid => 'path'
        } );

};

subtest 'dbic_merge_sorting() tests' => sub {

    plan tests => 20;

    my $t = Test::Mojo->new;

    $t->get_ok('/dbic_merge_sorting')->status_is(200)
      ->json_is( '/a' => 'a', 'Existing values are kept (a)' )
      ->json_is( '/b' => 'b', 'Existing values are kept (b)' )->json_is(
        '/order_by' => [
            'uno',
            { -desc => 'dos' },
            { -asc  => 'tres' },
            { -asc  => 'cuatro' }
        ]
      );

    $t->get_ok('/dbic_merge_sorting_result_set')->status_is(200)
      ->json_is( '/a' => 'a', 'Existing values are kept (a)' )
      ->json_is( '/b' => 'b', 'Existing values are kept (b)' )->json_is(
        '/order_by' => [
            'city_name',
            { -desc => 'city_zipcode' },
            { -asc  => 'city_country' },
            { -asc  => 'city_state' }
        ]
      );

    $t->get_ok('/dbic_merge_sorting_date')->status_is(200)
      ->json_is( '/a' => 'a', 'Existing values are kept (a)' )
      ->json_is( '/b' => 'b', 'Existing values are kept (b)' )->json_is(
        '/order_by' => [
            { -desc => 'reservedate' }
        ]
      );

    $t->get_ok('/dbic_merge_sorting_single')->status_is(200)
      ->json_is( '/a' => 'a', 'Existing values are kept (a)' )
      ->json_is( '/b' => 'b', 'Existing values are kept (b)' )->json_is(
        '/order_by' => [
            { '-desc' => 'uno' }
        ]
      );
};

subtest '/dbic_merge_prefetch' => sub {
    plan tests => 10;

    my $t = Test::Mojo->new;

    $t->get_ok('/dbic_merge_prefetch')->status_is(200)
      ->json_is( '/prefetch/0' => { 'biblio' => 'orders' } )
      ->json_is( '/prefetch/1' => 'item' );

    $t->get_ok('/dbic_merge_prefetch_recursive')->status_is(200)
      ->json_is('/prefetch/0' => {
        guarantee => [
          'article_requests',
          {housebound_profile => 'housebound_visits'},
          'housebound_role'
        ]
      });

    $t->get_ok('/dbic_merge_prefetch_count')->status_is(200)
      ->json_is('/prefetch/0' => 'guarantee');
};

subtest '/merge_q_params' => sub {
  plan tests => 3;
  my $t = Test::Mojo->new;

  $t->get_ok('/merge_q_params' => json => {
    q => {
      "-not_bool" => "suggestions.suggester.patron_card_lost",
      "-or" => [
        {
          "creation_date" => {
            "!=" => ["fff", "zzz", "xxx"]
          }
        },
        { "suggestions.suggester.housebound_profile.frequency" => "123" },
        {
          "suggestions.suggester.library_id" => {"like" => "%CPL%"}
        }
      ]
    }
  })->status_is(200)
    ->json_is( '/-and' => [
        {
          "-not_bool" => "suggester.lost",
          "-or" => [
            {
              "datecreated" => {
                "!=" => [
                  "fff",
                  "zzz",
                  "xxx"
                ]
              }
            },
            {
              "housebound_profile.frequency" => 123
            },
            {
              "suggester.branchcode" => {
                "like" => "\%CPL\%"
              }
            }
          ]
        },
        {
          "biblio_id" => 1
        }
      ]);
};

subtest '_build_query_params_from_api' => sub {

    plan tests => 16;

    my $t = Test::Mojo->new;

    # _match => contains
    $t->get_ok('/build_query?_match=contains&title=Ender&author=Orson')
      ->status_is(200)
      ->json_is( '/query' =>
          { author => { like => '%Orson%' }, title => { like => '%Ender%' } } );

    # _match => starts_with
    $t->get_ok('/build_query?_match=starts_with&title=Ender&author=Orson')
      ->status_is(200)
      ->json_is( '/query' =>
          { author => { like => 'Orson%' }, title => { like => 'Ender%' } } );

    # _match => ends_with
    $t->get_ok('/build_query?_match=ends_with&title=Ender&author=Orson')
      ->status_is(200)
      ->json_is( '/query' =>
          { author => { like => '%Orson' }, title => { like => '%Ender' } } );

    # _match => exact
    $t->get_ok('/build_query?_match=exact&title=Ender&author=Orson')
      ->status_is(200)
      ->json_is( '/query' => { author => 'Orson', title => 'Ender' } );

    # _match => blah
    $t->get_ok('/build_query?_match=blah&title=Ender&author=Orson')
      ->status_is(400)
      ->json_is( '/exception_msg'  => 'Invalid value for _match param (blah)' )
      ->json_is( '/exception_type' => 'Koha::Exceptions::WrongParameter' );

};

subtest 'stash_embed() tests' => sub {

    plan tests => 19;

    my $t = Test::Mojo->new;

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'checkouts,checkouts.item' } )
      ->json_is( '/embed' => { checkouts => { children => { item => { } } } } );

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'checkouts,checkouts.item,library' } )
      ->json_is( '/embed' => { checkouts => { children => { item => {} } }, library => {} } );

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'holds+count' } )
      ->json_is( '/embed' => { holds_count => { is_count => 1 } } );

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'holds:count' } )
      ->json_is( '/embed' => { holds_count => { is_count => 1 } } );

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'checkouts,checkouts.item,patron' } )
      ->json_is( '/embed' => {
            checkouts => { children => { item => {} } },
            patron    => {}
        });

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'checkouts,checkouts.item+strings,patron+strings' } )
      ->json_is( '/embed' => {
            checkouts => { children => { item => { strings => 1 } } },
            patron    => { strings => 1 }
        })
      ->json_is( '/strings' => undef );

    $t->get_ok( '/stash_embed' => { 'x-koha-embed' => 'checkouts+strings,checkouts.item,patron,+strings' } )
      ->json_is( '/embed' => {
            checkouts => { children => { item => { } }, strings => 1 },
            patron    => { }
        })
      ->json_is( '/strings' => 1 );

    $t->get_ok( '/stash_embed_no_spec' => { 'x-koha-embed' => 'checkouts,checkouts.item,patron' } )
      ->status_is(400)
      ->json_is( '/error' =>
          qq{Exception 'Koha::Exceptions::BadParameter' thrown 'Embedding objects is not allowed on this endpoint.'\n}
      );
};

subtest 'stash_overrides() tests' => sub {

    plan tests => 6;

    my $t = Test::Mojo->new;

    $t->get_ok( '/stash_overrides' => { 'x-koha-override' => 'any,none,some_other,any,' } )
      ->json_is( { 'any' => 1, 'none' => 1, 'some_other' => 1 } ); # empty string and duplicates are skipped

    $t->get_ok( '/stash_overrides' => { 'x-koha-override' => '' } )
      ->json_is( {} ); # empty string is skipped

    $t->get_ok( '/stash_overrides' => { } )
      ->json_is( {} ); # x-koha-ovverride not passed is skipped

};

subtest 'dbic_extended_attributes_join() tests' => sub {

    plan tests => 4;

    my $t = Test::Mojo->new;

    $t->get_ok( '/dbic_extended_attributes_join' => { 'x-koha-embed' => 'extended_attributes' } )->json_has(
        '/attributes' => {
            'join' => [
                'extended_attributes',
                'extended_attributes'
            ],
            'prefetch' => ['extended_attributes']
        }
    );

    $t->get_ok( '/dbic_extended_attributes_join' => { 'x-koha-embed' => 'extended_attributes' } )->json_has(
        '/filtered_params' => [
            {
                '-and' => [
                    [
                        {
                            'extended_attributes.code' => [
                                [
                                    'test1',
                                    'test2'
                                ]
                            ],
                            'extended_attributes.attribute' => { 'like' => 'abc%' }
                        }
                    ],
                    [
                        {
                            'extended_attributes_2.attribute' => { 'like' => '123%' },
                            'extended_attributes_2.code'      => [
                                [
                                    'test1',
                                    'test2'
                                ]
                            ]
                        }
                    ]
                ]
            }
        ]
    );
};

subtest 'dbic_validate_operators' => sub {
    plan tests => 16;

    my $t = Test::Mojo->new;

    # Valid queries
    my $q = {};
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    $q = [];
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    $q = {
        firstname => 'Bilbo',
        lastname  => 'Baggins'
    };
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    $q = {
        firstname => undef,
        lastname  => 'Baggins'
    };
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    $q = { lastname => [ 'Gaggins', 'Gamgee' ] };
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    $q = { lastname => { '!=' => [ 'Gaggins', 'Gamgee' ] } };
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    $q = { status => { '!=', 'completed', -not_like => 'pending%' } };
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(200);

    # Invalid queries
    $q = [ { "-and" => [ [ { "biblio_id" => { "like(sleep(1/100000))or" => "%a%" } } ] ] } ];
    $t->get_ok( '/dbic_validate_operators' => json => { q => $q } )->status_is(400);
};

subtest 'dbic_extended_attributes_join() tests' => sub {

    plan tests => 4;

    my $t = Test::Mojo->new;

    $t->get_ok( '/dbic_extended_attributes_join_multiple_values' => { 'x-koha-embed' => 'extended_attributes' } )
        ->json_has(
        '/attributes' => {
            'join' => [
                'extended_attributes_CODE_1',
                'extended_attributes_CODE_2'
            ],
            'prefetch' => ['extended_attributes']
        }
        );

    $t->get_ok( '/dbic_extended_attributes_join' => { 'x-koha-embed' => 'extended_attributes' } )->json_has(
        '/filtered_params' => [
            {
                '-and' => [
                    [
                        {
                            'extended_attributes_CODE_1.code'      => 'CODE_1',
                            'extended_attributes_CODE_1.attribute' => { 'like' => 'abc%' }
                        }
                    ],
                    [
                        {
                            'extended_attributes_CODE_2.code'      => 'CODE_2',
                            'extended_attributes_CODE_2.attribute' => { 'like' => 'abc%' }
                        }
                    ],
                ]
            }
        ]
    );
};
